mirror of
https://github.com/moodle/moodle.git
synced 2025-08-08 18:36:42 +02:00
Merge branch 'master_MDL-71696-versioning-integration' of https://github.com/catalyst/moodle-MDL-70329
This commit is contained in:
commit
b841a811be
223 changed files with 7768 additions and 2899 deletions
|
@ -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"/>
|
||||
|
|
|
@ -61,7 +61,6 @@ $renamedclasses = [
|
|||
'core_question\\bank\\copy_action_column' => 'qbank_editquestion\\copy_action_column',
|
||||
'core_question\\bank\\edit_action_column' => 'qbank_editquestion\\edit_action_column',
|
||||
'core_question\\bank\\creator_name_column' => 'qbank_viewcreator\\creator_name_column',
|
||||
'core_question\\bank\\modifier_name_column' => 'qbank_viewcreator\\modifier_name_column',
|
||||
'core_question\\bank\\question_name_column' => 'qbank_viewquestionname\\viewquestionname_column_helper',
|
||||
'core_question\\bank\\question_name_idnumber_tags_column' => 'qbank_viewquestionname\\question_name_idnumber_tags_column',
|
||||
'core_question\\bank\\delete_action_column' => 'qbank_deletequestion\\delete_action_column',
|
||||
|
@ -81,5 +80,7 @@ $renamedclasses = [
|
|||
'export_form' => 'qbank_exportquestions\\form\\export_form',
|
||||
'preview_options_form' => 'qbank_previewquestion\\form\\preview_options_form',
|
||||
'question_preview_options' => 'qbank_previewquestion\\output\\question_preview_options',
|
||||
'core_question\\form\\tags' => '\qbank_tagquestion\\form\\tags_form'
|
||||
'core_question\\form\\tags' => 'qbank_tagquestion\\form\\tags_form',
|
||||
'context_to_string_translator' => 'core_question\\local\\bank\\context_to_string_translator',
|
||||
'question_edit_contexts' => 'core_question\\local\\bank\\question_edit_contexts',
|
||||
];
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
1436
lib/questionlib.php
1436
lib/questionlib.php
File diff suppressed because it is too large
Load diff
|
@ -109,6 +109,18 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
return array($category, $course, $quiz, $qcat, $questions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a category contains a specific number of questions.
|
||||
*
|
||||
* @param int $categoryid int Category id.
|
||||
* @param int $numberofquestions Number of question in a category.
|
||||
* @return void Questions in a category.
|
||||
*/
|
||||
protected function assert_category_contains_questions(int $categoryid, int $numberofquestions): void {
|
||||
$questionsid = question_bank::get_finder()->get_questions_from_categories([$categoryid], null);
|
||||
$this->assertEquals($numberofquestions, count($questionsid));
|
||||
}
|
||||
|
||||
public function test_question_reorder_qtypes() {
|
||||
$this->assertEquals(
|
||||
array(0 => 't2', 1 => 't1', 2 => 't3'),
|
||||
|
@ -222,8 +234,7 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
|
||||
// Create some question categories and questions in this course.
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$questioncat = $questiongenerator->create_question_category(array('contextid' =>
|
||||
$coursecontext->id));
|
||||
$questioncat = $questiongenerator->create_question_category(array('contextid' => $coursecontext->id));
|
||||
$question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id));
|
||||
$question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id));
|
||||
|
||||
|
@ -256,8 +267,14 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
array(context_course::instance($course2->id)->id), '*', MUST_EXIST);
|
||||
|
||||
// Check that there are two questions in the restored to course's context.
|
||||
$this->assertEquals(2, $DB->count_records('question', array('category' => $restoredcategory->id)));
|
||||
|
||||
$this->assertEquals(2, $DB->get_record_sql('SELECT COUNT(q.id) as questioncount
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv
|
||||
ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe
|
||||
ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid = ?',
|
||||
[$restoredcategory->id])->questioncount);
|
||||
$rc->destroy();
|
||||
}
|
||||
|
||||
|
@ -327,8 +344,7 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$this->assertEquals(0, $DB->count_records('question_categories', $criteria));
|
||||
|
||||
// Verify questions deleted or moved.
|
||||
$criteria = array('category' => $qcat->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', $criteria));
|
||||
$this->assert_category_contains_questions($qcat->id, 0);
|
||||
|
||||
// Verify question not deleted.
|
||||
$criteria = array('id' => $questions[0]->id);
|
||||
|
@ -355,8 +371,7 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$this->assertEquals(0, $DB->count_records('question_categories', $criteria));
|
||||
|
||||
// Verify questions deleted or moved.
|
||||
$criteria = array('category' => $qcat->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', $criteria));
|
||||
$this->assert_category_contains_questions($qcat->id, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -377,8 +392,7 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$this->assertEquals(0, $DB->count_records('question_categories', $criteria));
|
||||
|
||||
// Verify questions deleted or moved.
|
||||
$criteria = array('category' => $qcat->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', $criteria));
|
||||
$this->assert_category_contains_questions($qcat->id, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -399,8 +413,7 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$this->assertEquals(0, $DB->count_records('question_categories', $criteria));
|
||||
|
||||
// Verify questions deleted or moved.
|
||||
$criteria = array('category' => $qcat->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', $criteria));
|
||||
$this->assert_category_contains_questions($qcat->id, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -421,8 +434,7 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$this->assertEquals(0, $DB->count_records('question_categories', $criteria));
|
||||
|
||||
// Verify questions deleted or moved.
|
||||
$criteria = array('category' => $qcat->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', $criteria));
|
||||
$this->assert_category_contains_questions($qcat->id, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -449,9 +461,11 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
// Verify questions are moved.
|
||||
$params = array($qcat2->contextid);
|
||||
$actualquestionscount = $DB->count_records_sql("SELECT COUNT(*)
|
||||
FROM {question} q
|
||||
JOIN {question_categories} qc ON q.category = qc.id
|
||||
WHERE qc.contextid = ?", $params);
|
||||
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 = ?", $params);
|
||||
$this->assertEquals($questionsinqcat1 + $questionsinqcat2, $actualquestionscount);
|
||||
|
||||
// Verify there is just a single top-level category.
|
||||
|
@ -1728,16 +1742,11 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$qtype = 'truefalse';
|
||||
$overrides = [
|
||||
'category' => $questioncat->id,
|
||||
'createdby' => ($isowner) ? $user->id : $otheruser->id,
|
||||
];
|
||||
|
||||
$question = $questiongenerator->create_question($qtype, null, $overrides);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, null);
|
||||
$fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on($question, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
|
@ -1779,16 +1788,11 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$qtype = 'truefalse';
|
||||
$overrides = [
|
||||
'category' => $questioncat->id,
|
||||
'createdby' => ($isowner) ? $user->id : $otheruser->id,
|
||||
];
|
||||
|
||||
$question = $questiongenerator->create_question($qtype, null, $overrides);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, null);
|
||||
$fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on($question->id, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
|
@ -1830,16 +1834,11 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$qtype = 'truefalse';
|
||||
$overrides = [
|
||||
'category' => $questioncat->id,
|
||||
'createdby' => ($isowner) ? $user->id : $otheruser->id,
|
||||
];
|
||||
|
||||
$question = $questiongenerator->create_question($qtype, null, $overrides);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, null);
|
||||
$fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on((string) $question->id, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
|
@ -1887,16 +1886,11 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$qtype = 'truefalse';
|
||||
$overrides = [
|
||||
'category' => $questioncat->id,
|
||||
'createdby' => ($isowner) ? $user->id : $otheruser->id,
|
||||
];
|
||||
|
||||
$question = $questiongenerator->create_question($qtype, null, $overrides);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, null);
|
||||
$fromform = (object) $generator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
// Move the question.
|
||||
question_move_questions_to_category([$question->id], $newquestioncat->id);
|
||||
|
||||
|
@ -1941,12 +1935,10 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
// Create the question.
|
||||
$question = $questiongenerator->create_question('truefalse', null, [
|
||||
'category' => $questioncat->id,
|
||||
'createdby' => ($isowner) ? $user->id : $otheruser->id,
|
||||
]);
|
||||
$question = question_bank::load_question_data($question->id);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = ($isowner) ? $user->id : $otheruser->id;
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on($question, $capability);
|
||||
$this->assertEquals($expect, $result);
|
||||
|
@ -1970,12 +1962,10 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
// Create the question.
|
||||
$question = $questiongenerator->create_question('truefalse', null, [
|
||||
'category' => $questioncat->id,
|
||||
'createdby' => $user->id,
|
||||
]);
|
||||
$question = question_bank::load_question_data($question->id);
|
||||
|
||||
// The question generator does not support setting of the createdby for some reason.
|
||||
$question->createdby = $user->id;
|
||||
|
||||
$this->setUser($user);
|
||||
$result = question_has_capability_on((string)$question->id, 'tag');
|
||||
$this->assertFalse($result);
|
||||
|
@ -2057,4 +2047,218 @@ class core_questionlib_testcase extends advanced_testcase {
|
|||
$this->assertSame('id11', core_question_find_next_unused_idnumber('id9', $category->id));
|
||||
$this->assertSame('id11', core_question_find_next_unused_idnumber('id8', $category->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the question_move_questions_to_category function.
|
||||
*
|
||||
* @covers ::question_move_questions_to_category
|
||||
*/
|
||||
public function test_question_move_questions_to_category() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test data.
|
||||
list($category1, $course1, $quiz1, $questioncat1, $questions1) = $this->setup_quiz_and_questions();
|
||||
list($category2, $course2, $quiz2, $questioncat2, $questions2) = $this->setup_quiz_and_questions();
|
||||
|
||||
$this->assertCount(2, $questions1);
|
||||
$this->assertCount(2, $questions2);
|
||||
$questionsidtomove = [];
|
||||
foreach ($questions1 as $question1) {
|
||||
$questionsidtomove[] = $question1->id;
|
||||
}
|
||||
|
||||
// Move the question from quiz 1 to quiz 2.
|
||||
question_move_questions_to_category($questionsidtomove, $questioncat2->id);
|
||||
$this->assert_category_contains_questions($questioncat2->id, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for the idnumber_exist_in_question_category function.
|
||||
*
|
||||
* @covers ::idnumber_exist_in_question_category
|
||||
*/
|
||||
public function test_idnumber_exist_in_question_category() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test data.
|
||||
list($category1, $course1, $quiz1, $questioncat1, $questions1) = $this->setup_quiz_and_questions();
|
||||
list($category2, $course2, $quiz2, $questioncat2, $questions2) = $this->setup_quiz_and_questions();
|
||||
|
||||
$questionbankentry1 = get_question_bank_entry($questions1[0]->id);
|
||||
$entry = new stdClass();
|
||||
$entry->id = $questionbankentry1->id;
|
||||
$entry->idnumber = 1;
|
||||
$DB->update_record('question_bank_entries', $entry);
|
||||
|
||||
$questionbankentry2 = get_question_bank_entry($questions2[0]->id);
|
||||
$entry2 = new stdClass();
|
||||
$entry2->id = $questionbankentry2->id;
|
||||
$entry2->idnumber = 1;
|
||||
$DB->update_record('question_bank_entries', $entry2);
|
||||
|
||||
$questionbe = $DB->get_record('question_bank_entries', ['id' => $questionbankentry1->id]);
|
||||
|
||||
// Validate that a first stage of an idnumber exists (this format: xxxx_x).
|
||||
list($response, $record) = idnumber_exist_in_question_category($questionbe->idnumber, $questioncat1->id);
|
||||
$this->assertEquals([], $record);
|
||||
$this->assertEquals(true, $response);
|
||||
|
||||
// Move the question to a category that has a question with the same idnumber.
|
||||
question_move_questions_to_category($questions1[0]->id, $questioncat2->id);
|
||||
|
||||
// Validate that function return the last record used for the idnumber.
|
||||
list($response, $record) = idnumber_exist_in_question_category($questionbe->idnumber, $questioncat2->id);
|
||||
$record = reset($record);
|
||||
$idnumber = $record->idnumber;
|
||||
$this->assertEquals($idnumber, '1_1');
|
||||
$this->assertEquals(true, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method is_latest().
|
||||
*
|
||||
* @covers ::is_latest
|
||||
*
|
||||
*/
|
||||
public function test_is_latest() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$qcat1 = $generator->create_question_category(['name' => 'My category', 'sortorder' => 1, 'idnumber' => 'myqcat']);
|
||||
$question = $generator->create_question('shortanswer', null, ['name' => 'q1', 'category' => $qcat1->id]);
|
||||
$record = $DB->get_record('question_versions', ['questionid' => $question->id]);
|
||||
$firstversion = $record->version;
|
||||
$questionbankentryid = $record->questionbankentryid;
|
||||
$islatest = is_latest($firstversion, $questionbankentryid);
|
||||
$this->assertTrue($islatest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test question bank entry deletion.
|
||||
*
|
||||
* @covers ::delete_question_bank_entry
|
||||
*/
|
||||
public function test_delete_question_bank_entry() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
// Setup.
|
||||
$context = context_system::instance();
|
||||
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$qcat = $qgen->create_question_category(array('contextid' => $context->id));
|
||||
$q1 = $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
|
||||
// Make sure there is an entry in the entry table.
|
||||
$sql = 'SELECT qbe.id as id,
|
||||
qv.id as versionid
|
||||
FROM {question_bank_entries} qbe
|
||||
JOIN {question_versions} qv
|
||||
ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question} q
|
||||
ON qv.questionid = q.id
|
||||
WHERE q.id = ?';
|
||||
$records = $DB->get_records_sql($sql, [$q1->id]);
|
||||
$this->assertCount(1, $records);
|
||||
// Delete the record.
|
||||
$record = reset($records);
|
||||
delete_question_bank_entry($record->id);
|
||||
$records = $DB->get_records('question_bank_entries', ['id' => $record->id]);
|
||||
// As the version record exists, it wont delete the data to resolve any errors.
|
||||
$this->assertCount(1, $records);
|
||||
$DB->delete_records('question_versions', ['id' => $record->versionid]);
|
||||
delete_question_bank_entry($record->id);
|
||||
$records = $DB->get_records('question_bank_entries', ['id' => $record->id]);
|
||||
$this->assertCount(0, $records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test question bank entry object.
|
||||
*
|
||||
* @covers ::get_question_bank_entry
|
||||
*/
|
||||
public function test_get_question_bank_entry() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
// Setup.
|
||||
$context = context_system::instance();
|
||||
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$qcat = $qgen->create_question_category(array('contextid' => $context->id));
|
||||
$q1 = $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
|
||||
// Make sure there is an entry in the entry table.
|
||||
$sql = 'SELECT qbe.id as id,
|
||||
qv.id as versionid
|
||||
FROM {question_bank_entries} qbe
|
||||
JOIN {question_versions} qv
|
||||
ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question} q
|
||||
ON qv.questionid = q.id
|
||||
WHERE q.id = ?';
|
||||
$records = $DB->get_records_sql($sql, [$q1->id]);
|
||||
$this->assertCount(1, $records);
|
||||
$record = reset($records);
|
||||
$questionbankentry = get_question_bank_entry($q1->id);
|
||||
$this->assertEquals($questionbankentry->id, $record->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the version objects for a question.
|
||||
*
|
||||
* @covers ::get_question_version
|
||||
*/
|
||||
public function test_get_question_version() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
// Setup.
|
||||
$context = context_system::instance();
|
||||
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$qcat = $qgen->create_question_category(array('contextid' => $context->id));
|
||||
$q1 = $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
|
||||
// Make sure there is an entry in the entry table.
|
||||
$sql = 'SELECT qbe.id as id,
|
||||
qv.id as versionid
|
||||
FROM {question_bank_entries} qbe
|
||||
JOIN {question_versions} qv
|
||||
ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question} q
|
||||
ON qv.questionid = q.id
|
||||
WHERE q.id = ?';
|
||||
$records = $DB->get_records_sql($sql, [$q1->id]);
|
||||
$this->assertCount(1, $records);
|
||||
$record = reset($records);
|
||||
$questionversions = get_question_version($q1->id);
|
||||
$questionversion = reset($questionversions);
|
||||
$this->assertEquals($questionversion->id, $record->versionid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get next version of a question.
|
||||
*
|
||||
* @covers ::get_next_version
|
||||
*/
|
||||
public function test_get_next_version() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
// Setup.
|
||||
$context = context_system::instance();
|
||||
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$qcat = $qgen->create_question_category(array('contextid' => $context->id));
|
||||
$q1 = $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
|
||||
// Make sure there is an entry in the entry table.
|
||||
$sql = 'SELECT qbe.id as id,
|
||||
qv.id as versionid,
|
||||
qv.version
|
||||
FROM {question_bank_entries} qbe
|
||||
JOIN {question_versions} qv
|
||||
ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question} q
|
||||
ON qv.questionid = q.id
|
||||
WHERE q.id = ?';
|
||||
$records = $DB->get_records_sql($sql, [$q1->id]);
|
||||
$this->assertCount(1, $records);
|
||||
$record = reset($records);
|
||||
$this->assertEquals(1, $record->version);
|
||||
$nextversion = get_next_version($record->id);
|
||||
$this->assertEquals(2, $nextversion);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -99,6 +99,51 @@ information provided here is intended especially for developers.
|
|||
- question_preview_popup_params() is moved to \qbank_previewquestion\helper::question_preview_popup_params()
|
||||
Calling these functions in the question will point to the plugin, but the deprecation message will be activated in MDL-72004.
|
||||
The deprecated codes are removed from the questionlib for those two methods.
|
||||
* Function question_hash() from questionlib.php is deprecated without replacement.
|
||||
* Some of the new and old methods in the questionlib.php now using type hinting. Please make a note of this while making changes
|
||||
or implementing any question bank related feature in a plugin. These are the list of methods:
|
||||
- is_latest()
|
||||
- get_next_version()
|
||||
- get_question_version()
|
||||
- get_question_bank_entry()
|
||||
- core_question_find_next_unused_idnumber()
|
||||
- question_module_uses_questions()
|
||||
- question_page_type_list()
|
||||
- core_question_question_preview_pluginfile()
|
||||
- question_rewrite_question_preview_urls()
|
||||
- question_rewrite_question_urls()
|
||||
- question_get_all_capabilities()
|
||||
- question_get_question_capabilities()
|
||||
- question_require_capability_on()
|
||||
- question_has_capability_on()
|
||||
- question_default_export_filename()
|
||||
- get_import_export_formats()
|
||||
- question_categorylist_parents()
|
||||
- question_categorylist()
|
||||
- question_make_default_categories()
|
||||
- question_get_top_categories_for_contexts()
|
||||
- sort_categories_by_tree()
|
||||
- print_question_icon()
|
||||
- question_sort_tags()
|
||||
- _tidy_question()
|
||||
- question_preload_questions()
|
||||
- question_move_category_to_context()
|
||||
- move_question_set_references()
|
||||
- question_move_questions_to_category()
|
||||
- idnumber_exist_in_question_category()
|
||||
- question_move_question_tags_to_new_context()
|
||||
- question_delete_activity()
|
||||
- question_delete_course_category()
|
||||
- question_delete_course()
|
||||
- question_delete_context()
|
||||
- question_delete_question()
|
||||
- delete_question_bank_entry()
|
||||
- question_category_in_use()
|
||||
- question_category_delete_safe()
|
||||
- question_context_has_any_questions()
|
||||
- questions_in_use()
|
||||
- question_save_qtype_order()
|
||||
- question_reorder_qtypes()
|
||||
* The postgres driver now wraps calls to pg_field_type() and caches them in databasemeta to save an invisible internal
|
||||
DB call on every request.
|
||||
* The default type of 'core/toast' messages has been changed to 'information' (callers can still explicitely set the type)
|
||||
|
@ -116,8 +161,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
|
||||
|
@ -176,6 +232,7 @@ value to get the list of blocks that won't be displayed for a theme.
|
|||
* A new parameter $strength of type int is added to method search_for_active_node. This parameter would help us to search for the active nodes based on the
|
||||
$strength passed to it.
|
||||
|
||||
|
||||
=== 3.11.4 ===
|
||||
* A new option dontforcesvgdownload has been added to the $options parameter of the send_file() function.
|
||||
Note: This option overrides the forced download of directly accessed SVGs, so should only be used where the calling method is
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue