From 53eee12a7a9787baa6b9c1700a8cae54222dee56 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Thu, 20 Aug 2015 19:12:02 +0100 Subject: [PATCH] MDL-51147 qtype_match: stats should cope even if Q edited badly The numbers become pretty meaningless, but we need to avoid fatal errors. --- question/type/match/question.php | 23 +++++++---- question/type/match/tests/question_test.php | 44 +++++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/question/type/match/question.php b/question/type/match/question.php index 671047405c8..28c20a22a0a 100644 --- a/question/type/match/question.php +++ b/question/type/match/question.php @@ -134,25 +134,34 @@ class qtype_match_question extends question_graded_automatically_with_countback } public function classify_response(array $response) { - $selectedchoices = array(); + $selectedchoicekeys = array(); foreach ($this->stemorder as $key => $stemid) { if (array_key_exists($this->field($key), $response) && $response[$this->field($key)]) { - $selectedchoices[$stemid] = $this->choiceorder[$response[$this->field($key)]]; + $selectedchoicekeys[$stemid] = $this->choiceorder[$response[$this->field($key)]]; } else { - $selectedchoices[$stemid] = 0; + $selectedchoicekeys[$stemid] = 0; } } $parts = array(); foreach ($this->stems as $stemid => $stem) { - if (empty($selectedchoices[$stemid])) { + if ($this->right[$stemid] == 0 || !isset($selectedchoicekeys[$stemid])) { + // Choice for a deleted subquestion, ignore. (See apply_attempt_state.) + continue; + } + $selectedchoicekey = $selectedchoicekeys[$stemid]; + if (empty($selectedchoicekey)) { $parts[$stemid] = question_classified_response::no_response(); continue; } - $choice = $this->choices[$selectedchoices[$stemid]]; + $choice = $this->choices[$selectedchoicekey]; + if ($choice == get_string('deletedchoice', 'qtype_match')) { + // Deleted choice, ignore. (See apply_attempt_state.) + continue; + } $parts[$stemid] = new question_classified_response( - $selectedchoices[$stemid], $choice, - ($selectedchoices[$stemid] == $this->right[$stemid]) / count($this->stems)); + $selectedchoicekey, $choice, + ($selectedchoicekey == $this->right[$stemid]) / count($this->stems)); } return $parts; } diff --git a/question/type/match/tests/question_test.php b/question/type/match/tests/question_test.php index be6a5a7dac6..4e83431d2c3 100644 --- a/question/type/match/tests/question_test.php +++ b/question/type/match/tests/question_test.php @@ -181,6 +181,50 @@ class qtype_match_question_test extends advanced_testcase { ), $match->classify_response($response)); } + public function test_classify_response_choice_deleted_after_attempt() { + $match = test_question_maker::make_a_matching_question(); + $firststep = new question_attempt_step(); + + $match->start_attempt($firststep, 1); + $response = $match->prepare_simulated_post_data(array( + 'Dog' => 'Amphibian', 'Frog' => 'Insect', 'Toad' => '', 'Cat' => 'Mammal')); + + $match = test_question_maker::make_a_matching_question(); + unset($match->stems[4]); + unset($match->stemsformat[4]); + unset($match->right[4]); + $match->apply_attempt_state($firststep); + + $this->assertEquals(array( + 1 => new question_classified_response(2, 'Amphibian', 0), + 2 => new question_classified_response(3, 'Insect', 0), + 3 => question_classified_response::no_response(), + ), $match->classify_response($response)); + } + + public function test_classify_response_choice_added_after_attempt() { + $match = test_question_maker::make_a_matching_question(); + $firststep = new question_attempt_step(); + + $match->start_attempt($firststep, 1); + $response = $match->prepare_simulated_post_data(array( + 'Dog' => 'Amphibian', 'Frog' => 'Insect', 'Toad' => '', 'Cat' => 'Mammal')); + + $match = test_question_maker::make_a_matching_question(); + $match->stems[5] = "Snake"; + $match->stemsformat[5] = FORMAT_HTML; + $match->choices[5] = "Reptile"; + $match->right[5] = 5; + $match->apply_attempt_state($firststep); + + $this->assertEquals(array( + 1 => new question_classified_response(2, 'Amphibian', 0), + 2 => new question_classified_response(3, 'Insect', 0), + 3 => question_classified_response::no_response(), + 4 => new question_classified_response(1, 'Mammal', 0.20), + ), $match->classify_response($response)); + } + public function test_prepare_simulated_post_data() { $m = test_question_maker::make_a_matching_question(); $m->start_attempt(new question_attempt_step(), 1);