MDL-71696 core_question: Changes for versioning
This commit adds the changes in questiontype base to work with new question tables and the new structure in the databse. Also needed for versioning. This implementation will also introduct the question status which allows a question to be in draft and ready status. I also introduces changes to the base view where it shows the latest version of the questions. The view of versions for a question is not implemented in this commit. This implementation will also introduce changes in the core qtype plugins to support versioning and the changed db schema.
This commit is contained in:
committed by
Safat Shahin
parent
7547f3e9f0
commit
c6cfca2a08
+242
-43
@@ -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.
|
||||
*/
|
||||
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'),
|
||||
@@ -327,8 +339,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 +366,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 +387,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 +408,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 +429,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 +456,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_entry} 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 +1737,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 +1783,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 +1829,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 +1881,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 +1930,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 +1957,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 +2042,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_entry', $entry);
|
||||
|
||||
$questionbankentry2 = get_question_bank_entry($questions2[0]->id);
|
||||
$entry2 = new stdClass();
|
||||
$entry2->id = $questionbankentry2->id;
|
||||
$entry2->idnumber = 1;
|
||||
$DB->update_record('question_bank_entry', $entry2);
|
||||
|
||||
$questionbe = $DB->get_record('question_bank_entry', ['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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user