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
+38
-8
@@ -42,25 +42,55 @@ $qtypes = question_bank::get_all_qtypes();
|
||||
$pluginmanager = core_plugin_manager::instance();
|
||||
|
||||
// Get some data we will need - question counts and which types are needed.
|
||||
$counts = $DB->get_records_sql("
|
||||
SELECT qtype, COUNT(1) as numquestions, SUM(hidden) as numhidden
|
||||
FROM {question} GROUP BY qtype", array());
|
||||
$needed = array();
|
||||
$hiddenstatus = \core_question\local\bank\constants::QUESTION_STATUS_HIDDEN;
|
||||
$draftstatus = \core_question\local\bank\constants::QUESTION_STATUS_DRAFT;
|
||||
|
||||
$sql = "SELECT result.qtype,
|
||||
SUM(result.numquestions) AS numquestions,
|
||||
SUM(result.numhidden) AS numhidden,
|
||||
SUM(result.numdraft) AS numdraft
|
||||
FROM (
|
||||
SELECT data.qtype,
|
||||
COUNT(data.numquestions) AS numquestions,
|
||||
(SELECT COUNT(qv.id)
|
||||
FROM {question_versions} qv
|
||||
WHERE qv.id = data.versionid
|
||||
AND qv.status = :hiddenstatus) AS numhidden,
|
||||
(SELECT COUNT(qv.id)
|
||||
FROM {question_versions} qv
|
||||
WHERE qv.id = data.versionid
|
||||
AND qv.status = :draftstatus) AS numdraft
|
||||
FROM (
|
||||
SELECT q.qtype, qv.id AS versionid, 1 AS numquestions
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
AND qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qbe.id)) data
|
||||
GROUP BY data.qtype, data.versionid) result
|
||||
GROUP BY result.qtype";
|
||||
|
||||
$counts = $DB->get_records_sql($sql, ['hiddenstatus' => $hiddenstatus, 'draftstatus' => $draftstatus]);
|
||||
$needed = [];
|
||||
foreach ($qtypes as $qtypename => $qtype) {
|
||||
if (!isset($counts[$qtypename])) {
|
||||
$counts[$qtypename] = new stdClass;
|
||||
$counts[$qtypename]->numquestions = 0;
|
||||
$counts[$qtypename]->numhidden = 0;
|
||||
$counts[$qtypename]->numdraft = 0;
|
||||
}
|
||||
$needed[$qtypename] = $counts[$qtypename]->numquestions > 0 ||
|
||||
$pluginmanager->other_plugins_that_require($qtype->plugin_name());
|
||||
$counts[$qtypename]->numquestions -= $counts[$qtypename]->numhidden;
|
||||
$counts[$qtypename]->numquestions -= ($counts[$qtypename]->numhidden + $counts[$qtypename]->numdraft);
|
||||
}
|
||||
$needed['missingtype'] = true; // The system needs the missing question type.
|
||||
foreach ($counts as $qtypename => $count) {
|
||||
if (!isset($qtypes[$qtypename])) {
|
||||
$counts['missingtype']->numquestions += $count->numquestions - $count->numhidden;
|
||||
$counts['missingtype']->numquestions += $count->numquestions - ($count->numhidden + $count->numdraft);
|
||||
$counts['missingtype']->numhidden += $count->numhidden;
|
||||
$counts['missingtype']->numdraft += $count->numdraft;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +183,8 @@ foreach ($sortedqtypes as $qtypename => $localname) {
|
||||
$row[] = $icon . ' ' . $localname;
|
||||
|
||||
// Number of questions of this type.
|
||||
if ($counts[$qtypename]->numquestions + $counts[$qtypename]->numhidden > 0) {
|
||||
if ($counts[$qtypename]->numhidden > 0) {
|
||||
if ($counts[$qtypename]->numquestions + $counts[$qtypename]->numhidden + $counts[$qtypename]->numdraft > 0) {
|
||||
if ($counts[$qtypename]->numhidden + $counts[$qtypename]->numdraft > 0) {
|
||||
$strcount = get_string('numquestionsandhidden', 'question', $counts[$qtypename]);
|
||||
} else {
|
||||
$strcount = $counts[$qtypename]->numquestions;
|
||||
|
||||
@@ -257,7 +257,7 @@ $string['notflagged'] = 'Not flagged';
|
||||
$string['novirtualquestiontype'] = 'No virtual question type for question type {$a}';
|
||||
$string['numqas'] = 'No. question attempts';
|
||||
$string['numquestions'] = 'No. questions';
|
||||
$string['numquestionsandhidden'] = '{$a->numquestions} (+{$a->numhidden} hidden)';
|
||||
$string['numquestionsandhidden'] = '{$a->numquestions} (+{$a->numhidden} hidden +{$a->numdraft} draft)';
|
||||
$string['page-question-x'] = 'Any question page';
|
||||
$string['page-question-edit'] = 'Question editing page';
|
||||
$string['page-question-category'] = 'Question category page';
|
||||
@@ -498,3 +498,4 @@ $string['xoutofmax'] = '{$a->mark} out of {$a->max}';
|
||||
$string['yougotnright'] = 'You have correctly selected {$a->num}.';
|
||||
$string['qbanknotfound'] = 'The \'{$a}\' question bank plugin doesn\'t exist or is not recognised.';
|
||||
$string['noquestionbanks'] = 'No question bank plugin found.';
|
||||
$string['questionloaderror'] = 'Could not load the question options.';
|
||||
|
||||
@@ -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',
|
||||
|
||||
+961
-705
File diff suppressed because it is too large
Load Diff
+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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -97,6 +97,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)
|
||||
@@ -183,6 +228,7 @@ value to get the list of blocks that won't be displayed for a theme.
|
||||
core_message_get_user_notification_preferences and core_message_get_user_message_preferences Webservice are now returning enabled boolean on
|
||||
components > notifications > processors. loggedin and loggedoff are deprecated but present for backward compatibility.
|
||||
|
||||
|
||||
=== 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
|
||||
|
||||
@@ -1807,8 +1807,7 @@ class quiz_attempt {
|
||||
$question->length = $replacedquestion->length;
|
||||
$question->penalty = 0;
|
||||
$question->stamp = '';
|
||||
$question->version = 0;
|
||||
$question->hidden = 0;
|
||||
$question->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$question->timecreated = null;
|
||||
$question->timemodified = null;
|
||||
$question->createdby = null;
|
||||
|
||||
@@ -46,13 +46,13 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
*
|
||||
* @param \quiz $quizobj object containing all the quiz settings information.
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\lib\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @param array $pagevars the variables from {@link question_edit_setup()}.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function edit_page(\quiz $quizobj, structure $structure,
|
||||
\question_edit_contexts $contexts, \moodle_url $pageurl, array $pagevars) {
|
||||
\core_question\lib\question_edit_contexts $contexts, \moodle_url $pageurl, array $pagevars) {
|
||||
$output = '';
|
||||
|
||||
// Information at the top.
|
||||
@@ -493,7 +493,7 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param \stdClass $section information about the section.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\lib\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
@@ -513,7 +513,7 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $slot which slot we are outputting.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\lib\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
@@ -546,7 +546,7 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $slot the first slot on the page we are outputting.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\lib\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
@@ -580,12 +580,12 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $page the page number that this menu will add to.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\lib\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function add_menu_actions(structure $structure, $page, \moodle_url $pageurl,
|
||||
\question_edit_contexts $contexts, array $pagevars) {
|
||||
\core_question\lib\question_edit_contexts $contexts, array $pagevars) {
|
||||
|
||||
$actions = $this->edit_menu_actions($structure, $page, $pageurl, $pagevars);
|
||||
if (empty($actions)) {
|
||||
@@ -1084,13 +1084,13 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
* is handled with the specific code for those.)
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\lib\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return bool Always returns true
|
||||
*/
|
||||
protected function initialise_editing_javascript(structure $structure,
|
||||
\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
|
||||
\core_question\lib\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
|
||||
|
||||
$config = new \stdClass();
|
||||
$config->resourceurl = '/mod/quiz/edit_rest.php';
|
||||
@@ -1190,13 +1190,13 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
* HTML for a page, with ids stripped, so it can be used as a javascript template.
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param \question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param \core_question\lib\question_edit_contexts $contexts the relevant question bank contexts.
|
||||
* @param array $pagevars the variables from {@link \question_edit_setup()}.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML for a new page.
|
||||
*/
|
||||
protected function new_page_template(structure $structure,
|
||||
\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
|
||||
\core_question\lib\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
|
||||
if (!$structure->has_questions()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ class custom_view extends \core_question\local\bank\view {
|
||||
const MAX_TEXT_LENGTH = 200;
|
||||
|
||||
/**
|
||||
* Constructor for custom_view.
|
||||
* @param \question_edit_contexts $contexts
|
||||
* Constructor.
|
||||
* @param \core_question\lib\question_edit_contexts $contexts
|
||||
* @param \moodle_url $pageurl
|
||||
* @param \stdClass $course course settings
|
||||
* @param \stdClass $cm activity settings.
|
||||
|
||||
@@ -48,7 +48,7 @@ class question_name_text_column extends question_name_column {
|
||||
$fields = parent::get_required_fields();
|
||||
$fields[] = 'q.questiontext';
|
||||
$fields[] = 'q.questiontextformat';
|
||||
$fields[] = 'q.idnumber';
|
||||
$fields[] = 'qbe.idnumber';
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
||||
@@ -608,10 +608,12 @@ class structure {
|
||||
|
||||
$slots = $DB->get_records_sql("
|
||||
SELECT slot.id AS slotid, slot.slot, slot.questionid, slot.page, slot.maxmark,
|
||||
slot.requireprevious, q.*, qc.contextid
|
||||
slot.requireprevious, q.*, qc.id as category, qc.contextid
|
||||
FROM {quiz_slots} slot
|
||||
LEFT JOIN {question} q ON q.id = slot.questionid
|
||||
LEFT JOIN {question_categories} qc ON qc.id = q.category
|
||||
LEFT JOIN {question} q ON q.id = slot.questionid
|
||||
LEFT JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
LEFT JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
LEFT JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
WHERE slot.quizid = ?
|
||||
ORDER BY slot.slot", array($quiz->id));
|
||||
|
||||
@@ -1237,7 +1239,7 @@ class structure {
|
||||
public function can_add_random_questions() {
|
||||
if ($this->canaddrandom === null) {
|
||||
$quizcontext = $this->quizobj->get_context();
|
||||
$relatedcontexts = new \question_edit_contexts($quizcontext);
|
||||
$relatedcontexts = new \core_question\lib\question_edit_contexts($quizcontext);
|
||||
$usablecontexts = $relatedcontexts->having_cap('moodle/question:useall');
|
||||
|
||||
$this->canaddrandom = !empty($usablecontexts);
|
||||
|
||||
@@ -53,7 +53,13 @@ $PAGE->set_url($url);
|
||||
$PAGE->set_pagelayout('admin');
|
||||
$PAGE->add_body_class('limitedwidth');
|
||||
|
||||
if (!$question = $DB->get_record('question', array('id' => $slot->questionid))) {
|
||||
// Get the question object + category id.
|
||||
$sql = "SELECT q.*, qv.status, qbe.questioncategoryid AS category
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE q.id = :id";
|
||||
if (!$question = $DB->get_record_sql($sql, ['id' => $slot->questionid])) {
|
||||
print_error('questiondoesnotexist', 'question', $returnurl);
|
||||
}
|
||||
|
||||
@@ -68,7 +74,7 @@ if (!$category = $DB->get_record('question_categories', array('id' => $question-
|
||||
question_require_capability_on($question, 'edit');
|
||||
|
||||
$thiscontext = context_module::instance($cm->id);
|
||||
$contexts = new question_edit_contexts($thiscontext);
|
||||
$contexts = new core_question\lib\question_edit_contexts($thiscontext);
|
||||
|
||||
// Create the question editing form.
|
||||
$mform = new mod_quiz\form\randomquestion_form(new moodle_url('/mod/quiz/editrandom.php'),
|
||||
|
||||
+1
-1
@@ -2421,7 +2421,7 @@ function mod_quiz_output_fragment_add_random_question_form($args) {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
|
||||
|
||||
$contexts = new \question_edit_contexts($args['context']);
|
||||
$contexts = new \core_question\lib\question_edit_contexts($args['context']);
|
||||
$formoptions = [
|
||||
'contexts' => $contexts,
|
||||
'cat' => $args['cat']
|
||||
|
||||
+12
-10
@@ -2432,15 +2432,17 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
|
||||
// Find existing random questions in this category that are
|
||||
// not used by any quiz.
|
||||
$existingquestions = $DB->get_records_sql(
|
||||
"SELECT q.id, q.qtype FROM {question} q
|
||||
WHERE qtype = 'random'
|
||||
AND category = ?
|
||||
AND " . $DB->sql_compare_text('questiontext') . " = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT *
|
||||
FROM {quiz_slots}
|
||||
WHERE questionid = q.id)
|
||||
ORDER BY id", array($category->id, $includesubcategories ? '1' : '0'));
|
||||
"SELECT q.id, q.qtype
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE q.qtype = 'random'
|
||||
AND qbe.questioncategoryid = ?
|
||||
AND " . $DB->sql_compare_text('questiontext') . " = ?
|
||||
AND NOT EXISTS (SELECT *
|
||||
FROM {quiz_slots}
|
||||
WHERE questionid = q.id)
|
||||
ORDER BY q.id", array($category->id, $includesubcategories ? '1' : '0'));
|
||||
|
||||
for ($i = 0; $i < $number; $i++) {
|
||||
// Take as many of orphaned "random" questions as needed.
|
||||
@@ -2450,7 +2452,7 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
|
||||
$form->includesubcategories = $includesubcategories;
|
||||
$form->fromtags = $tagstrings;
|
||||
$form->defaultmark = 1;
|
||||
$form->hidden = 1;
|
||||
$form->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$form->stamp = make_unique_id_code(); // Set the unique code (not to be changed).
|
||||
$question = new stdClass();
|
||||
$question->qtype = 'random';
|
||||
|
||||
@@ -57,7 +57,7 @@ class mod_quiz_local_structure_slot_random_test extends advanced_testcase {
|
||||
$form->includesubcategories = true;
|
||||
$form->fromtags = [];
|
||||
$form->defaultmark = 1;
|
||||
$form->hidden = 1;
|
||||
$form->status = \core_question\local\bank\constants::QUESTION_STATUS_HIDDEN;
|
||||
$form->stamp = make_unique_id_code();
|
||||
$question = new stdClass();
|
||||
$question->qtype = 'random';
|
||||
@@ -355,7 +355,7 @@ class mod_quiz_local_structure_slot_random_test extends advanced_testcase {
|
||||
$form->includesubcategories = true;
|
||||
$form->fromtags = [];
|
||||
$form->defaultmark = 1;
|
||||
$form->hidden = 1;
|
||||
$form->status = \core_question\local\bank\constants::QUESTION_STATUS_HIDDEN;
|
||||
$form->stamp = make_unique_id_code();
|
||||
$question = new stdClass();
|
||||
$question->qtype = 'random';
|
||||
|
||||
@@ -51,7 +51,7 @@ class quiz_question_bank_view_testcase extends advanced_testcase {
|
||||
$cm = get_coursemodule_from_instance('quiz', $quiz->id);
|
||||
|
||||
// Create a question in the default category.
|
||||
$contexts = new question_edit_contexts($context);
|
||||
$contexts = new core_question\lib\question_edit_contexts($context);
|
||||
$cat = question_make_default_categories($contexts->all());
|
||||
$questiondata = $questiongenerator->create_question('numerical', null,
|
||||
['name' => 'Example question', 'category' => $cat->id]);
|
||||
|
||||
@@ -202,7 +202,7 @@ class helper_test extends \advanced_testcase {
|
||||
public function test_get_displaydata() {
|
||||
$this->helper_setup();
|
||||
$coursecontext = \context_course::instance($this->course->id);
|
||||
$contexts = new \question_edit_contexts($coursecontext);
|
||||
$contexts = new question_edit_contexts($coursecontext);
|
||||
$addcontexts = $contexts->having_cap('moodle/question:add');
|
||||
$url = new \moodle_url('/question/bank/bulkmove/move.php');
|
||||
$displaydata = \qbank_bulkmove\helper::get_displaydata($addcontexts, $url, $url);
|
||||
|
||||
@@ -104,7 +104,7 @@ class behat_qbank_comment extends behat_question_base {
|
||||
|
||||
if ($this->running_javascript()) {
|
||||
$commentstextarea = $this->find('css',
|
||||
'.question-comment-view .comment-area textarea', $exception);
|
||||
'.comment-area textarea', $exception);
|
||||
$commentstextarea->setValue($comment);
|
||||
|
||||
// We delay 1 second which is all we need.
|
||||
@@ -115,6 +115,31 @@ class behat_qbank_comment extends behat_question_base {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the specified comment from the current question comment preview.
|
||||
*
|
||||
* @Then I delete :arg comment from question preview
|
||||
* @param string $comment
|
||||
*/
|
||||
public function i_delete_comment_from_question_preview($comment) {
|
||||
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $comment . '" comment ');
|
||||
|
||||
// Using xpath liternal to avoid possible problems with comments containing quotes.
|
||||
$commentliteral = behat_context_helper::escape($comment);
|
||||
|
||||
$commentxpath = "//*[contains(concat(' ', normalize-space(@class), ' '), ' comment-ctrl ')]" .
|
||||
"/descendant::div[@class='comment-message'][contains(., $commentliteral)]";
|
||||
|
||||
// Click on delete icon.
|
||||
$this->execute('behat_general::i_click_on_in_the',
|
||||
["Delete comment posted by", "icon", $this->escape($commentxpath), "xpath_element"]
|
||||
);
|
||||
|
||||
// Wait for the animation to finish, in theory is just 1 sec, adding 4 just in case.
|
||||
$this->getSession()->wait(4 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the specified comment from the current question comment modal.
|
||||
*
|
||||
|
||||
@@ -63,6 +63,7 @@ Feature: A Teacher can comment in a question
|
||||
And I navigate to "Question bank" in current page administration
|
||||
And I set the field "Select a category" to "Test questions"
|
||||
And I choose "Preview" action for "First question" in the question bank
|
||||
And I click on "Comments" "link"
|
||||
Then I should see "Save comment"
|
||||
And I add "Super test comment 01" comment to question preview
|
||||
And I click on "Save comment" "link"
|
||||
@@ -71,7 +72,8 @@ Feature: A Teacher can comment in a question
|
||||
And I click on "Close preview" "button"
|
||||
Then I should see "1" on the comments column
|
||||
And I choose "Preview" action for "First question" in the question bank
|
||||
And I delete "Super test comment 01" comment from question
|
||||
And I click on "Comments" "link"
|
||||
And I delete "Super test comment 01" comment from question preview
|
||||
And I should not see "Super test comment 01"
|
||||
And I click on "Close preview" "button"
|
||||
Then I should see "0" on the comments column
|
||||
@@ -100,6 +102,7 @@ Feature: A Teacher can comment in a question
|
||||
And I press "id_submitbutton"
|
||||
Then I should see "Essay 01 new"
|
||||
And I choose "Preview" action for "Essay 01 new" in the question bank
|
||||
And I click on "Comments" "link"
|
||||
Then I should see "Save comment"
|
||||
And I log out
|
||||
Then I log in as "teacher2"
|
||||
|
||||
@@ -287,7 +287,12 @@ class qbank_customfields_customfield_testcase extends advanced_testcase {
|
||||
$this->restore_course($backupid, $coursefullname, $courseshortname . '_2', $newcategory->id);
|
||||
|
||||
// The questions and their associated custom fields should have been restored.
|
||||
$newquestion1 = $DB->get_record('question', ['idnumber' => 'q1']);
|
||||
$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
|
||||
WHERE qbe.idnumber = ?';
|
||||
$newquestion1 = $DB->get_record_sql($sql, ['q1']);
|
||||
$newquestion1cfdata = $customfieldhandler->export_instance_data_object($newquestion1->id);
|
||||
$this->assertEquals('some text', $newquestion1cfdata->f1);
|
||||
$this->assertEquals('Yes', $newquestion1cfdata->f2);
|
||||
@@ -295,7 +300,7 @@ class qbank_customfields_customfield_testcase extends advanced_testcase {
|
||||
$this->assertEquals('b', $newquestion1cfdata->f4);
|
||||
$this->assertEquals('test', $newquestion1cfdata->f5);
|
||||
|
||||
$newquestion2 = $DB->get_record('question', ['idnumber' => 'q2']);
|
||||
$newquestion2 = $DB->get_record_sql($sql, ['q2']);
|
||||
$newquestion2cfdata = $customfieldhandler->export_instance_data_object($newquestion2->id);
|
||||
$this->assertEquals('some more text', $newquestion2cfdata->f1);
|
||||
$this->assertEquals('No', $newquestion2cfdata->f2);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
namespace qbank_deletequestion;
|
||||
|
||||
use core_question\local\bank\constants;
|
||||
use core_question\local\bank\menu_action_column_base;
|
||||
|
||||
/**
|
||||
@@ -82,7 +83,7 @@ class delete_action_column extends menu_action_column_base {
|
||||
if (!question_has_capability_on($question, 'edit')) {
|
||||
return [null, null, null];
|
||||
}
|
||||
if ($question->hidden) {
|
||||
if ($question->status === constants::QUESTION_STATUS_HIDDEN) {
|
||||
$hiddenparams = array(
|
||||
'unhide' => $question->id,
|
||||
'sesskey' => sesskey());
|
||||
@@ -102,7 +103,6 @@ class delete_action_column extends menu_action_column_base {
|
||||
|
||||
public function get_required_fields(): array {
|
||||
$required = parent::get_required_fields();
|
||||
$required[] = 'q.hidden';
|
||||
return $required;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ if ($cmid) {
|
||||
throw new moodle_exception('missingcourseorcmid', 'question');
|
||||
}
|
||||
|
||||
$contexts = new question_edit_contexts($thiscontext);
|
||||
$contexts = new core_question\lib\question_edit_contexts($thiscontext);
|
||||
$url = new moodle_url('/question/bank/deletequestion/delete.php');
|
||||
|
||||
$PAGE->set_url($url);
|
||||
@@ -62,7 +62,7 @@ $PAGE->set_heading($COURSE->fullname);
|
||||
// Unhide a question.
|
||||
if (($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) {
|
||||
question_require_capability_on($unhide, 'edit');
|
||||
$DB->set_field('question', 'hidden', 0, array('id' => $unhide));
|
||||
$DB->set_field('question_versions', 'status', 0, ['questionid' => $questionid]);
|
||||
|
||||
// Purge these questions from the cache.
|
||||
\question_bank::notify_question_edited($unhide);
|
||||
@@ -81,7 +81,7 @@ if ($deleteselected && ($confirm = optional_param('confirm', '', PARAM_ALPHANUM)
|
||||
$questionid = (int)$questionid;
|
||||
question_require_capability_on($questionid, 'edit');
|
||||
if (questions_in_use(array($questionid))) {
|
||||
$DB->set_field('question', 'hidden', 1, array('id' => $questionid));
|
||||
$DB->set_field('question_versions', 'status', 1, ['questionid' => $questionid]);
|
||||
} else {
|
||||
question_delete_question($questionid);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ Feature: Use the qbank plugin manager page for deletequestion
|
||||
Given I log in as "admin"
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Question bank > Questions" in current page administration
|
||||
And I click on "Select all" "checkbox"
|
||||
And I click on "First question" "checkbox"
|
||||
And I click on "First question second" "checkbox"
|
||||
And I click on "With selected" "button"
|
||||
And I click on question bulk action "deleteselected"
|
||||
And I click on "Delete" "button" in the "Confirm" "dialogue"
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
|
||||
namespace qbank_editquestion;
|
||||
|
||||
use core_question\local\bank\constants;
|
||||
|
||||
/**
|
||||
* Class editquestion_helper for methods related to add/edit/copy
|
||||
*
|
||||
@@ -83,4 +85,36 @@ class editquestion_helper {
|
||||
}
|
||||
return $PAGE->get_renderer('qbank_editquestion')->render_create_new_question_button($addquestiondisplay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string for the status of the question.
|
||||
*
|
||||
* @param int $status
|
||||
* @return string
|
||||
*/
|
||||
public static function get_question_status_string($status): string {
|
||||
if ((int)$status == constants::QUESTION_STATUS_READY) {
|
||||
$statusstring = get_string('questionstatusready', 'qbank_editquestion');
|
||||
} else if ((int)$status == constants::QUESTION_STATUS_HIDDEN) {
|
||||
$statusstring = get_string('questionstatushidden', 'qbank_editquestion');
|
||||
} else if ((int)$status == constants::QUESTION_STATUS_DRAFT) {
|
||||
$statusstring = get_string('questionstatusdraft', 'qbank_editquestion');
|
||||
} else {
|
||||
$statusstring = get_string('questionstatusunknown', 'qbank_editquestion');
|
||||
}
|
||||
return $statusstring;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of status of the questions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_question_status_list(): array {
|
||||
$statuslist = [];
|
||||
$statuslist[constants::QUESTION_STATUS_READY] = get_string('questionstatusready', 'qbank_editquestion');
|
||||
$statuslist[constants::QUESTION_STATUS_DRAFT] = get_string('questionstatusdraft', 'qbank_editquestion');
|
||||
return $statuslist;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,4 +57,14 @@ class renderer extends \plugin_renderer_base {
|
||||
return $this->render_from_template('qbank_editquestion/add_new_question', $addquestiondata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render question information for edit form.
|
||||
*
|
||||
* @param array $questiondata
|
||||
* @return bool|string
|
||||
*/
|
||||
public function render_question_info($questiondata) {
|
||||
return $this->render_from_template('qbank_editquestion/question_info', $questiondata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ class plugin_feature extends \core_question\local\bank\plugin_features_base{
|
||||
public function get_question_columns($qbank): array {
|
||||
return [
|
||||
new edit_action_column($qbank),
|
||||
new copy_action_column($qbank)
|
||||
new copy_action_column($qbank),
|
||||
new question_status_column($qbank)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* A column to show the status of the question.
|
||||
*
|
||||
* @package qbank_editquestion
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace qbank_editquestion;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_question\local\bank\column_base;
|
||||
|
||||
/**
|
||||
* Class question_status_column.
|
||||
*
|
||||
* @package qbank_editquestion
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_status_column extends column_base {
|
||||
|
||||
public function get_name(): string {
|
||||
return 'questionstatus';
|
||||
}
|
||||
|
||||
protected function get_title(): string {
|
||||
return get_string('questionstatus', 'qbank_editquestion');
|
||||
}
|
||||
|
||||
protected function display_content($question, $rowclasses): void {
|
||||
global $DB;
|
||||
$version = $DB->get_record('question_versions', ['questionid' => $question->id], 'status');
|
||||
echo \html_writer::tag('a', editquestion_helper::get_question_status_string($version->status));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,3 +25,14 @@
|
||||
|
||||
$string['pluginname'] = 'Edit questions';
|
||||
$string['privacy:metadata'] = 'The Edit questions question bank plugin does not store any user data.';
|
||||
|
||||
// Question status.
|
||||
$string['questionstatus'] = 'Status';
|
||||
$string['questionstatusready'] = 'Ready';
|
||||
$string['questionstatushidden'] = 'Hidden';
|
||||
$string['questionstatusdraft'] = 'Draft';
|
||||
$string['questionstatusunknown'] = 'Unknown';
|
||||
|
||||
// Edit form.
|
||||
$string['versioninfo'] = 'Version';
|
||||
$string['status'] = 'Question status';
|
||||
|
||||
@@ -108,7 +108,7 @@ if ($cmid) {
|
||||
} else {
|
||||
throw new moodle_exception('missingcourseorcmid', 'question');
|
||||
}
|
||||
$contexts = new question_edit_contexts($thiscontext);
|
||||
$contexts = new core_question\lib\question_edit_contexts($thiscontext);
|
||||
$PAGE->set_pagelayout('admin');
|
||||
|
||||
if (optional_param('addcancel', false, PARAM_BOOL)) {
|
||||
@@ -174,6 +174,7 @@ if ($id) {
|
||||
if (!$formeditable) {
|
||||
question_require_capability_on($question, 'view');
|
||||
}
|
||||
$question->beingcopied = false;
|
||||
if ($makecopy) {
|
||||
// If we are duplicating a question, add some indication to the question name.
|
||||
$question->name = get_string('questionnamecopy', 'question', $question->name);
|
||||
@@ -210,6 +211,13 @@ if ($formeditable && $id) {
|
||||
$toform->appendqnumstring = $appendqnumstring;
|
||||
$toform->returnurl = $originalreturnurl;
|
||||
$toform->makecopy = $makecopy;
|
||||
if (isset($question->id)) {
|
||||
$questionobject = question_bank::load_question($question->id);
|
||||
$toform->status = $questionobject->status;
|
||||
$toform->idnumber = $questionobject->idnumber;
|
||||
} else {
|
||||
$toform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
}
|
||||
if ($cm !== null) {
|
||||
$toform->cmid = $cm->id;
|
||||
$toform->courseid = $cm->course;
|
||||
@@ -236,7 +244,12 @@ if ($mform->is_cancelled()) {
|
||||
// If we are saving as a copy, break the connection to the old question.
|
||||
if ($makecopy) {
|
||||
$question->id = 0;
|
||||
$question->hidden = 0; // Copies should not be hidden.
|
||||
$question->status = \core_question\local\bank\constants::QUESTION_STATUS_READY; // Copies should not be hidden.
|
||||
}
|
||||
|
||||
// If is will be added directly to a module send the module name to be referenced.
|
||||
if ($appendqnumstring && $cm) {
|
||||
$fromform->modulename = 'mod_' . $cm->modname;
|
||||
}
|
||||
|
||||
// Process the combination of usecurrentcat, categorymoveto and category form
|
||||
@@ -248,7 +261,7 @@ if ($mform->is_cancelled()) {
|
||||
// If we are moving a question, check we have permission to move it from
|
||||
// whence it came (Where we are moving to is validated by the form).
|
||||
list($newcatid, $newcontextid) = explode(',', $fromform->category);
|
||||
if (!empty($question->id) && $newcatid != $question->category) {
|
||||
if (!empty($question->id) && $newcatid != $question->categoryobject->id) {
|
||||
$contextid = $newcontextid;
|
||||
question_require_capability_on($question, 'move');
|
||||
} else {
|
||||
|
||||
+13
-14
@@ -1,33 +1,32 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template qbank_viewcreator/modifier_display
|
||||
@template qbank_editquestion/question_info
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"displaydata": [
|
||||
{
|
||||
"modifier": "Marc Ghaly",
|
||||
"date": "2 June 2021, 5:32 PM",
|
||||
}
|
||||
"elements": [
|
||||
{"pluginhtml":"<div>Version: 1</div>"},
|
||||
{"pluginhtml":"<div>Usage: 1</div>"},
|
||||
]
|
||||
}
|
||||
}}
|
||||
|
||||
<span class="qbank-creator-name">
|
||||
{{modifier}}
|
||||
</span>
|
||||
<br>
|
||||
<span class="date">
|
||||
{{date}}
|
||||
</span>
|
||||
<div class="question-edit-elements">
|
||||
{{#editelements}}
|
||||
{{{pluginhtml}}}
|
||||
{{/editelements}}
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@ if ($cmid) {
|
||||
require_sesskey();
|
||||
|
||||
// Load the necessary data.
|
||||
$contexts = new question_edit_contexts($thiscontext);
|
||||
$contexts = new core_question\lib\question_edit_contexts($thiscontext);
|
||||
$questiondata = question_bank::load_question_data($questionid);
|
||||
|
||||
// Check permissions.
|
||||
|
||||
@@ -102,7 +102,8 @@ if ($param->delete) {
|
||||
}
|
||||
|
||||
helper::question_remove_stale_questions_from_category($param->delete);
|
||||
$questionstomove = $DB->count_records("question", ["category" => $param->delete]);
|
||||
|
||||
$questionstomove = $DB->count_records('question_bank_entries', ['questioncategoryid' => $param->delete]);
|
||||
|
||||
// Second pass, if we still have questions to move, setup the form.
|
||||
if ($questionstomove) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
namespace qbank_managecategories;
|
||||
|
||||
use context;
|
||||
use core_question\local\bank\constants;
|
||||
use moodle_exception;
|
||||
use html_writer;
|
||||
|
||||
@@ -60,14 +61,19 @@ class helper {
|
||||
public static function question_remove_stale_questions_from_category(int $categoryid): void {
|
||||
global $DB;
|
||||
|
||||
$select = 'category = :categoryid AND (qtype = :qtype OR hidden = :hidden)';
|
||||
$params = ['categoryid' => $categoryid, 'qtype' => 'random', 'hidden' => 1];
|
||||
$questions = $DB->get_recordset_select("question", $select, $params, '', 'id');
|
||||
$sql = 'SELECT q.id
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid = :categoryid
|
||||
AND (q.qtype = :qtype OR qv.status = :status)';
|
||||
|
||||
$params = ['categoryid' => $categoryid, 'qtype' => 'random', 'status' => 1];
|
||||
$questions = $DB->get_records_sql($sql, $params);
|
||||
foreach ($questions as $question) {
|
||||
// The function question_delete_question does not delete questions in use.
|
||||
question_delete_question($question->id);
|
||||
}
|
||||
$questions->close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,22 +246,43 @@ class helper {
|
||||
* Get all the category objects, including a count of the number of questions in that category,
|
||||
* for all the categories in the lists $contexts.
|
||||
*
|
||||
* @param mixed $contexts either a single contextid, or a comma-separated list of context ids.
|
||||
* @param $contexts
|
||||
* @param string $sortorder used as the ORDER BY clause in the select statement.
|
||||
* @param bool $top Whether to return the top categories or not.
|
||||
* @param int $showallversions 1 to show all versions not only the latest.
|
||||
* @param int|null $statustocount The version status to count. If null will get all except hidden.
|
||||
* @return array of category objects.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function get_categories_for_contexts($contexts, string $sortorder = 'parent, sortorder, name ASC',
|
||||
bool $top = false): array {
|
||||
bool $top = false, int $showallversions = 0,
|
||||
?int $statustocount = null): array {
|
||||
global $DB;
|
||||
$topwhere = $top ? '' : 'AND c.parent <> 0';
|
||||
return $DB->get_records_sql("
|
||||
SELECT c.*, (SELECT count(1) FROM {question} q
|
||||
WHERE c.id = q.category AND q.hidden='0' AND q.parent='0') AS questioncount
|
||||
FROM {question_categories} c
|
||||
WHERE c.contextid IN ($contexts) $topwhere
|
||||
ORDER BY $sortorder");
|
||||
$statuscondition = "AND (qv.status = '". question_version_status::QUESTION_STATUS_READY . "' " .
|
||||
" OR qv.status = '" . question_version_status::QUESTION_STATUS_DRAFT . "' )";
|
||||
|
||||
$sql = "SELECT c.*,
|
||||
(SELECT COUNT(1)
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE q.parent = '0'
|
||||
$statuscondition
|
||||
AND c.id = qbe.questioncategoryid
|
||||
AND ($showallversions = 1
|
||||
OR (qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qbe.id)
|
||||
)
|
||||
)
|
||||
) AS questioncount
|
||||
FROM {question_categories} c
|
||||
WHERE c.contextid IN ($contexts) $topwhere
|
||||
ORDER BY $sortorder";
|
||||
|
||||
return $DB->get_records_sql($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -328,8 +328,15 @@ class question_category_object {
|
||||
*/
|
||||
public function move_questions(int $oldcat, int $newcat): void {
|
||||
global $DB;
|
||||
$questionids = $DB->get_records_select_menu('question',
|
||||
'category = ? AND (parent = 0 OR parent = id)', [$oldcat], '', 'id,1');
|
||||
|
||||
$sql = "SELECT q.id, 1
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid = ?
|
||||
AND (q.parent = 0 OR q.parent = q.id)";
|
||||
|
||||
$questionids = $DB->get_records_sql_menu($sql, [$oldcat]);
|
||||
question_move_questions_to_category(array_keys($questionids), $newcat);
|
||||
}
|
||||
|
||||
@@ -479,14 +486,25 @@ class question_category_object {
|
||||
|
||||
// If the category name has changed, rename any random questions in that category.
|
||||
if ($oldcat->name != $cat->name) {
|
||||
$where = "qtype = 'random' AND category = ? AND " . $DB->sql_compare_text('questiontext') . " = ?";
|
||||
// Get the question ids for each question category.
|
||||
$sql = "SELECT q.id
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid = ?";
|
||||
|
||||
$randomqtype = question_bank::get_qtype('random');
|
||||
$randomqname = $randomqtype->question_name($cat, false);
|
||||
$DB->set_field_select('question', 'name', $randomqname, $where, [$cat->id, '0']);
|
||||
$questionids = $DB->get_records_sql($sql, [$cat->id]);
|
||||
|
||||
$randomqname = $randomqtype->question_name($cat, true);
|
||||
$DB->set_field_select('question', 'name', $randomqname, $where, [$cat->id, '1']);
|
||||
foreach ($questionids as $question) {
|
||||
$where = "qtype = 'random' AND id = ? AND " . $DB->sql_compare_text('questiontext') . " = ?";
|
||||
|
||||
$randomqtype = question_bank::get_qtype('random');
|
||||
$randomqname = $randomqtype->question_name($cat, false);
|
||||
$DB->set_field_select('question', 'name', $randomqname, $where, [$question->id, '0']);
|
||||
|
||||
$randomqname = $randomqtype->question_name($cat, true);
|
||||
$DB->set_field_select('question', 'name', $randomqname, $where, [$question->id, '1']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oldcat->contextid != $tocontextid) {
|
||||
|
||||
@@ -72,21 +72,21 @@ class helper_test extends \advanced_testcase {
|
||||
|
||||
$qcat1 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$q1a = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcat1->id]); // Will be hidden.
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q1a->id]);
|
||||
$DB->set_field('question_versions', 'status', 1, ['questionid' => $q1a->id]);
|
||||
|
||||
$qcat2 = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$q2a = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden.
|
||||
$q2b = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden but used.
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q2a->id]);
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q2b->id]);
|
||||
$DB->set_field('question_versions', 'status', 1, ['questionid' => $q2a->id]);
|
||||
$DB->set_field('question_versions', 'status', 1, ['questionid' => $q2b->id]);
|
||||
quiz_add_quiz_question($q2b->id, $this->quiz);
|
||||
quiz_add_random_questions($this->quiz, 0, $qcat2->id, 1, false);
|
||||
|
||||
// We added one random question to the quiz and we expect the quiz to have only one random question.
|
||||
$q2d = $DB->get_record_sql("SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {quiz_slots} s ON s.questionid = q.id
|
||||
WHERE q.qtype = :qtype
|
||||
FROM {question} q
|
||||
JOIN {quiz_slots} s ON s.questionid = q.id
|
||||
WHERE q.qtype = :qtype
|
||||
AND s.quizid = :quizid",
|
||||
['qtype' => 'random', 'quizid' => $this->quiz->id], MUST_EXIST);
|
||||
|
||||
@@ -95,25 +95,45 @@ class helper_test extends \advanced_testcase {
|
||||
$q1b = $this->qgenerator->create_question('random', null, ['category' => $qcat1->id]); // Will not be used.
|
||||
$q2c = $this->qgenerator->create_question('random', null, ['category' => $qcat2->id]); // Will not be used.
|
||||
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
$sql = "SELECT count(q.id)
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid = ?";
|
||||
$this->assertEquals(2, $DB->count_records_sql($sql, [$qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records_sql($sql, [$qcat2->id]));
|
||||
|
||||
// Non-existing category, nothing will happen.
|
||||
helper::question_remove_stale_questions_from_category(0);
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
$sql = "SELECT count(q.id)
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid = ?";
|
||||
$this->assertEquals(2, $DB->count_records_sql($sql, [$qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records_sql($sql, [$qcat2->id]));
|
||||
|
||||
// First category, should be empty afterwards.
|
||||
helper::question_remove_stale_questions_from_category($qcat1->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
$sql = "SELECT count(q.id)
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid = ?";
|
||||
$this->assertEquals(0, $DB->count_records_sql($sql, [$qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records_sql($sql, [$qcat2->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q1a->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q1b->id]));
|
||||
|
||||
// Second category, used questions should be left untouched.
|
||||
helper::question_remove_stale_questions_from_category($qcat2->id);
|
||||
$this->assertEquals(0, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
$sql = "SELECT count(q.id)
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid = ?";
|
||||
$this->assertEquals(0, $DB->count_records_sql($sql, [$qcat1->id]));
|
||||
$this->assertEquals(2, $DB->count_records_sql($sql, [$qcat2->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q2a->id]));
|
||||
$this->assertTrue($DB->record_exists('question', ['id' => $q2b->id]));
|
||||
$this->assertFalse($DB->record_exists('question', ['id' => $q2c->id]));
|
||||
|
||||
@@ -25,7 +25,7 @@ use context;
|
||||
use context_course;
|
||||
use context_module;
|
||||
use moodle_url;
|
||||
use question_edit_contexts;
|
||||
use core_question\lib\question_edit_contexts;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,15 +79,17 @@ class submit_tags extends external_api {
|
||||
|
||||
if (!$question = $DB->get_record_sql('
|
||||
SELECT q.*, qc.contextid
|
||||
FROM {question} q
|
||||
JOIN {question_categories} qc ON qc.id = q.category
|
||||
WHERE q.id = ?', [$params['questionid']])) {
|
||||
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 q.id = ?', [$questionid])) {
|
||||
throw new \moodle_exception('questiondoesnotexist', 'question');
|
||||
}
|
||||
|
||||
$cantag = question_has_capability_on($question, 'tag');
|
||||
$questioncontext = \context::instance_by_id($question->contextid);
|
||||
$contexts = new \question_edit_contexts($editingcontext);
|
||||
$contexts = new \core_question\lib\question_edit_contexts($editingcontext);
|
||||
|
||||
$formoptions = [
|
||||
'editingcontext' => $editingcontext,
|
||||
|
||||
@@ -53,9 +53,15 @@ function qbank_tagquestion_output_fragment_tags_form($args) {
|
||||
$filtercourses = null;
|
||||
}
|
||||
|
||||
$category = $DB->get_record('question_categories', ['id' => $question->category]);
|
||||
$sql = "SELECT qc.*
|
||||
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 q.id = :id";
|
||||
$category = $DB->get_record_sql($sql, ['id' => $question->id]);
|
||||
$questioncontext = \context::instance_by_id($category->contextid);
|
||||
$contexts = new \question_edit_contexts($editingcontext);
|
||||
$contexts = new \core_question\lib\question_edit_contexts($editingcontext);
|
||||
|
||||
// Load the question tags and filter the course tags by the current course.
|
||||
if (core_tag_tag::is_enabled('core_question', 'question')) {
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
<?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 qbank_viewcreator;
|
||||
|
||||
use core_question\local\bank\column_base;
|
||||
|
||||
/**
|
||||
* A column type for the name of the question last modifier.
|
||||
*
|
||||
* @package qbank_viewcreator
|
||||
* @copyright 2009 Tim Hunt
|
||||
* @author 2021 Ghaly Marc-Alexandre <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class modifier_name_column extends column_base {
|
||||
|
||||
public function get_name(): string {
|
||||
return 'modifiername';
|
||||
}
|
||||
|
||||
protected function get_title(): string {
|
||||
return get_string('lastmodifiedby', 'question');
|
||||
}
|
||||
|
||||
protected function display_content($question, $rowclasses): void {
|
||||
global $PAGE;
|
||||
$displaydata = [];
|
||||
|
||||
if (!empty($question->modifierfirstname) && !empty($question->modifierlastname)) {
|
||||
$u = new \stdClass();
|
||||
$u = username_load_fields_from_object($u, $question, 'modifier');
|
||||
$displaydata['date'] = userdate($question->timemodified, get_string('strftimedatetime', 'langconfig'));
|
||||
$displaydata['modifier'] = fullname($u);
|
||||
echo $PAGE->get_renderer('qbank_viewcreator')->render_modifier_name($displaydata);
|
||||
}
|
||||
}
|
||||
|
||||
public function get_extra_joins(): array {
|
||||
return ['um' => 'LEFT JOIN {user} um ON um.id = q.modifiedby'];
|
||||
}
|
||||
|
||||
public function get_required_fields(): array {
|
||||
$allnames = \core_user\fields::get_name_fields();
|
||||
$requiredfields = [];
|
||||
foreach ($allnames as $allname) {
|
||||
$requiredfields[] = 'um.' . $allname . ' AS modifier' . $allname;
|
||||
}
|
||||
$requiredfields[] = 'q.timemodified';
|
||||
return $requiredfields;
|
||||
}
|
||||
|
||||
public function is_sortable(): array {
|
||||
return [
|
||||
'firstname' => ['field' => 'um.firstname', 'title' => get_string('firstname')],
|
||||
'lastname' => ['field' => 'um.lastname', 'title' => get_string('lastname')],
|
||||
'timemodified' => ['field' => 'q.timemodified', 'title' => get_string('date')]
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,13 +36,13 @@ class renderer extends \plugin_renderer_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render question modifier.
|
||||
* Render question edit form callback.
|
||||
*
|
||||
* @param array $displaydata
|
||||
* @return string
|
||||
*/
|
||||
public function render_modifier_name($displaydata) {
|
||||
return $this->render_from_template('qbank_viewcreator/modifier_display', $displaydata);
|
||||
public function render_version_info($displaydata) {
|
||||
return $this->render_from_template('qbank_viewcreator/version_info', $displaydata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@ class plugin_feature extends plugin_features_base {
|
||||
|
||||
public function get_question_columns($qbank): array {
|
||||
return [
|
||||
new creator_name_column($qbank),
|
||||
new modifier_name_column($qbank)
|
||||
new creator_name_column($qbank)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,5 @@
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'View creator';
|
||||
$string['privacy:metadata'] = 'The View creator question bank plugin does not store any personal data.';
|
||||
$string['privacy:metadata'] = 'View creator question bank plugin does not store any user data.';
|
||||
$string['version'] = 'Version {$a}';
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Helper functions and callbacks.
|
||||
*
|
||||
* @package qbank_viewcreator
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edit page callback for information.
|
||||
*
|
||||
* @param object $question
|
||||
* @return string
|
||||
*/
|
||||
function qbank_viewcreator_edit_form_display($question): string {
|
||||
global $DB, $PAGE;
|
||||
$versiondata = [];
|
||||
$questionversion = $DB->get_record('question_versions', ['questionid' => $question->id])->version;
|
||||
$versiondata['versionnumber'] = $questionversion;
|
||||
if (!empty($question->createdby)) {
|
||||
$a = new stdClass();
|
||||
$a->time = userdate($question->timecreated);
|
||||
$a->user = fullname($DB->get_record('user', ['id' => $question->createdby]));
|
||||
$versiondata['createdby'] = get_string('created', 'question') . ' ' .
|
||||
get_string('byandon', 'question', $a) ;
|
||||
}
|
||||
return $PAGE->get_renderer('qbank_viewcreator')->render_version_info($versiondata);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template qbank_editquestion/version_info
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"addquestiondata": [
|
||||
{
|
||||
"versionnumber": 1,
|
||||
"createdby": "Admin User on Wednesday, 20 October 2021, 5:33 AM"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
<div class="question-version-number">
|
||||
<a><u>{{#str}} version, qbank_viewcreator, {{versionnumber}} {{/str}}</u></a>
|
||||
</div>
|
||||
{{#createdby}}
|
||||
<div class="question-creator-info">
|
||||
<a>{{{createdby}}}</a>
|
||||
</div>
|
||||
{{/createdby}}
|
||||
@@ -24,10 +24,8 @@ Feature: Use the qbank plugin manager page for viewcreator plugin
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Question bank" in current page administration
|
||||
Then I should not see "Created by"
|
||||
And I should not see "Last modified by"
|
||||
And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration
|
||||
And I click on "Enable" "link" in the "View creator" "table_row"
|
||||
And I am on the "Test quiz" "quiz activity" page
|
||||
And I navigate to "Question bank" in current page administration
|
||||
Then I should see "Created by"
|
||||
And I should see "Last modified by"
|
||||
|
||||
@@ -64,14 +64,14 @@ class question_name_idnumber_tags_column extends viewquestionname_column_helper
|
||||
|
||||
public function get_required_fields(): array {
|
||||
$fields = parent::get_required_fields();
|
||||
$fields[] = 'q.idnumber';
|
||||
$fields[] = 'qbe.idnumber';
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function is_sortable(): array {
|
||||
return [
|
||||
'name' => ['field' => 'q.name', 'title' => get_string('questionname', 'question')],
|
||||
'idnumber' => ['field' => 'q.idnumber', 'title' => get_string('idnumber', 'question')],
|
||||
'idnumber' => ['field' => 'qbe.idnumber', 'title' => get_string('idnumber', 'question')],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -60,12 +60,8 @@ class question_text_row extends row_base {
|
||||
echo $text;
|
||||
}
|
||||
|
||||
public function get_extra_joins(): array {
|
||||
return ['qc' => 'JOIN {question_categories} qc ON qc.id = q.category'];
|
||||
}
|
||||
|
||||
public function get_required_fields(): array {
|
||||
return ['q.id', 'q.questiontext', 'q.questiontextformat', 'qc.contextid'];
|
||||
return ['q.questiontext', 'q.questiontextformat'];
|
||||
}
|
||||
|
||||
public function has_preference(): bool {
|
||||
|
||||
@@ -92,7 +92,7 @@ class category_condition extends condition {
|
||||
$categoryids = [$this->category->id];
|
||||
}
|
||||
list($catidtest, $this->params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'cat');
|
||||
$this->where = 'q.category ' . $catidtest;
|
||||
$this->where = 'qbe.questioncategoryid ' . $catidtest;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace core_question\bank\search;
|
||||
|
||||
use core_question\local\bank\constants;
|
||||
|
||||
/**
|
||||
* This class controls whether hidden / deleted questions are hidden in the list.
|
||||
*
|
||||
@@ -46,7 +48,8 @@ class hidden_condition extends condition {
|
||||
public function __construct($hide = true) {
|
||||
$this->hide = $hide;
|
||||
if ($hide) {
|
||||
$this->where = 'q.hidden = 0';
|
||||
$this->where = 'qv.status = ' . constants::QUESTION_STATUS_READY .
|
||||
' OR qv.status = ' . constants::QUESTION_STATUS_DRAFT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ class core_question_external extends external_api {
|
||||
|
||||
$cantag = question_has_capability_on($question, 'tag');
|
||||
$questioncontext = \context::instance_by_id($question->contextid);
|
||||
$contexts = new \question_edit_contexts($editingcontext);
|
||||
$contexts = new \core_question\lib\question_edit_contexts($editingcontext);
|
||||
|
||||
$formoptions = [
|
||||
'editingcontext' => $editingcontext,
|
||||
@@ -300,7 +300,7 @@ class core_question_external extends external_api {
|
||||
|
||||
$categorycontextid = $DB->get_field('question_categories', 'contextid', ['id' => $categoryid], MUST_EXIST);
|
||||
$categorycontext = \context::instance_by_id($categorycontextid);
|
||||
$editcontexts = new \question_edit_contexts($categorycontext);
|
||||
$editcontexts = new \core_question\lib\question_edit_contexts($categorycontext);
|
||||
// The user must be able to view all questions in the category that they are requesting.
|
||||
$editcontexts->require_cap('moodle/question:viewall');
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Converts contextlevels to strings and back to help with reading/writing contexts to/from import/export files.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @author 2021 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_question\lib;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class context_to_string_translator.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
||||
* @author 2021 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class context_to_string_translator {
|
||||
|
||||
/**
|
||||
* @var array used to translate between contextids and strings for this context.
|
||||
*/
|
||||
protected $contexttostringarray = [];
|
||||
|
||||
/**
|
||||
* context_to_string_translator constructor.
|
||||
*
|
||||
* @param \context[] $contexts
|
||||
*/
|
||||
public function __construct($contexts) {
|
||||
$this->generate_context_to_string_array($contexts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Context to string.
|
||||
*
|
||||
* @param int $contextid
|
||||
* @return mixed
|
||||
*/
|
||||
public function context_to_string($contextid) {
|
||||
return $this->contexttostringarray[$contextid];
|
||||
}
|
||||
|
||||
/**
|
||||
* String to context.
|
||||
*
|
||||
* @param string $contextname
|
||||
* @return false|int|string
|
||||
*/
|
||||
public function string_to_context($contextname) {
|
||||
$contextid = array_search($contextname, $this->contexttostringarray);
|
||||
return $contextid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate context to array.
|
||||
*
|
||||
* @param \context[] $contexts
|
||||
*/
|
||||
protected function generate_context_to_string_array($contexts) {
|
||||
if (!$this->contexttostringarray) {
|
||||
$catno = 1;
|
||||
foreach ($contexts as $context) {
|
||||
switch ($context->contextlevel) {
|
||||
case CONTEXT_MODULE :
|
||||
$contextstring = 'module';
|
||||
break;
|
||||
case CONTEXT_COURSE :
|
||||
$contextstring = 'course';
|
||||
break;
|
||||
case CONTEXT_COURSECAT :
|
||||
$contextstring = "cat$catno";
|
||||
$catno++;
|
||||
break;
|
||||
case CONTEXT_SYSTEM :
|
||||
$contextstring = 'system';
|
||||
break;
|
||||
}
|
||||
$this->contexttostringarray[$context->id] = $contextstring;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Tracks all the contexts related to the one we are currently editing questions and provides helper methods to check permissions.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 2007 Jamie Pratt [email protected]
|
||||
* @author 2021 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_question\lib;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class question_edit_contexts.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 2007 Jamie Pratt [email protected]
|
||||
* @author 2021 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_edit_contexts {
|
||||
|
||||
/**
|
||||
* @var \string[][] array of the capabilities.
|
||||
*/
|
||||
public static $caps = [
|
||||
'editq' => [
|
||||
'moodle/question:add',
|
||||
'moodle/question:editmine',
|
||||
'moodle/question:editall',
|
||||
'moodle/question:viewmine',
|
||||
'moodle/question:viewall',
|
||||
'moodle/question:usemine',
|
||||
'moodle/question:useall',
|
||||
'moodle/question:movemine',
|
||||
'moodle/question:moveall'],
|
||||
'questions' => [
|
||||
'moodle/question:add',
|
||||
'moodle/question:editmine',
|
||||
'moodle/question:editall',
|
||||
'moodle/question:viewmine',
|
||||
'moodle/question:viewall',
|
||||
'moodle/question:movemine',
|
||||
'moodle/question:moveall'],
|
||||
'categories' => [
|
||||
'moodle/question:managecategory'],
|
||||
'import' => [
|
||||
'moodle/question:add'],
|
||||
'export' => [
|
||||
'moodle/question:viewall',
|
||||
'moodle/question:viewmine']];
|
||||
|
||||
/**
|
||||
* @var array of contexts.
|
||||
*/
|
||||
protected $allcontexts;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param \context $thiscontext the current context.
|
||||
*/
|
||||
public function __construct(\context $thiscontext) {
|
||||
$this->allcontexts = array_values($thiscontext->get_parent_contexts(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the contexts.
|
||||
*
|
||||
* @return \context[] all parent contexts
|
||||
*/
|
||||
public function all() {
|
||||
return $this->allcontexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lowest context.
|
||||
*
|
||||
* @return \context lowest context which must be either the module or course context
|
||||
*/
|
||||
public function lowest() {
|
||||
return $this->allcontexts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contexts having cap.
|
||||
*
|
||||
* @param string $cap capability
|
||||
* @return \context[] parent contexts having capability, zero based index
|
||||
*/
|
||||
public function having_cap($cap) {
|
||||
$contextswithcap = [];
|
||||
foreach ($this->allcontexts as $context) {
|
||||
if (has_capability($cap, $context)) {
|
||||
$contextswithcap[] = $context;
|
||||
}
|
||||
}
|
||||
return $contextswithcap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contexts having at least one cap.
|
||||
*
|
||||
* @param array $caps capabilities
|
||||
* @return \context[] parent contexts having at least one of $caps, zero based index
|
||||
*/
|
||||
public function having_one_cap($caps) {
|
||||
$contextswithacap = [];
|
||||
foreach ($this->allcontexts as $context) {
|
||||
foreach ($caps as $cap) {
|
||||
if (has_capability($cap, $context)) {
|
||||
$contextswithacap[] = $context;
|
||||
break; // Done with caps loop.
|
||||
}
|
||||
}
|
||||
}
|
||||
return $contextswithacap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context having at least one cap.
|
||||
*
|
||||
* @param string $tabname edit tab name
|
||||
* @return \context[] parent contexts having at least one of $caps, zero based index
|
||||
*/
|
||||
public function having_one_edit_tab_cap($tabname) {
|
||||
return $this->having_one_cap(self::$caps[$tabname]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contexts for adding question and also using it.
|
||||
*
|
||||
* @return \context[] those contexts where a user can add a question and then use it.
|
||||
*/
|
||||
public function having_add_and_use() {
|
||||
$contextswithcap = [];
|
||||
foreach ($this->allcontexts as $context) {
|
||||
if (!has_capability('moodle/question:add', $context)) {
|
||||
continue;
|
||||
}
|
||||
if (!has_any_capability(['moodle/question:useall', 'moodle/question:usemine'], $context)) {
|
||||
continue;
|
||||
}
|
||||
$contextswithcap[] = $context;
|
||||
}
|
||||
return $contextswithcap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has at least one parent context got the cap $cap?
|
||||
*
|
||||
* @param string $cap capability
|
||||
* @return boolean
|
||||
*/
|
||||
public function have_cap($cap) {
|
||||
return (count($this->having_cap($cap)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Has at least one parent context got one of the caps $caps?
|
||||
*
|
||||
* @param array $caps capability
|
||||
* @return boolean
|
||||
*/
|
||||
public function have_one_cap($caps) {
|
||||
foreach ($caps as $cap) {
|
||||
if ($this->have_cap($cap)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has at least one parent context got one of the caps for actions on $tabname
|
||||
*
|
||||
* @param string $tabname edit tab name
|
||||
* @return boolean
|
||||
*/
|
||||
public function have_one_edit_tab_cap($tabname) {
|
||||
return $this->have_one_cap(self::$caps[$tabname]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw error if at least one parent context hasn't got the cap $cap
|
||||
*
|
||||
* @param string $cap capability
|
||||
*/
|
||||
public function require_cap($cap) {
|
||||
if (!$this->have_cap($cap)) {
|
||||
throw new \moodle_exception('nopermissions', '', '', $cap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw error if at least one parent context hasn't got one of the caps $caps
|
||||
*
|
||||
* @param array $caps capabilities
|
||||
*/
|
||||
public function require_one_cap($caps) {
|
||||
if (!$this->have_one_cap($caps)) {
|
||||
$capsstring = join(', ', $caps);
|
||||
throw new \moodle_exception('nopermissions', '', '', $capsstring);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw error if at least one parent context hasn't got one of the caps $caps
|
||||
*
|
||||
* @param string $tabname edit tab name
|
||||
*/
|
||||
public function require_one_edit_tab_cap($tabname) {
|
||||
if (!$this->have_one_edit_tab_cap($tabname)) {
|
||||
throw new \moodle_exception('nopermissions', '', '', 'access question edit tab '.$tabname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,9 @@ abstract class action_column_base extends column_base {
|
||||
}
|
||||
|
||||
public function get_extra_joins(): array {
|
||||
return ['qc' => 'JOIN {question_categories} qc ON qc.id = q.category'];
|
||||
return ['qv' => 'JOIN {question_versions} qv ON qv.questionid = q.id',
|
||||
'qbe' => 'JOIN {question_bank_entry} qbe on qbe.id = qv.questionbankentryid',
|
||||
'qc' => 'JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid'];
|
||||
}
|
||||
|
||||
public function get_required_fields(): array {
|
||||
|
||||
@@ -290,7 +290,29 @@ class random_question_loader {
|
||||
$fieldsstring = implode(',', $fields);
|
||||
}
|
||||
|
||||
return $DB->get_records_list('question', 'id', $questionids, 'id', $fieldsstring, $offset, $limit);
|
||||
// Create the query to get the questions (validate that at least we have a question id. If not, do not execute the sql.)
|
||||
$hasquestions = false;
|
||||
$where = 'IN (';
|
||||
foreach ($questionids as $questionid) {
|
||||
$where = $where . $questionid . ',';
|
||||
// Extra validation, we do not want an infinite loop.
|
||||
$hasquestions = (bool)$questionid;
|
||||
}
|
||||
$where = rtrim($where,',');
|
||||
$where = $where . ')';
|
||||
|
||||
if ($hasquestions) {
|
||||
$sql = "SELECT {$fieldsstring}
|
||||
FROM (SELECT q.*, qbe.questioncategoryid as category
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE q.id {$where}) q";
|
||||
|
||||
return $DB->get_records_sql($sql, [], $offset, $limit);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,7 +70,7 @@ class view {
|
||||
protected $editquestionurl;
|
||||
|
||||
/**
|
||||
* @var \question_edit_contexts
|
||||
* @var \core_question\lib\question_edit_contexts
|
||||
*/
|
||||
protected $contexts;
|
||||
|
||||
@@ -157,7 +157,7 @@ class view {
|
||||
/**
|
||||
* Constructor for view.
|
||||
*
|
||||
* @param \question_edit_contexts $contexts
|
||||
* @param \core_question\lib\question_edit_contexts $contexts
|
||||
* @param \moodle_url $pageurl
|
||||
* @param object $course course settings
|
||||
* @param object $cm (optional) activity settings.
|
||||
@@ -244,8 +244,9 @@ class view {
|
||||
'preview_action_column',
|
||||
'delete_action_column',
|
||||
'export_xml_action_column',
|
||||
'question_status_column',
|
||||
'creator_name_column',
|
||||
'modifier_name_column'
|
||||
'comment_count_column'
|
||||
];
|
||||
if (question_get_display_preference('qbshowtext', 0, PARAM_BOOL, new \moodle_url(''))) {
|
||||
$corequestionbankcolumns[] = 'question_text_row';
|
||||
@@ -560,7 +561,7 @@ class view {
|
||||
protected function build_query(): void {
|
||||
// Get the required tables and fields.
|
||||
$joins = [];
|
||||
$fields = ['q.hidden', 'q.category'];
|
||||
$fields = ['qv.status', 'qc.id', 'qv.version', 'qv.id as versionid', 'qbe.id as questionbankentryid'];
|
||||
if (!empty($this->requiredcolumns)) {
|
||||
foreach ($this->requiredcolumns as $column) {
|
||||
$extrajoins = $column->get_extra_joins();
|
||||
@@ -583,7 +584,12 @@ class view {
|
||||
}
|
||||
|
||||
// Build the where clause.
|
||||
$tests = ['q.parent = 0'];
|
||||
$latestversion = 'qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entry} be
|
||||
ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qbe.id)';
|
||||
$tests = ['q.parent = 0', $latestversion];
|
||||
$this->sqlparams = [];
|
||||
foreach ($this->searchconditions as $searchcondition) {
|
||||
if ($searchcondition->where()) {
|
||||
@@ -1153,7 +1159,7 @@ class view {
|
||||
*/
|
||||
protected function get_row_classes($question, $rowcount): array {
|
||||
$classes = [];
|
||||
if ($question->hidden) {
|
||||
if ((int)$question->status === 1) {
|
||||
$classes[] = 'dimmed_text';
|
||||
}
|
||||
if ($question->id == $this->lastchangedid) {
|
||||
|
||||
+35
-16
@@ -51,35 +51,54 @@ function get_module_from_cmid($cmid) {
|
||||
return array($modrec, $cmrec);
|
||||
}
|
||||
/**
|
||||
* Function to read all questions for category into big array
|
||||
*
|
||||
* @param int $category category number
|
||||
* @param bool $noparent if true only questions with NO parent will be selected
|
||||
* @param bool $recurse include subdirectories
|
||||
* @param bool $export set true if this is called by questionbank export
|
||||
* Function to read all questions for category into big array
|
||||
*
|
||||
* @param object $category category number
|
||||
* @param bool $noparent if true only questions with NO parent will be selected
|
||||
* @param bool $recurse include subdirectories
|
||||
* @param bool $export set true if this is called by questionbank export
|
||||
* @return array
|
||||
*/
|
||||
function get_questions_category( $category, $noparent=false, $recurse=true, $export=true ) {
|
||||
function get_questions_category(object $category, bool $noparent, bool $recurse = true, bool $export = true,
|
||||
bool $latestversion = false): array {
|
||||
global $DB;
|
||||
|
||||
// Build sql bit for $noparent
|
||||
// Build sql bit for $noparent.
|
||||
$npsql = '';
|
||||
if ($noparent) {
|
||||
$npsql = " and parent='0' ";
|
||||
$npsql = " and q.parent='0' ";
|
||||
}
|
||||
|
||||
// Get list of categories
|
||||
// Get list of categories.
|
||||
if ($recurse) {
|
||||
$categorylist = question_categorylist($category->id);
|
||||
} else {
|
||||
$categorylist = array($category->id);
|
||||
$categorylist = [$category->id];
|
||||
}
|
||||
|
||||
// Get the list of questions for the category
|
||||
// Get the list of questions for the category.
|
||||
list($usql, $params) = $DB->get_in_or_equal($categorylist);
|
||||
$questions = $DB->get_records_select('question', "category {$usql} {$npsql}", $params, 'category, qtype, name');
|
||||
|
||||
// Iterate through questions, getting stuff we need
|
||||
$qresults = array();
|
||||
// Get the latest version of a question.
|
||||
$version = '';
|
||||
if ($latestversion) {
|
||||
$version = 'AND (qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entry} be
|
||||
ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qbe.id) OR qv.version is null)';
|
||||
}
|
||||
$questions = $DB->get_records_sql(
|
||||
"SELECT q.*, qv.status, qc.id AS category
|
||||
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.id {$usql} {$npsql} {$version}
|
||||
ORDER BY qc.id, q.qtype, q.name", $params);
|
||||
|
||||
// Iterate through questions, getting stuff we need.
|
||||
$qresults = [];
|
||||
foreach($questions as $key => $question) {
|
||||
$question->export_process = $export;
|
||||
$qtype = question_bank::get_qtype($question->qtype, false);
|
||||
@@ -307,7 +326,7 @@ function question_build_edit_resources($edittab, $baseurl, $params) {
|
||||
}
|
||||
|
||||
if ($thiscontext){
|
||||
$contexts = new question_edit_contexts($thiscontext);
|
||||
$contexts = new core_question\lib\question_edit_contexts($thiscontext);
|
||||
$contexts->require_one_edit_tab_cap($edittab);
|
||||
} else {
|
||||
$contexts = null;
|
||||
|
||||
+64
-24
@@ -419,8 +419,10 @@ abstract class question_bank {
|
||||
|
||||
list($categorysql, $params) = $DB->get_in_or_equal($categories);
|
||||
$sql = "SELECT DISTINCT q.qtype
|
||||
FROM {question} q
|
||||
WHERE q.category $categorysql";
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid $categorysql";
|
||||
|
||||
$qtypes = $DB->get_fieldset_sql($sql, $params);
|
||||
return $qtypes;
|
||||
@@ -454,7 +456,7 @@ class question_finder implements cache_data_source {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return get the question definition cache we are using.
|
||||
* @return cache_application|cache_session|cache_store the question definition cache we are using.
|
||||
*/
|
||||
protected function get_data_cache() {
|
||||
// Do not double cache here because it may break cache resetting.
|
||||
@@ -497,11 +499,17 @@ class question_finder implements cache_data_source {
|
||||
$extraconditions = ' AND (' . $extraconditions . ')';
|
||||
}
|
||||
|
||||
return $DB->get_records_select_menu('question',
|
||||
"category {$qcsql}
|
||||
AND parent = 0
|
||||
AND hidden = 0
|
||||
{$extraconditions}", $qcparams + $extraparams, '', 'id,id AS id2');
|
||||
$readystatus = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$sql = "SELECT q.id, q.id AS id2
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qbe.questioncategoryid {$qcsql}
|
||||
AND q.parent = 0
|
||||
AND qv.status = $readystatus
|
||||
{$extraconditions}";
|
||||
|
||||
return $DB->get_records_sql_menu($sql, $qcparams + $extraparams);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,14 +551,23 @@ class question_finder implements cache_data_source {
|
||||
|
||||
list($qcsql, $qcparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'qc');
|
||||
|
||||
$readystatus = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$select = "q.id, (SELECT COUNT(1)
|
||||
FROM " . $qubaids->from_question_attempts('qa') . "
|
||||
WHERE qa.questionid = q.id AND " . $qubaids->where() . "
|
||||
) AS previous_attempts";
|
||||
$from = "{question} q";
|
||||
$where = "q.category {$qcsql}
|
||||
$join = "JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid";
|
||||
$from = $from . " " . $join;
|
||||
$where = "qbe.questioncategoryid {$qcsql}
|
||||
AND q.parent = 0
|
||||
AND q.hidden = 0";
|
||||
AND qv.status = " . $readystatus . "
|
||||
AND qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be
|
||||
ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qbe.id)";
|
||||
$params = $qcparams;
|
||||
|
||||
if (!empty($tagids)) {
|
||||
@@ -580,20 +597,32 @@ class question_finder implements cache_data_source {
|
||||
}
|
||||
|
||||
return $DB->get_records_sql_menu("SELECT $select
|
||||
FROM $from
|
||||
WHERE $where $extraconditions
|
||||
ORDER BY previous_attempts",
|
||||
FROM $from
|
||||
WHERE $where $extraconditions
|
||||
ORDER BY previous_attempts",
|
||||
$qubaids->from_where_params() + $params + $extraparams);
|
||||
}
|
||||
|
||||
/* See cache_data_source::load_for_cache. */
|
||||
public function load_for_cache($questionid) {
|
||||
global $DB;
|
||||
$questiondata = $DB->get_record_sql('
|
||||
SELECT q.*, qc.contextid
|
||||
FROM {question} q
|
||||
JOIN {question_categories} qc ON q.category = qc.id
|
||||
WHERE q.id = :id', array('id' => $questionid), MUST_EXIST);
|
||||
|
||||
$sql = 'SELECT q.id, qc.id as category, q.parent, q.name, q.questiontext, q.questiontextformat,
|
||||
q.generalfeedback, q.generalfeedbackformat, q.defaultmark, q.penalty, q.qtype,
|
||||
q.length, q.stamp, q.timecreated, q.timemodified,
|
||||
q.createdby, q.modifiedby, qbe.idnumber,
|
||||
qc.contextid,
|
||||
qv.status,
|
||||
qv.id as versionid,
|
||||
qv.version,
|
||||
qv.questionbankentryid
|
||||
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 q.id = :id';
|
||||
|
||||
$questiondata = $DB->get_record_sql($sql, ['id' => $questionid], MUST_EXIST);
|
||||
get_question_options($questiondata);
|
||||
return $questiondata;
|
||||
}
|
||||
@@ -602,15 +631,26 @@ class question_finder implements cache_data_source {
|
||||
public function load_many_for_cache(array $questionids) {
|
||||
global $DB;
|
||||
list($idcondition, $params) = $DB->get_in_or_equal($questionids);
|
||||
$questiondata = $DB->get_records_sql('
|
||||
SELECT q.*, qc.contextid
|
||||
FROM {question} q
|
||||
JOIN {question_categories} qc ON q.category = qc.id
|
||||
WHERE q.id ' . $idcondition, $params);
|
||||
$sql = 'SELECT q.id, qc.id as category, q.parent, q.name, q.questiontext, q.questiontextformat,
|
||||
q.generalfeedback, q.generalfeedbackformat, q.defaultmark, q.penalty, q.qtype,
|
||||
q.length, q.stamp, q.timecreated, q.timemodified,
|
||||
q.createdby, q.modifiedby, qbe.idnumber,
|
||||
qc.contextid,
|
||||
qv.status,
|
||||
qv.id as versionid,
|
||||
qv.version,
|
||||
qv.questionbankentryid
|
||||
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 q.id ';
|
||||
|
||||
$questiondata = $DB->get_records_sql($sql . $idcondition, $params);
|
||||
|
||||
foreach ($questionids as $id) {
|
||||
if (!array_key_exists($id, $questiondata)) {
|
||||
throw new dml_missing_record_exception('question', '', array('id' => $id));
|
||||
throw new dml_missing_record_exception('question', '', ['id' => $id]);
|
||||
}
|
||||
get_question_options($questiondata[$id]);
|
||||
}
|
||||
|
||||
@@ -117,12 +117,13 @@ abstract class question_test_helper {
|
||||
*/
|
||||
public static function get_question_editing_form($cat, $questiondata) {
|
||||
$catcontext = context::instance_by_id($cat->contextid, MUST_EXIST);
|
||||
$contexts = new question_edit_contexts($catcontext);
|
||||
$contexts = new core_question\lib\question_edit_contexts($catcontext);
|
||||
$dataforformconstructor = new stdClass();
|
||||
$dataforformconstructor->createdby = $questiondata->createdby;
|
||||
$dataforformconstructor->qtype = $questiondata->qtype;
|
||||
$dataforformconstructor->contextid = $questiondata->contextid = $catcontext->id;
|
||||
$dataforformconstructor->category = $questiondata->category = $cat->id;
|
||||
$dataforformconstructor->status = $questiondata->status;
|
||||
$dataforformconstructor->formoptions = new stdClass();
|
||||
$dataforformconstructor->formoptions->canmove = true;
|
||||
$dataforformconstructor->formoptions->cansaveasnew = true;
|
||||
@@ -178,8 +179,8 @@ class test_question_maker {
|
||||
$q->penalty = 0.3333333;
|
||||
$q->length = 1;
|
||||
$q->stamp = make_unique_id_code();
|
||||
$q->version = make_unique_id_code();
|
||||
$q->hidden = 0;
|
||||
$q->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$q->version = 1;
|
||||
$q->timecreated = time();
|
||||
$q->timemodified = time();
|
||||
$q->createdby = $USER->id;
|
||||
@@ -200,8 +201,8 @@ class test_question_maker {
|
||||
$qdata->penalty = 0.3333333;
|
||||
$qdata->length = 1;
|
||||
$qdata->stamp = make_unique_id_code();
|
||||
$qdata->version = make_unique_id_code();
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->version = 1;
|
||||
$qdata->timecreated = time();
|
||||
$qdata->timemodified = time();
|
||||
$qdata->createdby = $USER->id;
|
||||
|
||||
+23
-10
@@ -136,7 +136,7 @@ class qformat_default {
|
||||
*/
|
||||
public function setContexts($contexts) {
|
||||
$this->contexts = $contexts;
|
||||
$this->translator = new context_to_string_translator($this->contexts);
|
||||
$this->translator = new core_question\lib\context_to_string_translator($this->contexts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,8 +411,8 @@ class qformat_default {
|
||||
// Id number not really set. Get rid of it.
|
||||
unset($question->idnumber);
|
||||
} else {
|
||||
if ($DB->record_exists('question',
|
||||
['idnumber' => $question->idnumber, 'category' => $question->category])) {
|
||||
if ($DB->record_exists('question_bank_entry',
|
||||
['idnumber' => $question->idnumber, 'questioncategoryid' => $question->category])) {
|
||||
// We cannot have duplicate idnumbers in a category. Just remove it.
|
||||
unset($question->idnumber);
|
||||
}
|
||||
@@ -426,6 +426,20 @@ class qformat_default {
|
||||
);
|
||||
|
||||
$question->id = $DB->insert_record('question', $question);
|
||||
// Create a bank entry for each question imported.
|
||||
$questionbankentry = new \stdClass();
|
||||
$questionbankentry->questioncategoryid = $question->category;
|
||||
$questionbankentry->idnumber = $question->idnumber ?? null;
|
||||
$questionbankentry->ownerid = $question->createdby;
|
||||
$questionbankentry->id = $DB->insert_record('question_bank_entry', $questionbankentry);
|
||||
// Create a version for each question imported.
|
||||
$questionversion = new \stdClass();
|
||||
$questionversion->questionbankentryid = $questionbankentry->id;
|
||||
$questionversion->questionid = $question->id;
|
||||
$questionversion->version = 1;
|
||||
$questionversion->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$questionversion->id = $DB->insert_record('question_versions', $questionversion);
|
||||
|
||||
$event = \core\event\question_created::create_from_question_instance($question, $this->importcontext);
|
||||
$event->trigger();
|
||||
|
||||
@@ -499,9 +513,6 @@ class qformat_default {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Give the question a unique version stamp determined by question_hash()
|
||||
$DB->set_field('question', 'version', question_hash($question),
|
||||
array('id' => $question->id));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -886,7 +897,8 @@ class qformat_default {
|
||||
// get the questions (from database) in this category
|
||||
// only get q's with no parents (no cloze subquestions specifically)
|
||||
if ($this->category) {
|
||||
$questions = get_questions_category($this->category, true);
|
||||
// Export only the latest version of a question.
|
||||
$questions = get_questions_category($this->category, true, true, true, true);
|
||||
} else {
|
||||
$questions = $this->questions;
|
||||
}
|
||||
@@ -907,8 +919,9 @@ class qformat_default {
|
||||
|
||||
foreach ($questions as $question) {
|
||||
// used by file api
|
||||
$contextid = $DB->get_field('question_categories', 'contextid',
|
||||
array('id' => $question->category));
|
||||
$questionbankentry = question_bank::load_question($question->id);
|
||||
$qcategory = $questionbankentry->category;
|
||||
$contextid = $DB->get_field('question_categories', 'contextid', ['id' => $qcategory]);
|
||||
$question->contextid = $contextid;
|
||||
|
||||
// do not export hidden questions
|
||||
@@ -934,7 +947,7 @@ class qformat_default {
|
||||
// If parent wasn't written.
|
||||
if (!in_array($trackcategoryparent, $writtencategories)) {
|
||||
// If parent is empty.
|
||||
if (!count($DB->get_records('question', array('category' => $trackcategoryparent)))) {
|
||||
if (!count($DB->get_records('question_bank_entry', ['questioncategoryid' => $trackcategoryparent]))) {
|
||||
$categoryname = $this->get_category_path($trackcategoryparent, $this->contexttofile);
|
||||
$categoryinfo = $DB->get_record('question_categories', array('id' => $trackcategoryparent),
|
||||
'name, info, infoformat, idnumber', MUST_EXIST);
|
||||
|
||||
@@ -67,6 +67,10 @@ class qformat_multianswer extends qformat_default {
|
||||
$question->generalfeedbackformat = FORMAT_MOODLE;
|
||||
$question->length = 1;
|
||||
$question->penalty = 0.3333333;
|
||||
$question->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$question->version = 1;
|
||||
$question->versionid = 0;
|
||||
$question->questionbankentryid = 0;
|
||||
|
||||
if (!empty($question)) {
|
||||
$question->name = $this->create_default_question_name($question->questiontext, get_string('questionname', 'question'));
|
||||
|
||||
@@ -1242,7 +1242,7 @@ class qformat_xml extends qformat_default {
|
||||
$expout .= " <defaultgrade>{$question->defaultmark}</defaultgrade>\n";
|
||||
}
|
||||
$expout .= " <penalty>{$question->penalty}</penalty>\n";
|
||||
$expout .= " <hidden>{$question->hidden}</hidden>\n";
|
||||
$expout .= " <hidden>{$question->status}</hidden>\n";
|
||||
$expout .= " <idnumber>{$idnumber}</idnumber>\n";
|
||||
|
||||
// The rest of the output depends on question type.
|
||||
|
||||
@@ -132,7 +132,13 @@ class qformat_xml_import_export_test extends advanced_testcase {
|
||||
*/
|
||||
public function assert_question_in_category($qname, $catname) {
|
||||
global $DB;
|
||||
$question = $DB->get_record('question', ['name' => $qname], '*', MUST_EXIST);
|
||||
|
||||
$sql = "SELECT q.*, qbe.questioncategoryid AS category
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE q.name = :name";
|
||||
$question = $DB->get_record_sql($sql, ['name' => $qname], MUST_EXIST);
|
||||
$category = $DB->get_record('question_categories', ['name' => $catname], '*', MUST_EXIST);
|
||||
$this->assertEquals($category->id, $question->category);
|
||||
}
|
||||
|
||||
@@ -57,8 +57,7 @@ class qformat_xml_test extends question_testcase {
|
||||
$q->penalty = 0.3333333;
|
||||
$q->length = 1;
|
||||
$q->stamp = make_unique_id_code();
|
||||
$q->version = make_unique_id_code();
|
||||
$q->hidden = 0;
|
||||
$q->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$q->timecreated = time();
|
||||
$q->timemodified = time();
|
||||
$q->createdby = $USER->id;
|
||||
@@ -342,7 +341,7 @@ END;
|
||||
$qdata->defaultmark = 0;
|
||||
$qdata->length = 0;
|
||||
$qdata->penalty = 0;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->idnumber = null;
|
||||
|
||||
$exporter = new qformat_xml();
|
||||
@@ -570,7 +569,7 @@ END;
|
||||
$qdata->defaultmark = 1;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 0;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->idnumber = null;
|
||||
$qdata->options = new stdClass();
|
||||
$qdata->options->id = 456;
|
||||
@@ -742,7 +741,7 @@ END;
|
||||
$qdata->defaultmark = 1;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 0.3333333;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->idnumber = null;
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
@@ -975,7 +974,7 @@ END;
|
||||
$qdata->defaultmark = 2;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 0.3333333;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->idnumber = null;
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
@@ -1152,7 +1151,7 @@ END;
|
||||
$qdata->defaultmark = 1;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 0.1;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->idnumber = null;
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
@@ -1284,7 +1283,7 @@ END;
|
||||
$qdata->defaultmark = 1;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 0.3333333;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->idnumber = null;
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
@@ -1460,7 +1459,7 @@ END;
|
||||
$qdata->defaultmark = 1;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 1;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->idnumber = null;
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
@@ -1520,7 +1519,7 @@ END;
|
||||
$qdata->defaultmark = 1;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 1;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->idnumber = 'TestIDNum2';
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
|
||||
+8
-2
@@ -57,9 +57,15 @@ function core_question_output_fragment_tags_form($args) {
|
||||
$filtercourses = null;
|
||||
}
|
||||
|
||||
$category = $DB->get_record('question_categories', ['id' => $question->category]);
|
||||
$sql = "SELECT qc.*
|
||||
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 q.id = :id";
|
||||
$category = $DB->get_record_sql($sql, ['id' => $question->id]);
|
||||
$questioncontext = \context::instance_by_id($category->contextid);
|
||||
$contexts = new \question_edit_contexts($editingcontext);
|
||||
$contexts = new \core_question\lib\question_edit_contexts($editingcontext);
|
||||
|
||||
// Load the question tags and filter the course tags by the current course.
|
||||
if (core_tag_tag::is_enabled('core_question', 'question')) {
|
||||
|
||||
@@ -173,6 +173,12 @@ class core_question_backup_testcase extends advanced_testcase {
|
||||
delete_course($courseid2, false);
|
||||
foreach ($questions as $question) {
|
||||
question_delete_question($question->id);
|
||||
|
||||
// TODO: Remove this assert when restore a question is fixed (create version and bank entry).
|
||||
$this->assertDebuggingCalled('Deleting question ' . $question->id .
|
||||
' which is no longer linked to a context. Assuming system context ' .
|
||||
'to avoid errors, but this may mean that some data like ' .
|
||||
'files, tags, are not cleaned up.');
|
||||
}
|
||||
$category1->delete_full(false);
|
||||
|
||||
@@ -190,9 +196,11 @@ class core_question_backup_testcase extends advanced_testcase {
|
||||
|
||||
// The questions should have been moved to a question category that belongs to a course context.
|
||||
$questions = $DB->get_records_sql("SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {question_categories} qc ON q.category = qc.id
|
||||
WHERE qc.contextid = ?", [$coursecontext3->id]);
|
||||
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 = ?", [$coursecontext3->id]);
|
||||
$this->assertCount(2, $questions);
|
||||
|
||||
// Now, retrieve tags for each question and check if they are assigned at the right context.
|
||||
|
||||
@@ -49,7 +49,7 @@ class core_question_bank_view_testcase extends advanced_testcase {
|
||||
$context = context_course::instance($course->id);
|
||||
|
||||
// Create a question in the default category.
|
||||
$contexts = new question_edit_contexts($context);
|
||||
$contexts = new core_question\lib\question_edit_contexts($context);
|
||||
$cat = question_make_default_categories($contexts->all());
|
||||
$questiondata = $questiongenerator->create_question('numerical', null,
|
||||
['name' => 'Example question', 'category' => $cat->id]);
|
||||
@@ -93,7 +93,7 @@ class core_question_bank_view_testcase extends advanced_testcase {
|
||||
$context = context_course::instance($course->id);
|
||||
|
||||
// Create a question in the default category.
|
||||
$contexts = new question_edit_contexts($context);
|
||||
$contexts = new core_question\lib\question_edit_contexts($context);
|
||||
$cat = question_make_default_categories($contexts->all());
|
||||
$questiondata = $questiongenerator->create_question('numerical', null,
|
||||
['name' => 'Example question', 'category' => $cat->id]);
|
||||
|
||||
@@ -32,7 +32,6 @@ Feature: A teacher can duplicate questions in the question bank
|
||||
And I press "id_submitbutton"
|
||||
Then I should see "Duplicated question name"
|
||||
And I should see "Test question to be copied"
|
||||
And "Duplicated question name" row "Last modified by" column of "categoryquestions" table should contain "Teacher One"
|
||||
And "Test question to be copied ID number qid" row "Created by" column of "categoryquestions" table should contain "Admin User"
|
||||
|
||||
Scenario: Duplicated questions automatically get a new name suggested
|
||||
|
||||
@@ -32,8 +32,7 @@ Feature: A teacher can edit questions in the question bank
|
||||
And I press "id_submitbutton"
|
||||
Then I should see "Edited question name"
|
||||
And I should not see "Test question to be edited"
|
||||
And "Edited question name" row "Created by" column of "categoryquestions" table should contain "Admin User"
|
||||
And "Edited question name" row "Last modified by" column of "categoryquestions" table should contain "Teacher 1"
|
||||
And "Edited question name" row "Created by" column of "categoryquestions" table should contain "Teacher 1"
|
||||
|
||||
Scenario: Editing a question can be cancelled
|
||||
When I choose "Edit question" action for "Test question to be edited" in the question bank
|
||||
@@ -41,7 +40,6 @@ Feature: A teacher can edit questions in the question bank
|
||||
And I press "Cancel"
|
||||
Then I should see "Test question to be edited"
|
||||
And "Test question to be edited" row "Created by" column of "categoryquestions" table should contain "Admin User"
|
||||
And "Test question to be edited" row "Last modified by" column of "categoryquestions" table should contain "Admin User"
|
||||
|
||||
Scenario: A question can have its idnumber removed
|
||||
Given the following "questions" exist:
|
||||
|
||||
@@ -57,7 +57,7 @@ class events_test extends \advanced_testcase {
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
|
||||
|
||||
$contexts = new question_edit_contexts(\context_module::instance($quiz->cmid));
|
||||
$contexts = new \core_question\lib\question_edit_contexts(context_module::instance($quiz->cmid));
|
||||
|
||||
$defaultcategoryobj = question_make_default_categories([$contexts->lowest()]);
|
||||
$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
|
||||
@@ -108,7 +108,11 @@ class events_test extends \advanced_testcase {
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
|
||||
|
||||
<<<<<<< HEAD:question/tests/event/events_test.php
|
||||
$contexts = new question_edit_contexts(\context_module::instance($quiz->cmid));
|
||||
=======
|
||||
$contexts = new core_question\lib\question_edit_contexts(context_module::instance($quiz->cmid));
|
||||
>>>>>>> MDL-71696 core_question: Changes for versioning:question/tests/events_test.php
|
||||
|
||||
$defaultcategoryobj = question_make_default_categories([$contexts->lowest()]);
|
||||
$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
|
||||
@@ -229,12 +233,13 @@ class events_test extends \advanced_testcase {
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$qtype->save_question($questiondata, $fromform);
|
||||
$question = $qtype->save_question($questiondata, $fromform);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\question_updated', $event);
|
||||
// Every save is a new question after Moodle 4.0.
|
||||
$this->assertInstanceOf('\core\event\question_created', $event);
|
||||
$this->assertEquals($question->id, $event->objectid);
|
||||
$this->assertEquals($cat->id, $event->other['categoryid']);
|
||||
$this->assertDebuggingNotCalled();
|
||||
|
||||
@@ -14,15 +14,25 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Quiz module test data generator.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 2013 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core_question\local\bank\constants;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Quiz module test data generator class
|
||||
* Class core_question_generator for generating question data.
|
||||
*
|
||||
* @package moodlecore
|
||||
* @subpackage question
|
||||
* @copyright 2013 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @package core_question
|
||||
* @copyright 2013 The Open University
|
||||
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_question_generator extends component_generator_base {
|
||||
|
||||
@@ -31,19 +41,23 @@ class core_question_generator extends component_generator_base {
|
||||
*/
|
||||
protected $categorycount = 0;
|
||||
|
||||
/**
|
||||
* Make the category count to zero.
|
||||
*/
|
||||
public function reset() {
|
||||
$this->categorycount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new question category.
|
||||
*
|
||||
* @param array|stdClass $record
|
||||
* @return stdClass question_categories record.
|
||||
*/
|
||||
public function create_question_category($record = null) {
|
||||
global $DB;
|
||||
|
||||
$this->categorycount++;
|
||||
$this->categorycount ++;
|
||||
|
||||
$defaults = [
|
||||
'name' => 'Test question category ' . $this->categorycount,
|
||||
@@ -79,18 +93,11 @@ class core_question_generator extends component_generator_base {
|
||||
* @return stdClass the question data.
|
||||
*/
|
||||
public function create_question($qtype, $which = null, $overrides = null) {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
|
||||
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, $which);
|
||||
$fromform = (object) $this->datagenerator->combine_defaults_and_record(
|
||||
(array) $fromform, $overrides);
|
||||
|
||||
$question = new stdClass();
|
||||
$question->category = $fromform->category;
|
||||
$question->qtype = $qtype;
|
||||
$question->qtype = $qtype;
|
||||
$question->createdby = 0;
|
||||
$question->idnumber = null;
|
||||
$question->status = constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $this->update_question($question, $which, $overrides);
|
||||
}
|
||||
@@ -122,10 +129,8 @@ class core_question_generator extends component_generator_base {
|
||||
$qtype = $question->qtype;
|
||||
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, $which);
|
||||
$fromform = (object) $this->datagenerator->combine_defaults_and_record(
|
||||
(array) $question, $fromform);
|
||||
$fromform = (object) $this->datagenerator->combine_defaults_and_record(
|
||||
(array) $fromform, $overrides);
|
||||
$fromform = (object) $this->datagenerator->combine_defaults_and_record((array) $question, $fromform);
|
||||
$fromform = (object) $this->datagenerator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
|
||||
$question = question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class generator_test extends \advanced_testcase {
|
||||
question_move_questions_to_category([$quest1->id], $qcat1->id);
|
||||
$this->assertSame('myquest', \question_bank::load_question_data($quest1->id)->idnumber);
|
||||
// Can only change idnumber of quest2 once quest1 has been moved to another category.
|
||||
$quest2 = $generator->update_question($questions[1], null, ['idnumber' => 'myquest']);
|
||||
$quest2 = $generator->update_question($questions[1], null, ['idnumber' => 'myquest_4']);
|
||||
question_move_questions_to_category([$quest2->id], $qcat1->id);
|
||||
$this->assertSame('myquest_4', \question_bank::load_question_data($quest2->id)->idnumber);
|
||||
// Check can add an idnumber of 0.
|
||||
|
||||
@@ -310,12 +310,14 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$q2 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
|
||||
|
||||
$this->setUser($otheruser);
|
||||
$questiongenerator->update_question($q2);
|
||||
// When we update a question, a new question/version is created.
|
||||
$q2updated = $questiongenerator->update_question($q2);
|
||||
$q3 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
|
||||
$q4 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
|
||||
|
||||
$this->setUser($user);
|
||||
$questiongenerator->update_question($q3);
|
||||
// When we update a question, a new question/version is created.
|
||||
$q3updated = $questiongenerator->update_question($q3);
|
||||
$q5 = $questiongenerator->create_question('shortanswer', null, ['category' => $othercat->id]);
|
||||
|
||||
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
|
||||
@@ -337,12 +339,12 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$this->assertEquals(0, $qrecord->createdby);
|
||||
$this->assertEquals(0, $qrecord->modifiedby);
|
||||
|
||||
$qrecord = $DB->get_record('question', ['id' => $q2->id]);
|
||||
$this->assertEquals(0, $qrecord->createdby);
|
||||
$qrecord = $DB->get_record('question', ['id' => $q2updated->id]);
|
||||
$this->assertEquals($otheruser->id, $qrecord->createdby);
|
||||
$this->assertEquals($otheruser->id, $qrecord->modifiedby);
|
||||
|
||||
$qrecord = $DB->get_record('question', ['id' => $q3->id]);
|
||||
$this->assertEquals($otheruser->id, $qrecord->createdby);
|
||||
$qrecord = $DB->get_record('question', ['id' => $q3updated->id]);
|
||||
$this->assertEquals(0, $qrecord->createdby);
|
||||
$this->assertEquals(0, $qrecord->modifiedby);
|
||||
|
||||
$qrecord = $DB->get_record('question', ['id' => $q4->id]);
|
||||
@@ -513,9 +515,11 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$DB->count_records_sql("SELECT COUNT(q.id)
|
||||
FROM {question} q
|
||||
JOIN {question_categories} qc ON q.category = qc.id
|
||||
WHERE qc.contextid = ?
|
||||
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 = ?
|
||||
AND (q.createdby = ? OR q.modifiedby = ? OR q.createdby = ? OR q.modifiedby = ?)",
|
||||
[$course1context->id, $user1->id, $user1->id, $user2->id, $user2->id])
|
||||
);
|
||||
@@ -524,9 +528,11 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$this->assertEquals(
|
||||
2,
|
||||
$DB->count_records_sql("SELECT COUNT(q.id)
|
||||
FROM {question} q
|
||||
JOIN {question_categories} qc ON q.category = qc.id
|
||||
WHERE qc.contextid = ? AND (q.createdby = ? OR q.modifiedby = ?)",
|
||||
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 = ? AND (q.createdby = ? OR q.modifiedby = ?)",
|
||||
[$course1context->id, $user3->id, $user3->id])
|
||||
);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class question_bank_column_testcase extends advanced_testcase {
|
||||
$this->resetAfterTest();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$questionbank = new core_question\local\bank\view(
|
||||
new question_edit_contexts(context_course::instance($course->id)),
|
||||
new core_question\lib\question_edit_contexts(context_course::instance($course->id)),
|
||||
new moodle_url('/'),
|
||||
$course
|
||||
);
|
||||
@@ -79,7 +79,7 @@ class question_bank_column_testcase extends advanced_testcase {
|
||||
$this->resetAfterTest();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$questionbank = new core_question\local\bank\view(
|
||||
new question_edit_contexts(context_course::instance($course->id)),
|
||||
new core_question\lib\question_edit_contexts(context_course::instance($course->id)),
|
||||
new moodle_url('/'),
|
||||
$course
|
||||
);
|
||||
|
||||
@@ -68,7 +68,7 @@ class random_question_loader_testcase extends advanced_testcase {
|
||||
|
||||
$cat = $generator->create_question_category();
|
||||
$question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $question1->id]);
|
||||
$DB->set_field('question_versions', 'status', 1, ['questionid' => $question1->id]);
|
||||
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
|
||||
|
||||
$this->assertNull($loader->get_next_question_id($cat->id, 0));
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
<?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;
|
||||
|
||||
use question_bank;
|
||||
|
||||
/**
|
||||
* Comment lib unit tests.
|
||||
*
|
||||
* @package core_question
|
||||
* @copyright 2021 Catalyst IT Australia Pty Ltd
|
||||
* @author Guillermo Gomez Arias <guillermogomez@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \question_bank
|
||||
*/
|
||||
class version_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* @var \context_module module context.
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* @var \stdClass course object.
|
||||
*/
|
||||
protected $course;
|
||||
|
||||
/**
|
||||
* @var \component_generator_base question generator.
|
||||
*/
|
||||
protected $qgenerator;
|
||||
|
||||
/**
|
||||
* @var \stdClass quiz object.
|
||||
*/
|
||||
protected $quiz;
|
||||
|
||||
/**
|
||||
* Called before every test.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
self::setAdminUser();
|
||||
$this->resetAfterTest();
|
||||
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$this->course = $datagenerator->create_course();
|
||||
$this->quiz = $datagenerator->create_module('quiz', ['course' => $this->course->id]);
|
||||
$this->qgenerator = $datagenerator->get_plugin_generator('core_question');
|
||||
$this->context = \context_module::instance($this->quiz->cmid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if creating a question a new version and bank entry records are created.
|
||||
*
|
||||
* @covers ::load_question
|
||||
*/
|
||||
public function test_make_question_create_version_and_bank_entry() {
|
||||
global $DB;
|
||||
|
||||
$qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
|
||||
|
||||
// Get the question object after creating a question.
|
||||
$questiondefinition = question_bank::load_question($question->id);
|
||||
|
||||
// The version and bank entry in the object should be the same.
|
||||
$sql = "SELECT qv.id AS versionid, qv.questionbankentryid
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE q.id = ?";
|
||||
$questionversion = $DB->get_record_sql($sql, [$questiondefinition->id]);
|
||||
$this->assertEquals($questionversion->versionid, $questiondefinition->versionid);
|
||||
$this->assertEquals($questionversion->questionbankentryid, $questiondefinition->questionbankentryid);
|
||||
|
||||
// If a question is updated, a new version should be created.
|
||||
$this->qgenerator->update_question($question, null, ['name' => 'This is a new version']);
|
||||
$newquestiondefinition = question_bank::load_question($question->id);
|
||||
// The version should be 2.
|
||||
$this->assertEquals('2', $newquestiondefinition->version);
|
||||
|
||||
// Both versions should be in same bank entry.
|
||||
$this->assertEquals($questiondefinition->questionbankentryid, $newquestiondefinition->questionbankentryid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if deleting a question the related version and bank entry records are deleted.
|
||||
*
|
||||
* @covers ::load_question
|
||||
* @covers ::question_delete_question
|
||||
*/
|
||||
public function test_delete_question_delete_versions() {
|
||||
global $DB;
|
||||
|
||||
$qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
|
||||
$questionfirstversionid = $question->id;
|
||||
|
||||
// Create a new version and try to remove it.
|
||||
$this->qgenerator->update_question($question, null, ['name' => 'This is a new version']);
|
||||
|
||||
// The new version and bank entry record should exist.
|
||||
$sql = "SELECT q.id, qv.id AS versionid, qv.questionbankentryid
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE q.id = ?";
|
||||
$questionobject = $DB->get_records_sql($sql, [$question->id]);
|
||||
$this->assertCount(1, $questionobject);
|
||||
|
||||
// Try to delete new version.
|
||||
question_delete_question($question->id);
|
||||
|
||||
// The version record should not exist.
|
||||
$sql = "SELECT qv.*
|
||||
FROM {question_versions} qv
|
||||
WHERE qv.id = ?";
|
||||
$questionversion = $DB->get_record_sql($sql, [$questionobject[$question->id]->versionid]);
|
||||
$this->assertFalse($questionversion);
|
||||
|
||||
// The bank entry record should exist because there is an older version.
|
||||
$sql = "SELECT qbe.*
|
||||
FROM {question_bank_entries} qbe
|
||||
WHERE qbe.id = ?";
|
||||
$questionbankentry = $DB->get_records_sql($sql, [$questionobject[$question->id]->questionbankentryid]);
|
||||
$this->assertCount(1, $questionbankentry);
|
||||
|
||||
// Now remove the first version.
|
||||
question_delete_question($questionfirstversionid);
|
||||
$sql = "SELECT qbe.*
|
||||
FROM {question_bank_entries} qbe
|
||||
WHERE qbe.id = ?";
|
||||
$questionbankentry = $DB->get_record_sql($sql, [$questionobject[$question->id]->questionbankentryid]);
|
||||
// The bank entry record should not exist.
|
||||
$this->assertFalse($questionbankentry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if deleting a question will not break a quiz.
|
||||
*
|
||||
* @covers ::load_question
|
||||
* @covers ::quiz_add_quiz_question
|
||||
* @covers ::question_delete_question
|
||||
*/
|
||||
public function test_delete_question_in_use() {
|
||||
$qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategory->id]);
|
||||
$questionfirstversionid = $question->id;
|
||||
|
||||
// Create a new version and try to remove it after adding it to a quiz.
|
||||
$this->qgenerator->update_question($question, null, ['name' => 'This is a new version']);
|
||||
|
||||
// Add it to the quiz.
|
||||
quiz_add_quiz_question($question->id, $this->quiz);
|
||||
|
||||
// Try to delete new version.
|
||||
question_delete_question($question->id);
|
||||
// Try to delete old version.
|
||||
question_delete_question($questionfirstversionid);
|
||||
|
||||
// The questions should exist even after trying to remove it.
|
||||
$questionversion1 = question_bank::load_question($question->id);
|
||||
$questionversion2 = question_bank::load_question($questionfirstversionid);
|
||||
$this->assertEquals($questionversion1->id, $question->id);
|
||||
$this->assertEquals($questionversion2->id, $questionfirstversionid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if moving a category will not break a quiz.
|
||||
*
|
||||
* @covers ::load_question
|
||||
* @covers ::quiz_add_quiz_question
|
||||
*/
|
||||
public function test_move_category_with_questions() {
|
||||
global $DB;
|
||||
|
||||
$qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$qcategorychild = $this->qgenerator->create_question_category(['contextid' => $this->context->id, 'parent' => $qcategory->id]);
|
||||
$systemcontext = \context_system::instance();
|
||||
$qcategorysys = $this->qgenerator->create_question_category(['contextid' => $systemcontext->id]);
|
||||
$question = $this->qgenerator->create_question('shortanswer', null, ['category' => $qcategorychild->id]);
|
||||
$questiondefinition = question_bank::load_question($question->id);
|
||||
|
||||
// Add it to the quiz.
|
||||
quiz_add_quiz_question($question->id, $this->quiz);
|
||||
|
||||
// Move the category to system context.
|
||||
$contexts = new \core_question\local\bank\question_edit_contexts($systemcontext);
|
||||
$qcobject = new \qbank_managecategories\question_category_object(null,
|
||||
new \moodle_url('/question/bank/managecategories/category.php', ['courseid' => SITEID]),
|
||||
$contexts->having_one_edit_tab_cap('categories'), 0, null, 0,
|
||||
$contexts->having_cap('moodle/question:add'));
|
||||
$qcobject->move_questions_and_delete_category($qcategorychild->id, $qcategorysys->id);
|
||||
|
||||
// The bank entry record should point to the new category in order to not break quizzes.
|
||||
$sql = "SELECT qbe.questioncategoryid
|
||||
FROM {question_bank_entries} qbe
|
||||
WHERE qbe.id = ?";
|
||||
$questionbankentry = $DB->get_record_sql($sql, [$questiondefinition->questionbankentryid]);
|
||||
$this->assertEquals($qcategorysys->id, $questionbankentry->questioncategoryid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all versions will have the same bank entry idnumber value.
|
||||
*
|
||||
* @covers ::load_question
|
||||
*/
|
||||
public function test_id_number_in_bank_entry() {
|
||||
global $DB;
|
||||
|
||||
$qcategory = $this->qgenerator->create_question_category(['contextid' => $this->context->id]);
|
||||
$question = $this->qgenerator->create_question('shortanswer', null,
|
||||
[
|
||||
'category' => $qcategory->id,
|
||||
'idnumber' => 'id1'
|
||||
]);
|
||||
$questionid1 = $question->id;
|
||||
|
||||
// Create a new version and try to remove it after adding it to a quiz.
|
||||
$this->qgenerator->update_question($question, null, ['idnumber' => 'id2']);
|
||||
$questionid2 = $question->id;
|
||||
// Change the id number and get the question object.
|
||||
$this->qgenerator->update_question($question, null, ['idnumber' => 'id3']);
|
||||
$questionid3 = $question->id;
|
||||
|
||||
// The new version and bank entry record should exist.
|
||||
$questiondefinition = question_bank::load_question($question->id);
|
||||
$sql = "SELECT q.id AS questionid, qv.id AS versionid, qbe.id AS questionbankentryid, qbe.idnumber
|
||||
FROM {question_bank_entries} qbe
|
||||
JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id
|
||||
JOIN {question} q ON q.id = qv.questionid
|
||||
WHERE qbe.id = ?";
|
||||
$questionbankentry = $DB->get_records_sql($sql, [$questiondefinition->questionbankentryid]);
|
||||
|
||||
// We should have 3 versions and 1 question bank entry with the same idnumber.
|
||||
$this->assertCount(3, $questionbankentry);
|
||||
$this->assertEquals($questionbankentry[$questionid1]->idnumber, 'id3');
|
||||
$this->assertEquals($questionbankentry[$questionid2]->idnumber, 'id3');
|
||||
$this->assertEquals($questionbankentry[$questionid3]->idnumber, 'id3');
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@ class qtype_calculated_test_helper extends question_test_helper {
|
||||
$qdata->name = 'Simple sum';
|
||||
$qdata->questiontext = 'What is {a} + {b}?';
|
||||
$qdata->generalfeedback = 'Generalfeedback: {={a} + {b}} is the right answer.';
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
$qdata->options->unitgradingtype = 0;
|
||||
@@ -186,6 +187,8 @@ class qtype_calculated_test_helper extends question_test_helper {
|
||||
$fromform->feedback[2]['format'] = FORMAT_HTML;
|
||||
$fromform->feedback[2]['text'] = 'Completely wrong.';
|
||||
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ class qtype_calculated_test extends advanced_testcase {
|
||||
|
||||
$this->assertEquals(['id', 'category', 'parent', 'name', 'questiontext', 'questiontextformat',
|
||||
'generalfeedback', 'generalfeedbackformat', 'defaultmark', 'penalty', 'qtype',
|
||||
'length', 'stamp', 'version', 'hidden', 'timecreated', 'timemodified',
|
||||
'createdby', 'modifiedby', 'idnumber', 'contextid', 'options', 'hints', 'categoryobject'],
|
||||
'length', 'stamp', 'timecreated', 'timemodified', 'createdby', 'modifiedby', 'idnumber', 'contextid',
|
||||
'status', 'versionid', 'version', 'questionbankentryid', 'categoryobject', 'options', 'hints'],
|
||||
array_keys(get_object_vars($questiondata)));
|
||||
$this->assertEquals($category->id, $questiondata->category);
|
||||
$this->assertEquals(0, $questiondata->parent);
|
||||
@@ -102,7 +102,7 @@ class qtype_calculated_test extends advanced_testcase {
|
||||
$this->assertEquals(0, $questiondata->penalty);
|
||||
$this->assertEquals('calculated', $questiondata->qtype);
|
||||
$this->assertEquals(1, $questiondata->length);
|
||||
$this->assertEquals(0, $questiondata->hidden);
|
||||
$this->assertEquals(0, $questiondata->status);
|
||||
$this->assertEquals($question->createdby, $questiondata->createdby);
|
||||
$this->assertEquals($question->createdby, $questiondata->modifiedby);
|
||||
$this->assertEquals('', $questiondata->idnumber);
|
||||
|
||||
@@ -205,6 +205,8 @@ class qtype_calculatedsimple_test_helper extends question_test_helper {
|
||||
$form->definition[19] = '1-0-b';
|
||||
$form->definition[20] = '1-0-a';
|
||||
|
||||
$form->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -222,7 +224,8 @@ class qtype_calculatedsimple_test_helper extends question_test_helper {
|
||||
$q->penalty = 0.3333333;
|
||||
$q->qtype = 'calculatedsimple';
|
||||
$q->length = '1';
|
||||
$q->hidden = '0';
|
||||
$q->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$q->version = 1;
|
||||
$q->options = new stdClass();
|
||||
$q->options->synchronize = 0;
|
||||
$q->options->single = 0;
|
||||
|
||||
@@ -89,7 +89,7 @@ class qtype_calculatedsimple_test extends advanced_testcase {
|
||||
$actualquestiondata = end($actualquestionsdata);
|
||||
|
||||
foreach ($questiondata as $property => $value) {
|
||||
if (!in_array($property, array('id', 'version', 'timemodified', 'timecreated', 'options'))) {
|
||||
if (!in_array($property, array('id', 'timemodified', 'timecreated', 'options'))) {
|
||||
$this->assertEquals($value, $actualquestiondata->$property);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class qtype_ddimageortext_edit_form_test extends advanced_testcase {
|
||||
$fakequestion->inputs = null;
|
||||
|
||||
$form = new qtype_ddimageortext_edit_form(new moodle_url('/'), $fakequestion, $category,
|
||||
new question_edit_contexts($syscontext));
|
||||
new core_question\lib\question_edit_contexts($syscontext));
|
||||
|
||||
return [$form, $category];
|
||||
}
|
||||
|
||||
@@ -246,6 +246,8 @@ class qtype_ddimageortext_test_helper extends question_test_helper {
|
||||
$fromform->hintclearwrong = array(1, 0, 1, 0);
|
||||
$fromform->hintshownumcorrect = array(1, 1, 1, 1);
|
||||
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class qtype_ddmarker_edit_form_test extends advanced_testcase {
|
||||
$fakequestion->inputs = null;
|
||||
|
||||
$form = new qtype_ddmarker_edit_form(new moodle_url('/'), $fakequestion, $category,
|
||||
new question_edit_contexts($syscontext));
|
||||
new core_question\lib\question_edit_contexts($syscontext));
|
||||
|
||||
return [$form, $category];
|
||||
}
|
||||
|
||||
@@ -193,6 +193,8 @@ class qtype_ddmarker_test_helper extends question_test_helper {
|
||||
$fromform->hintclearwrong = array(0, 1);
|
||||
$fromform->hintoptions = array(0, 1);
|
||||
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
|
||||
@@ -258,6 +260,8 @@ class qtype_ddmarker_test_helper extends question_test_helper {
|
||||
$fromform->hintclearwrong = array(0, 1);
|
||||
$fromform->hintoptions = array(0, 1);
|
||||
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class qtype_ddwtos_edit_form_test extends advanced_testcase {
|
||||
$fakequestion->inputs = null;
|
||||
|
||||
$form = new $classname(new moodle_url('/'), $fakequestion, $category,
|
||||
new question_edit_contexts($syscontext));
|
||||
new core_question\lib\question_edit_contexts($syscontext));
|
||||
|
||||
return [$form, $category];
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ class qtype_ddwtos_test_helper extends question_test_helper {
|
||||
test_question_maker::set_standard_combined_feedback_form_data($fromform);
|
||||
$fromform->shownumcorrect = 0;
|
||||
$fromform->penalty = 0.3333333;
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
@@ -124,6 +125,7 @@ class qtype_ddwtos_test_helper extends question_test_helper {
|
||||
test_question_maker::set_standard_combined_feedback_form_data($fromform);
|
||||
$fromform->shownumcorrect = 0;
|
||||
$fromform->penalty = 0.3333333;
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
@@ -149,6 +151,7 @@ class qtype_ddwtos_test_helper extends question_test_helper {
|
||||
test_question_maker::set_standard_combined_feedback_form_data($fromform);
|
||||
$fromform->shownumcorrect = 0;
|
||||
$fromform->penalty = 0.3333333;
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
@@ -207,6 +210,7 @@ class qtype_ddwtos_test_helper extends question_test_helper {
|
||||
test_question_maker::set_standard_combined_feedback_form_data($fromform);
|
||||
$fromform->shownumcorrect = 0;
|
||||
$fromform->penalty = 0.3333333;
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
|
||||
@@ -73,8 +73,10 @@ class qtype_ddwtos_test extends question_testcase {
|
||||
$dd->penalty = 0.3333333;
|
||||
$dd->length = 1;
|
||||
$dd->stamp = make_unique_id_code();
|
||||
$dd->version = make_unique_id_code();
|
||||
$dd->hidden = 0;
|
||||
$dd->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$dd->version = 1;
|
||||
$dd->versionid = 0;
|
||||
$dd->questionbankentryid = 0;
|
||||
$dd->idnumber = null;
|
||||
$dd->timecreated = time();
|
||||
$dd->timemodified = time();
|
||||
@@ -147,7 +149,6 @@ class qtype_ddwtos_test extends question_testcase {
|
||||
|
||||
$expected = test_question_maker::make_question('ddwtos');
|
||||
$expected->stamp = $qdata->stamp;
|
||||
$expected->version = $qdata->version;
|
||||
$expected->idnumber = null;
|
||||
|
||||
$q = $this->qtype->make_question($qdata);
|
||||
@@ -284,7 +285,7 @@ class qtype_ddwtos_test extends question_testcase {
|
||||
$qdata->defaultmark = 3;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 0.3333333;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
$qdata->options->shuffleanswers = 1;
|
||||
|
||||
@@ -70,7 +70,6 @@ class qtype_description_test_helper extends question_test_helper {
|
||||
$qdata->category = 0;
|
||||
$qdata->parent = 0;
|
||||
$qdata->stamp = make_unique_id_code();
|
||||
$qdata->version = make_unique_id_code();
|
||||
$qdata->timecreated = time();
|
||||
$qdata->timemodified = time();
|
||||
$qdata->createdby = $USER->id;
|
||||
@@ -84,7 +83,7 @@ class qtype_description_test_helper extends question_test_helper {
|
||||
$qdata->defaultmark = 0;
|
||||
$qdata->length = 0;
|
||||
$qdata->penalty = 0;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$qdata->hints = array();
|
||||
$qdata->options = new stdClass();
|
||||
$qdata->options->answers = array();
|
||||
@@ -105,6 +104,7 @@ class qtype_description_test_helper extends question_test_helper {
|
||||
'format' => FORMAT_HTML);
|
||||
$form->generalfeedback = array('text' => 'And here is some more text shown only on the review page.',
|
||||
'format' => FORMAT_HTML);
|
||||
$form->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class qtype_description_test extends advanced_testcase {
|
||||
$actualquestiondata = end($actualquestionsdata);
|
||||
|
||||
foreach ($questiondata as $property => $value) {
|
||||
if (!in_array($property, array('id', 'version', 'timemodified', 'timecreated'))) {
|
||||
if (!in_array($property, array('id', 'timemodified', 'timecreated'))) {
|
||||
$this->assertEquals($value, $actualquestiondata->$property);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +103,15 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
$this->question = $question;
|
||||
$this->contexts = $contexts;
|
||||
|
||||
// Get the question category id.
|
||||
if (isset($question->id)) {
|
||||
$qcategory = $question->categoryobject->id ?? get_question_bank_entry($question->id)->questioncategoryid;
|
||||
} else {
|
||||
$qcategory = $question->category;
|
||||
}
|
||||
|
||||
$record = $DB->get_record('question_categories',
|
||||
array('id' => $question->category), 'contextid');
|
||||
array('id' => $qcategory), 'contextid');
|
||||
$this->context = context::instance_by_id($record->contextid);
|
||||
|
||||
$this->editoroptions = array('subdirs' => 1, 'maxfiles' => EDITOR_UNLIMITED_FILES,
|
||||
@@ -175,8 +182,13 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
$currentgrp[0] = $mform->createElement('questioncategory', 'category',
|
||||
get_string('categorycurrent', 'question'),
|
||||
array('contexts' => array($this->categorycontext)));
|
||||
if ($this->question->formoptions->canedit ||
|
||||
$this->question->formoptions->cansaveasnew) {
|
||||
// Validate if the question is being duplicated.
|
||||
$beingcopied = false;
|
||||
if (isset($this->question->beingcopied)) {
|
||||
$beingcopied = $this->question->beingcopied;
|
||||
}
|
||||
if (($this->question->formoptions->canedit ||
|
||||
$this->question->formoptions->cansaveasnew) && ($beingcopied)) {
|
||||
// Not move only form.
|
||||
$currentgrp[1] = $mform->createElement('checkbox', 'usecurrentcat', '',
|
||||
get_string('categorycurrentuse', 'question'));
|
||||
@@ -187,16 +199,32 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
$mform->addGroup($currentgrp, 'currentgrp',
|
||||
get_string('categorycurrent', 'question'), null, false);
|
||||
|
||||
$mform->addElement('questioncategory', 'categorymoveto',
|
||||
if (($beingcopied)) {
|
||||
$mform->addElement('questioncategory', 'categorymoveto',
|
||||
get_string('categorymoveto', 'question'),
|
||||
array('contexts' => array($this->categorycontext)));
|
||||
if ($this->question->formoptions->canedit ||
|
||||
if ($this->question->formoptions->canedit ||
|
||||
$this->question->formoptions->cansaveasnew) {
|
||||
// Not move only form.
|
||||
$mform->disabledIf('categorymoveto', 'usecurrentcat', 'checked');
|
||||
// Not move only form.
|
||||
$mform->disabledIf('categorymoveto', 'usecurrentcat', 'checked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->question->id) && !$this->question->beingcopied) {
|
||||
// Add extra information from plugins when editing a question (e.g.: Authors, version control and usage).
|
||||
$functionname = 'edit_form_display';
|
||||
$questiondata = [];
|
||||
$plugins = get_plugin_list_with_function('qbank', $functionname);
|
||||
foreach ($plugins as $componentname => $plugin) {
|
||||
$element = new StdClass();
|
||||
$element->pluginhtml = component_callback($componentname, $functionname, [$this->question]);
|
||||
$questiondata['editelements'][] = $element;
|
||||
}
|
||||
$mform->addElement('static', 'versioninfo', get_string('versioninfo', 'qbank_editquestion'),
|
||||
$PAGE->get_renderer('qbank_editquestion')->render_question_info($questiondata));
|
||||
}
|
||||
|
||||
$mform->addElement('text', 'name', get_string('questionname', 'question'),
|
||||
array('size' => 50, 'maxlength' => 255));
|
||||
$mform->setType('name', PARAM_TEXT);
|
||||
@@ -207,6 +235,9 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
$mform->setType('questiontext', PARAM_RAW);
|
||||
$mform->addRule('questiontext', null, 'required', null, 'client');
|
||||
|
||||
$mform->addElement('select', 'status', get_string('status', 'qbank_editquestion'),
|
||||
\qbank_editquestion\editquestion_helper::get_question_status_list());
|
||||
|
||||
$mform->addElement('float', 'defaultmark', get_string('defaultmark', 'question'),
|
||||
array('size' => 7));
|
||||
$mform->setDefault('defaultmark', $this->get_default_value('defaultmark', 1));
|
||||
@@ -237,30 +268,6 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
$this->customfieldhandler->instance_form_definition($mform, empty($this->question->id) ? 0 : $this->question->id);
|
||||
}
|
||||
|
||||
if (!empty($this->question->id)) {
|
||||
$mform->addElement('header', 'createdmodifiedheader',
|
||||
get_string('createdmodifiedheader', 'question'));
|
||||
$a = new stdClass();
|
||||
if (!empty($this->question->createdby)) {
|
||||
$a->time = userdate($this->question->timecreated);
|
||||
$a->user = fullname($DB->get_record(
|
||||
'user', array('id' => $this->question->createdby)));
|
||||
} else {
|
||||
$a->time = get_string('unknown', 'question');
|
||||
$a->user = get_string('unknown', 'question');
|
||||
}
|
||||
$mform->addElement('static', 'created', get_string('created', 'question'),
|
||||
get_string('byandon', 'question', $a));
|
||||
if (!empty($this->question->modifiedby)) {
|
||||
$a = new stdClass();
|
||||
$a->time = userdate($this->question->timemodified);
|
||||
$a->user = fullname($DB->get_record(
|
||||
'user', array('id' => $this->question->modifiedby)));
|
||||
$mform->addElement('static', 'modified', get_string('modified', 'question'),
|
||||
get_string('byandon', 'question', $a));
|
||||
}
|
||||
}
|
||||
|
||||
$this->add_hidden_fields();
|
||||
|
||||
$mform->addElement('hidden', 'qtype');
|
||||
@@ -870,13 +877,20 @@ abstract class question_edit_form extends question_wizard_form {
|
||||
$categoryinfo = $fromform['category'];
|
||||
}
|
||||
list($categoryid, $notused) = explode(',', $categoryinfo);
|
||||
$conditions = 'category = ? AND idnumber = ?';
|
||||
$conditions = 'questioncategoryid = ? AND idnumber = ?';
|
||||
$params = [$categoryid, $fromform['idnumber']];
|
||||
if (!empty($this->question->id)) {
|
||||
// Get the question bank entry id to not check the idnumber for the same bank entry.
|
||||
$sql = "SELECT DISTINCT qbe.id
|
||||
FROM {question_versions} qv
|
||||
JOIN {question_bank_entry} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE qv.questionid = ?";
|
||||
$bankentry = $DB->get_record_sql($sql, ['id' => $this->question->id]);
|
||||
$conditions .= ' AND id <> ?';
|
||||
$params[] = $this->question->id;
|
||||
$params[] = $bankentry->id;
|
||||
}
|
||||
if ($DB->record_exists_select('question', $conditions, $params)) {
|
||||
|
||||
if ($DB->record_exists_select('question_bank_entry', $conditions, $params)) {
|
||||
$errors['idnumber'] = get_string('idnumbertaken', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ class qtype_essay_test_helper extends question_test_helper {
|
||||
$fromform->filetypeslist = ''; // Although once saved in the DB, this becomes null, the form returns '' here.
|
||||
$fromform->graderinfo = array('text' => '', 'format' => FORMAT_HTML);
|
||||
$fromform->responsetemplate = array('text' => '', 'format' => FORMAT_HTML);
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
@@ -147,6 +148,7 @@ class qtype_essay_test_helper extends question_test_helper {
|
||||
$fromform->filetypeslist = ''; // Although once saved in the DB, this becomes null, the form returns '' here.
|
||||
$fromform->graderinfo = array('text' => '', 'format' => FORMAT_HTML);
|
||||
$fromform->responsetemplate = array('text' => '', 'format' => FORMAT_HTML);
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
@@ -184,6 +186,7 @@ class qtype_essay_test_helper extends question_test_helper {
|
||||
$fromform->filetypeslist = ''; // Although once saved in the DB, this becomes null, the form returns '' here.
|
||||
$fromform->graderinfo = array('text' => '', 'format' => FORMAT_HTML);
|
||||
$fromform->responsetemplate = array('text' => '', 'format' => FORMAT_HTML);
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class qtype_essay_restore_testcase extends restore_date_testcase {
|
||||
// Create a course with one essay question in its question bank.
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
$contexts = new question_edit_contexts(context_course::instance($course->id));
|
||||
$contexts = new core_question\lib\question_edit_contexts(context_course::instance($course->id));
|
||||
$category = question_make_default_categories($contexts->all());
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$essay = $questiongenerator->create_question('essay', null, array('category' => $category->id));
|
||||
@@ -60,7 +60,7 @@ class qtype_essay_restore_testcase extends restore_date_testcase {
|
||||
$newcourseid = $this->backup_and_restore($course);
|
||||
|
||||
// Verify that the restored question has options.
|
||||
$contexts = new question_edit_contexts(context_course::instance($newcourseid));
|
||||
$contexts = new core_question\lib\question_edit_contexts(context_course::instance($newcourseid));
|
||||
$newcategory = question_make_default_categories($contexts->all());
|
||||
$newessay = $DB->get_record('question', ['category' => $newcategory->id, 'qtype' => 'essay']);
|
||||
$this->assertTrue($DB->record_exists('qtype_essay_options', ['questionid' => $newessay->id]));
|
||||
|
||||
@@ -94,7 +94,7 @@ class qtype_gapselect_edit_form_test extends advanced_testcase {
|
||||
$fakequestion->inputs = null;
|
||||
|
||||
$form = new $classname(new moodle_url('/'), $fakequestion, $category,
|
||||
new question_edit_contexts($syscontext));
|
||||
new core_question\lib\question_edit_contexts($syscontext));
|
||||
|
||||
return [$form, $category];
|
||||
}
|
||||
|
||||
@@ -57,8 +57,10 @@ class qtype_gapselect_test_helper extends question_test_helper {
|
||||
$gapselect->penalty = 0.3333333;
|
||||
$gapselect->length = 1;
|
||||
$gapselect->stamp = make_unique_id_code();
|
||||
$gapselect->version = make_unique_id_code();
|
||||
$gapselect->hidden = 0;
|
||||
$gapselect->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$gapselect->versionid = 0;
|
||||
$gapselect->version = 1;
|
||||
$gapselect->questionbankentryid = 0;
|
||||
$gapselect->idnumber = null;
|
||||
$gapselect->timecreated = time();
|
||||
$gapselect->timemodified = time();
|
||||
@@ -108,6 +110,7 @@ class qtype_gapselect_test_helper extends question_test_helper {
|
||||
test_question_maker::set_standard_combined_feedback_form_data($fromform);
|
||||
$fromform->shownumcorrect = 0;
|
||||
$fromform->penalty = 0.3333333;
|
||||
$fromform->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $fromform;
|
||||
}
|
||||
@@ -126,6 +129,7 @@ class qtype_gapselect_test_helper extends question_test_helper {
|
||||
$gapselect->questiontext = 'The [[1]] brown [[2]] jumped over the [[3]] dog.';
|
||||
$gapselect->generalfeedback = 'This sentence uses each letter of the alphabet.';
|
||||
$gapselect->qtype = question_bank::get_qtype('gapselect');
|
||||
$gapselect->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$gapselect->shufflechoices = true;
|
||||
|
||||
@@ -165,6 +169,7 @@ class qtype_gapselect_test_helper extends question_test_helper {
|
||||
'7 [[1]] 11 [[2]] 13 [[1]] 17 [[2]] 19 = 3';
|
||||
$gapselect->generalfeedback = 'This sentence uses each letter of the alphabet.';
|
||||
$gapselect->qtype = question_bank::get_qtype('gapselect');
|
||||
$gapselect->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$gapselect->shufflechoices = true;
|
||||
|
||||
@@ -200,6 +205,7 @@ class qtype_gapselect_test_helper extends question_test_helper {
|
||||
'<span lang="en" class="multilang">sat on the</span><span lang="ru" class="multilang">сидела на</span> [[2]].';
|
||||
$gapselect->generalfeedback = 'This sentence uses each letter of the alphabet.';
|
||||
$gapselect->qtype = question_bank::get_qtype('gapselect');
|
||||
$gapselect->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$gapselect->shufflechoices = true;
|
||||
|
||||
@@ -240,6 +246,7 @@ class qtype_gapselect_test_helper extends question_test_helper {
|
||||
$gapselect->questiontext = 'The price of the ball is [[1]] approx.';
|
||||
$gapselect->generalfeedback = 'The choice is yours';
|
||||
$gapselect->qtype = question_bank::get_qtype('gapselect');
|
||||
$gapselect->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$gapselect->shufflechoices = true;
|
||||
|
||||
|
||||
@@ -105,7 +105,6 @@ class qtype_gapselect_test extends question_testcase {
|
||||
|
||||
$expected = test_question_maker::make_question('gapselect');
|
||||
$expected->stamp = $qdata->stamp;
|
||||
$expected->version = $qdata->version;
|
||||
|
||||
$q = $this->qtype->make_question($qdata);
|
||||
|
||||
@@ -239,7 +238,7 @@ class qtype_gapselect_test extends question_testcase {
|
||||
$qdata->defaultmark = 3;
|
||||
$qdata->length = 1;
|
||||
$qdata->penalty = 0.3333333;
|
||||
$qdata->hidden = 0;
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$qdata->options = new stdClass();
|
||||
$qdata->options->shuffleanswers = 1;
|
||||
|
||||
@@ -59,7 +59,10 @@ class qtype_match_test_helper extends question_test_helper {
|
||||
$q->defaultmark = 1;
|
||||
$q->penalty = 0.3333333;
|
||||
$q->length = 1;
|
||||
$q->hidden = 0;
|
||||
$q->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$q->versionid = 0;
|
||||
$q->version = 1;
|
||||
$q->questionbankentryid = 0;
|
||||
$q->createdby = $USER->id;
|
||||
$q->modifiedby = $USER->id;
|
||||
|
||||
@@ -105,6 +108,10 @@ class qtype_match_test_helper extends question_test_helper {
|
||||
$q->generalfeedback = array('text' => 'General feedback.', 'format' => FORMAT_HTML);
|
||||
$q->defaultmark = 1;
|
||||
$q->penalty = 0.3333333;
|
||||
$q->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$q->versionid = 0;
|
||||
$q->version = 1;
|
||||
$q->questionbankentryid = 0;
|
||||
|
||||
$q->shuffleanswers = 1;
|
||||
test_question_maker::set_standard_combined_feedback_form_data($q);
|
||||
@@ -180,7 +187,10 @@ class qtype_match_test_helper extends question_test_helper {
|
||||
$q->defaultmark = 1;
|
||||
$q->penalty = 0.3333333;
|
||||
$q->length = 1;
|
||||
$q->hidden = 0;
|
||||
$q->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$q->versionid = 0;
|
||||
$q->version = 1;
|
||||
$q->questionbankentryid = 0;
|
||||
$q->createdby = $USER->id;
|
||||
$q->modifiedby = $USER->id;
|
||||
|
||||
@@ -221,6 +231,10 @@ class qtype_match_test_helper extends question_test_helper {
|
||||
$q->generalfeedback = ['text' => 'Java has some advantages over PHP I guess!', 'format' => FORMAT_HTML];
|
||||
$q->defaultmark = 1;
|
||||
$q->penalty = 0.3333333;
|
||||
$q->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$q->versionid = 0;
|
||||
$q->version = 1;
|
||||
$q->questionbankentryid = 0;
|
||||
|
||||
$q->shuffleanswers = 1;
|
||||
test_question_maker::set_standard_combined_feedback_form_data($q);
|
||||
|
||||
@@ -66,8 +66,10 @@ class qtype_match_test extends advanced_testcase {
|
||||
$q->penalty = 0.3333333;
|
||||
$q->length = 1;
|
||||
$q->stamp = make_unique_id_code();
|
||||
$q->version = make_unique_id_code();
|
||||
$q->hidden = 0;
|
||||
$q->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$q->version = 1;
|
||||
$q->versionid = 0;
|
||||
$q->questionbankentryid = 0;
|
||||
$q->idnumber = null;
|
||||
$q->timecreated = time();
|
||||
$q->timemodified = time();
|
||||
@@ -184,7 +186,8 @@ class qtype_match_test extends advanced_testcase {
|
||||
$actualquestiondata = end($actualquestionsdata);
|
||||
|
||||
foreach ($questiondata as $property => $value) {
|
||||
if (!in_array($property, array('id', 'version', 'timemodified', 'timecreated', 'options', 'stamp'))) {
|
||||
if (!in_array($property, ['id', 'timemodified', 'timecreated', 'options', 'stamp',
|
||||
'versionid', 'questionbankentryid'])) {
|
||||
$this->assertEquals($value, $actualquestiondata->$property);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,8 +75,7 @@ class qtype_missingtype extends question_type {
|
||||
$question->length = 1;
|
||||
$question->penalty = 0;
|
||||
$question->stamp = '';
|
||||
$question->version = 0;
|
||||
$question->hidden = 0;
|
||||
$question->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$question->timecreated = null;
|
||||
$question->timemodified = null;
|
||||
$question->createdby = null;
|
||||
|
||||
@@ -56,8 +56,10 @@ class qtype_missing_test extends question_testcase {
|
||||
$questiondata->qtype = 'strange_unknown';
|
||||
$questiondata->length = 1;
|
||||
$questiondata->stamp = make_unique_id_code();
|
||||
$questiondata->version = make_unique_id_code();
|
||||
$questiondata->hidden = 0;
|
||||
$questiondata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
$questiondata->version = 1;
|
||||
$questiondata->versionid = 0;
|
||||
$questiondata->questionbankentryid = 0;
|
||||
$questiondata->idnumber = null;
|
||||
$questiondata->timecreated = 0;
|
||||
$questiondata->timemodified = 0;
|
||||
|
||||
@@ -73,6 +73,7 @@ class qtype_multianswer extends question_type {
|
||||
// For wrapped questions the maxgrade is always equal to the defaultmark,
|
||||
// there is no entry in the question_instances table for them.
|
||||
$wrapped->maxmark = $wrapped->defaultmark;
|
||||
$wrapped->category = $question->categoryobject->id;
|
||||
$question->options->questions[$sequence[$wrapped->id]] = $wrapped;
|
||||
}
|
||||
$question->hints = $DB->get_records('question_hints',
|
||||
@@ -94,15 +95,17 @@ class qtype_multianswer extends question_type {
|
||||
|
||||
// First we get all the existing wrapped questions.
|
||||
$oldwrappedquestions = [];
|
||||
if ($oldwrappedids = $DB->get_field('question_multianswer', 'sequence',
|
||||
array('question' => $question->id))) {
|
||||
$oldwrappedidsarray = explode(',', $oldwrappedids);
|
||||
$unorderedquestions = $DB->get_records_list('question', 'id', $oldwrappedidsarray);
|
||||
if (isset($question->oldparent)) {
|
||||
if ($oldwrappedids = $DB->get_field('question_multianswer', 'sequence',
|
||||
['question' => $question->oldparent])) {
|
||||
$oldwrappedidsarray = explode(',', $oldwrappedids);
|
||||
$unorderedquestions = $DB->get_records_list('question', 'id', $oldwrappedidsarray);
|
||||
|
||||
// Keep the order as given in the sequence field.
|
||||
foreach ($oldwrappedidsarray as $questionid) {
|
||||
if (isset($unorderedquestions[$questionid])) {
|
||||
$oldwrappedquestions[] = $unorderedquestions[$questionid];
|
||||
// Keep the order as given in the sequence field.
|
||||
foreach ($oldwrappedidsarray as $questionid) {
|
||||
if (isset($unorderedquestions[$questionid])) {
|
||||
$oldwrappedquestions[] = $unorderedquestions[$questionid];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,10 +114,10 @@ class qtype_multianswer extends question_type {
|
||||
foreach ($question->options->questions as $wrapped) {
|
||||
if (!empty($wrapped)) {
|
||||
// If we still have some old wrapped question ids, reuse the next of them.
|
||||
|
||||
$wrapped->id = 0;
|
||||
if (is_array($oldwrappedquestions) &&
|
||||
$oldwrappedquestion = array_shift($oldwrappedquestions)) {
|
||||
$wrapped->id = $oldwrappedquestion->id;
|
||||
$wrapped->oldid = $oldwrappedquestion->id;
|
||||
if ($oldwrappedquestion->qtype != $wrapped->qtype) {
|
||||
switch ($oldwrappedquestion->qtype) {
|
||||
case 'multichoice':
|
||||
@@ -132,11 +135,8 @@ class qtype_multianswer extends question_type {
|
||||
default:
|
||||
throw new moodle_exception('qtypenotrecognized',
|
||||
'qtype_multianswer', '', $oldwrappedquestion->qtype);
|
||||
$wrapped->id = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$wrapped->id = 0;
|
||||
}
|
||||
}
|
||||
$wrapped->name = $question->name;
|
||||
@@ -183,7 +183,7 @@ class qtype_multianswer extends question_type {
|
||||
$question->id = $authorizedquestion->id;
|
||||
}
|
||||
|
||||
$question->category = $authorizedquestion->category;
|
||||
$question->category = $form->category;
|
||||
$form->defaultmark = $question->defaultmark;
|
||||
$form->questiontext = $question->questiontext;
|
||||
$form->questiontextformat = 0;
|
||||
|
||||
@@ -131,6 +131,7 @@ class qtype_multianswer_test_helper extends question_test_helper {
|
||||
|
||||
$qdata->defaultmark = 2.0;
|
||||
$qdata->qtype = 'multianswer';
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$sa = new stdClass();
|
||||
test_question_maker::initialise_question_data($sa);
|
||||
@@ -140,6 +141,7 @@ class qtype_multianswer_test_helper extends question_test_helper {
|
||||
$sa->generalfeedback = '';
|
||||
$sa->penalty = 0.0;
|
||||
$sa->qtype = 'shortanswer';
|
||||
$sa->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$sa->options = new stdClass();
|
||||
$sa->options->usecase = 0;
|
||||
@@ -159,6 +161,7 @@ class qtype_multianswer_test_helper extends question_test_helper {
|
||||
$mc->generalfeedback = '';
|
||||
$mc->penalty = 0.0;
|
||||
$mc->qtype = 'multichoice';
|
||||
$mc->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$mc->options = new stdClass();
|
||||
$mc->options->layout = 0;
|
||||
@@ -214,6 +217,7 @@ class qtype_multianswer_test_helper extends question_test_helper {
|
||||
|
||||
$qdata->defaultmark = 1.0;
|
||||
$qdata->qtype = 'multianswer';
|
||||
$qdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$mc = new stdClass();
|
||||
test_question_maker::initialise_question_data($mc);
|
||||
@@ -223,6 +227,7 @@ class qtype_multianswer_test_helper extends question_test_helper {
|
||||
$mc->generalfeedback = '';
|
||||
$mc->penalty = 0.0;
|
||||
$mc->qtype = 'multichoice';
|
||||
$mc->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
$mc->options = new stdClass();
|
||||
$mc->options->layout = 0;
|
||||
@@ -274,6 +279,8 @@ class qtype_multianswer_test_helper extends question_test_helper {
|
||||
1 => array('text' => 'Hint 2', 'format' => FORMAT_HTML, 'itemid' => 0),
|
||||
);
|
||||
|
||||
$formdata->status = \core_question\local\bank\constants::QUESTION_STATUS_READY;
|
||||
|
||||
return $formdata;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user