From f55f89162dc08c1815aa4585aa536379209db5bf Mon Sep 17 00:00:00 2001 From: Eric Merrill Date: Mon, 8 Oct 2018 15:46:28 -0400 Subject: [PATCH] MDL-63456 qtype_multichoice: Return a default options object if missing --- question/type/multichoice/questiontype.php | 44 ++++++++++- .../multichoice/tests/questiontype_test.php | 73 +++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/question/type/multichoice/questiontype.php b/question/type/multichoice/questiontype.php index 6de105e999f..60bd72146b6 100644 --- a/question/type/multichoice/questiontype.php +++ b/question/type/multichoice/questiontype.php @@ -39,11 +39,51 @@ require_once($CFG->libdir . '/questionlib.php'); class qtype_multichoice extends question_type { public function get_question_options($question) { global $DB, $OUTPUT; - $question->options = $DB->get_record('qtype_multichoice_options', - array('questionid' => $question->id), '*', MUST_EXIST); + + $question->options = $DB->get_record('qtype_multichoice_options', ['questionid' => $question->id]); + + if ($question->options === false) { + // If this has happened, then we have a problem. + // For the user to be able to edit or delete this question, we need options. + debugging("Question ID {$question->id} was missing an options record. Using default.", DEBUG_DEVELOPER); + + $question->options = $this->create_default_options($question); + } + parent::get_question_options($question); } + /** + * Create a default options object for the provided question. + * + * @param object $question The queston we are working with. + * @return object The options object. + */ + protected function create_default_options($question) { + // Create a default question options record. + $options = new stdClass(); + $options->questionid = $question->id; + + // Get the default strings and just set the format. + $options->correctfeedback = get_string('correctfeedbackdefault', 'question'); + $options->correctfeedbackformat = FORMAT_HTML; + $options->partiallycorrectfeedback = get_string('partiallycorrectfeedbackdefault', 'question');; + $options->partiallycorrectfeedbackformat = FORMAT_HTML; + $options->incorrectfeedback = get_string('incorrectfeedbackdefault', 'question'); + $options->incorrectfeedbackformat = FORMAT_HTML; + + $config = get_config('qtype_multichoice'); + $options->single = $config->answerhowmany; + if (isset($question->layout)) { + $options->layout = $question->layout; + } + $options->answernumbering = $config->answernumbering; + $options->shuffleanswers = $config->shuffleanswers; + $options->shownumcorrect = 1; + + return $options; + } + public function save_question_options($question) { global $DB; $context = $question->context; diff --git a/question/type/multichoice/tests/questiontype_test.php b/question/type/multichoice/tests/questiontype_test.php index fa93d268d7c..4c9f1980f6c 100644 --- a/question/type/multichoice/tests/questiontype_test.php +++ b/question/type/multichoice/tests/questiontype_test.php @@ -163,4 +163,77 @@ class qtype_multichoice_test extends advanced_testcase { } } } + + /** + * Test to make sure that loading of question options works, including in an error case. + */ + public function test_get_question_options() { + global $DB; + + $this->resetAfterTest(true); + $this->setAdminUser(); + + // Create a complete, in DB question to use. + $questiondata = test_question_maker::get_question_data('multichoice', 'two_of_four'); + $formdata = test_question_maker::get_question_form_data('multichoice', 'two_of_four'); + + $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $cat = $generator->create_question_category(array()); + + $formdata->category = "{$cat->id},{$cat->contextid}"; + qtype_multichoice_edit_form::mock_submit((array)$formdata); + + $form = qtype_multichoice_test_helper::get_question_editing_form($cat, $questiondata); + + $this->assertTrue($form->is_validated()); + + $fromform = $form->get_data(); + + $returnedfromsave = $this->qtype->save_question($questiondata, $fromform); + + // Now get just the raw DB record. + $question = $DB->get_record('question', ['id' => $returnedfromsave->id], '*', MUST_EXIST); + + // Load it. + $this->qtype->get_question_options($question); + $this->assertDebuggingNotCalled(); + $this->assertInstanceOf(stdClass::class, $question->options); + + $options = $question->options; + $this->assertEquals($question->id, $options->questionid); + $this->assertEquals(0, $options->single); + + $this->assertCount(4, $options->answers); + + // Now we are going to delete the options record. + $DB->delete_records('qtype_multichoice_options', ['questionid' => $question->id]); + + // Now see what happens. + $question = $DB->get_record('question', ['id' => $returnedfromsave->id], '*', MUST_EXIST); + $this->qtype->get_question_options($question); + + $this->assertDebuggingCalled('Question ID '.$question->id.' was missing an options record. Using default.'); + $this->assertInstanceOf(stdClass::class, $question->options); + $options = $question->options; + $this->assertEquals($question->id, $options->questionid); + $this->assertCount(4, $options->answers); + + $this->assertEquals(get_string('correctfeedbackdefault', 'question'), $options->correctfeedback); + $this->assertEquals(FORMAT_HTML, $options->correctfeedbackformat); + + // We no longer know how many answers, so it just has to guess with the default value. + $this->assertEquals(get_config('qtype_multichoice', 'answerhowmany'), $options->single); + + // And finally we try again with no answer either. + $DB->delete_records('question_answers', ['question' => $question->id]); + + $question = $DB->get_record('question', ['id' => $returnedfromsave->id], '*', MUST_EXIST); + $this->qtype->get_question_options($question); + + $this->assertDebuggingCalled('Question ID '.$question->id.' was missing an options record. Using default.'); + $this->assertInstanceOf(stdClass::class, $question->options); + $options = $question->options; + $this->assertEquals($question->id, $options->questionid); + $this->assertCount(0, $options->answers); + } }