Merge branch 'MDL-74452_master' of https://github.com/marxjohnson/moodle

This commit is contained in:
Shamim Rezaie 2023-03-29 15:26:49 +11:00
commit a0b134107c
11 changed files with 184 additions and 7 deletions

View File

@ -24,6 +24,7 @@
namespace mod_quiz\output;
use core_question\local\bank\question_version_status;
use mod_quiz\question\bank\qbank_helper;
use \mod_quiz\structure;
use \html_writer;
@ -765,7 +766,8 @@ class edit_renderer extends \plugin_renderer_base {
'questionname' => $this->get_question_name_for_slot($structure, $slot, $pageurl),
'questionicons' => $this->get_action_icon($structure, $slot, $pageurl),
'questiondependencyicon' => ($structure->can_be_edited() ? $this->question_dependency_icon($structure, $slot) : ''),
'versionselection' => false
'versionselection' => false,
'draftversion' => $structure->get_question_in_slot($slot)->status == question_version_status::QUESTION_STATUS_DRAFT,
];
$data['versionoptions'] = [];

View File

@ -134,7 +134,9 @@ class qbank_helper {
-- just before the commit that added this comment.
-- For relevant question_bank_entries, this gets the latest non-draft slot number.
LEFT JOIN (
SELECT lv.questionbankentryid, MAX(lv.version) AS version
SELECT lv.questionbankentryid,
MAX(CASE WHEN lv.status <> :draft THEN lv.version END) AS usableversion,
MAX(lv.version) AS anyversion
FROM {quiz_slots} lslot
JOIN {question_references} lqr ON lqr.usingcontextid = :quizcontextid2 AND lqr.component = 'mod_quiz'
AND lqr.questionarea = 'slot' AND lqr.itemid = lslot.id
@ -142,13 +144,14 @@ class qbank_helper {
WHERE lslot.quizid = :quizid2
$slotidtest2
AND lqr.version IS NULL
AND lv.status <> :draft
GROUP BY lv.questionbankentryid
) latestversions ON latestversions.questionbankentryid = qr.questionbankentryid
LEFT JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id
-- Either specified version, or latest ready version.
AND qv.version = COALESCE(qr.version, latestversions.version)
-- Either specified version, or latest usable version, or a draft version.
AND qv.version = COALESCE(qr.version,
latestversions.usableversion,
latestversions.anyversion)
LEFT JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
LEFT JOIN {question} q ON q.id = qv.questionid

View File

@ -20,6 +20,7 @@ use cm_info;
use coding_exception;
use context;
use context_module;
use core_question\local\bank\question_version_status;
use mod_quiz\question\bank\qbank_helper;
use mod_quiz\question\display_options;
use moodle_exception;
@ -561,6 +562,10 @@ class quiz_settings {
$qcategories = [];
foreach ($this->get_questions() as $questiondata) {
if ($questiondata->status == question_version_status::QUESTION_STATUS_DRAFT) {
// Skip questions where all versions are draft.
continue;
}
if ($questiondata->qtype === 'random' && $includepotential) {
if (!isset($qcategories[$questiondata->category])) {
$qcategories[$questiondata->category] = false;

View File

@ -736,6 +736,9 @@ $string['questiondependencyadd'] = 'No restriction on when question {$a->thisq}
$string['questiondependencyfree'] = 'No restriction on this question';
$string['questiondependencyremove'] = 'Question {$a->thisq} cannot be attempted until the previous question {$a->previousq} has been completed • Click to change';
$string['questiondependsonprevious'] = 'This question cannot be attempted until the previous question has been completed.';
$string['questiondraftonly'] = 'The question \'{$a}\' has all versions in Draft status, so cannot be used. Visit the question bank and set the status to Ready.';
$string['questiondraftwillnotwork'] = 'This question has all versions in Draft status. The quiz will not work with this question in place. Remove this question,
or visit the question bank and set the status to Ready.';
$string['questioninuse'] = 'The question \'{$a->questionname}\' is currently being used in: <br />{$a->quiznames}<br />The question will not be deleted from these quizzes but only from the category list.';
$string['questionmissing'] = 'Question for this session is missing';
$string['questionname'] = 'Question name';

View File

@ -180,6 +180,9 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
$slot += 1;
$maxmark[$slot] = $questiondata->maxmark;
$page[$slot] = $questiondata->page;
if ($questiondata->status == \core_question\local\bank\question_version_status::QUESTION_STATUS_DRAFT) {
throw new moodle_exception('questiondraftonly', 'mod_quiz', '', $questiondata->name);
}
if ($questiondata->qtype == 'random') {
$randomfound = true;
continue;
@ -318,7 +321,11 @@ function quiz_start_attempt_built_on_last($quba, $attempt, $lastattempt) {
$oldnumberstonew = [];
foreach ($oldquba->get_attempt_iterator() as $oldslot => $oldqa) {
$newslot = $quba->add_question($oldqa->get_question(false), $oldqa->get_max_mark());
$question = $oldqa->get_question(false);
if ($question->status == \core_question\local\bank\question_version_status::QUESTION_STATUS_DRAFT) {
throw new moodle_exception('questiondraftonly', 'mod_quiz', '', $question->name);
}
$newslot = $quba->add_question($question, $oldqa->get_max_mark());
$quba->start_question_based_on($newslot, $oldqa);

View File

@ -50,3 +50,6 @@
{{{questiondependencyicon}}}
{{/canbeedited}}
</div>
{{#draftversion}}
<div class="alert alert-danger" role="alert">{{#str}}questiondraftwillnotwork, mod_quiz{{/str}}</div>
{{/draftversion}}

View File

@ -16,8 +16,8 @@
namespace mod_quiz;
use core_question\local\bank\question_version_status;
use question_engine;
use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die();
@ -430,4 +430,79 @@ class attempt_test extends \advanced_testcase {
$this->assertFalse($attempt->check_page_access(4));
$this->assertFalse($attempt->check_page_access(1));
}
/**
* Starting a new attempt with a question in draft status should throw an exception.
*
* @covers ::quiz_start_new_attempt()
* @return void
*/
public function test_start_new_attempt_with_draft(): void {
$this->resetAfterTest();
// Create course.
$course = $this->getDataGenerator()->create_course();
// Create students.
$student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create quiz.
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'grade' => 100.0, 'sumgrades' => 2, 'layout' => '1,0']);
// Create question and add it to quiz.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$question = $questiongenerator->create_question('shortanswer', null,
['category' => $cat->id, 'status' => question_version_status::QUESTION_STATUS_DRAFT]);
quiz_add_quiz_question($question->id, $quiz, 1);
$quizobj = quiz_settings::create($quiz->id);
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$attempt = quiz_create_attempt($quizobj, 1, false, time(), false, $student1->id);
$this->expectExceptionObject(new \moodle_exception('questiondraftonly', 'mod_quiz', '', $question->name));
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, time());
}
/**
* Starting a new attempt built on last with a question in draft status should throw an exception.
*
* @covers ::quiz_start_attempt_built_on_last()
* @return void
*/
public function test_quiz_start_attempt_built_on_last_with_draft(): void {
global $DB;
$this->resetAfterTest();
// Create course.
$course = $this->getDataGenerator()->create_course();
// Create students.
$student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create quiz.
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'grade' => 100.0, 'sumgrades' => 2, 'layout' => '1,0']);
// Create question and add it to quiz.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
quiz_add_quiz_question($question->id, $quiz, 1);
$quizobj = quiz_settings::create($quiz->id);
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$attempt = quiz_create_attempt($quizobj, 1, false, time(), false, $student1->id);
$attempt = quiz_start_new_attempt($quizobj, $quba, $attempt, 1, time());
$attempt = quiz_attempt_save_started($quizobj, $quba, $attempt);
$DB->set_field('question_versions', 'status', question_version_status::QUESTION_STATUS_DRAFT,
['questionid' => $question->id]);
$quizobj = quiz_settings::create($quiz->id);
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$newattempt = quiz_create_attempt($quizobj, 2, $attempt, time(), false, $student1->id);
$this->expectExceptionObject(new \moodle_exception('questiondraftonly', 'mod_quiz', '', $question->name));
quiz_start_attempt_built_on_last($quba, $newattempt, $attempt);
}
}

View File

@ -111,3 +111,13 @@ Feature: Quiz question versioning
And I press "Add selected questions to the quiz"
Then I should see "Other question" on quiz page "1"
And the field "version" in the "Other question" "list_item" matches value "Always latest"
@javascript
Scenario: Adding a question where all available versions are drafts should display a helpful message.
Given quiz "Quiz 1" contains the following questions:
| question | page |
| First question | 1 |
And I am on the "Quiz 1" "mod_quiz > Question bank" page logged in as teacher
And I set the field "question_status_dropdown" in the "First question" "table_row" to "Draft"
When I am on the "Quiz 1" "mod_quiz > Edit" page
Then I should see "This question has all versions in Draft status. The quiz will not work with this question in place."

View File

@ -27,6 +27,7 @@
namespace mod_quiz\external;
use core_external\external_api;
use core_question\local\bank\question_version_status;
use externallib_advanced_testcase;
use mod_quiz\question\display_options;
use mod_quiz\quiz_attempt;
@ -1974,6 +1975,10 @@ class external_test extends externallib_advanced_testcase {
$question = $questiongenerator->create_question('essay', null, ['category' => $cat->id]);
quiz_add_quiz_question($question->id, $quiz);
$question = $questiongenerator->create_question('multichoice', null,
['category' => $cat->id, 'status' => question_version_status::QUESTION_STATUS_DRAFT]);
quiz_add_quiz_question($question->id, $quiz);
$this->setUser($this->student);
$result = mod_quiz_external::get_quiz_required_qtypes($quiz->id);

View File

@ -16,6 +16,10 @@
namespace mod_quiz\external;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../../../../webservice/tests/helpers.php');
use coding_exception;
use core_question_generator;
use externallib_advanced_testcase;

View File

@ -16,6 +16,7 @@
namespace mod_quiz;
use core_question\local\bank\question_version_status;
use mod_quiz\external\submit_question_version;
use mod_quiz\question\bank\qbank_helper;
@ -143,4 +144,63 @@ class qbank_helper_test extends \advanced_testcase {
$slot = reset($slots);
$this->assertEquals($finalq->id, $slot->questionid);
}
/**
* When a question only has draft versions, we should get those and not a dummy question.
*
* @return void
* @covers ::get_question_structure
*/
public function test_get_question_structure_with_drafts(): void {
$this->resetAfterTest();
// Create a quiz.
$quiz = $this->create_test_quiz($this->course);
$quizcontext = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
// Create some questions with drafts in the quiz question bank.
/** @var \core_question_generator $questiongenerator */
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category(['contextid' => $quizcontext->id]);
$q1 = $questiongenerator->create_question('essay', null,
['category' => $cat->id, 'name' => 'This is q1 the first version']);
$q2 = $questiongenerator->create_question('essay', null,
['category' => $cat->id, 'name' => 'This is q2 the first version',
'status' => question_version_status::QUESTION_STATUS_DRAFT]);
$q3 = $questiongenerator->create_question('essay', null,
['category' => $cat->id, 'name' => 'This is q3 the first version',
'status' => question_version_status::QUESTION_STATUS_DRAFT]);
// Create a new draft version of a question.
$q1final = $questiongenerator->update_question(clone $q1, null,
['name' => 'This is q1 the second version', 'status' => question_version_status::QUESTION_STATUS_DRAFT]);
$q3final = $questiongenerator->update_question(clone $q3, null,
['name' => 'This is q3 the second version', 'status' => question_version_status::QUESTION_STATUS_DRAFT]);
// Add the questions to the quiz.
quiz_add_quiz_question($q1->id, $quiz);
quiz_add_quiz_question($q2->id, $quiz);
quiz_add_quiz_question($q3->id, $quiz);
// Load the quiz object and check.
$quizobj = \mod_quiz\quiz_settings::create($quiz->id);
$quizobj->preload_questions();
$quizobj->load_questions();
$questions = $quizobj->get_questions();
$this->assertCount(3, $questions);
// When a question has a Ready version, we should get that and not he draft.
$this->assertTrue(array_key_exists($q1->id, $questions));
$this->assertFalse(array_key_exists($q1final->id, $questions));
$this->assertEquals(question_version_status::QUESTION_STATUS_READY, $questions[$q1->id]->status);
$this->assertEquals('essay', $questions[$q1->id]->qtype);
// When a question only has a draft, we should get that.
$this->assertTrue(array_key_exists($q2->id, $questions));
$this->assertEquals(question_version_status::QUESTION_STATUS_DRAFT, $questions[$q2->id]->status);
$this->assertEquals('essay', $questions[$q2->id]->qtype);
// When a question has several versions but all draft, we should get the latest draft.
$this->assertFalse(array_key_exists($q3->id, $questions));
$this->assertTrue(array_key_exists($q3final->id, $questions));
$this->assertEquals(question_version_status::QUESTION_STATUS_DRAFT, $questions[$q3final->id]->status);
$this->assertEquals('essay', $questions[$q3final->id]->qtype);
}
}