MDL-20636 Fix potential bug with qtype_shortanswer correct answer display.
[moodle.git] / question / type / multianswer / question.php
CommitLineData
ab50232b
TH
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18
19/**
20 * Multianswer question definition class.
21 *
22 * @package qtype
23 * @subpackage multianswer
24 * @copyright 2010 Pierre Pichet
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 */
27
28require_once($CFG->dirroot . '/question/type/shortanswer/question.php');
29require_once($CFG->dirroot . '/question/type/numerical/question.php');
30require_once($CFG->dirroot . '/question/type/multichoice/question.php');
31
32
33/**
34 * Represents a multianswer question.
35 *
36 * A multi-answer question is made of of several subquestions of various types.
37 * You can think of it as an application of the composite pattern to qusetion
38 * types.
39 *
40 * @copyright 2010 Pierre Pichet
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 */
43class qtype_multianswer_question extends question_graded_automatically {
44 /** @var array of question_graded_automatically. */
45 public $subquestions = array();
46
47 /**
48 * Get a question_attempt_step_subquestion_adapter
49 * @param question_attempt_step $step the step to adapt.
50 * @param int $i the subquestion index.
51 * @return question_attempt_step_subquestion_adapter.
52 */
53 protected function get_substep($step, $i) {
54 return new question_attempt_step_subquestion_adapter($step, 'sub' . $i . '_');
55 }
56
57 public function start_attempt(question_attempt_step $step) {
58 foreach ($this->subquestions as $i => $subq) {
59 $subq->start_attempt($this->get_substep($step, $i));
60
61 }
62 }
63
64 public function apply_attempt_state(question_attempt_step $step) {
65 foreach ($this->subquestions as $i => $subq) {
66 $subq->apply_attempt_state($this->get_substep($step, $i));
67
68 }
69 }
70
fa6c8620
TH
71 public function get_question_summary() {
72 $summary = $this->html_to_text($this->questiontext, $this->questiontextformat);
73 foreach ($this->subquestions as $i => $subq) {
74 switch ($subq->qtype->name()) {
75 case 'multichoice':
76 $choices = array();
77 $dummyqa = new question_attempt($subq, $this->contextid);
78 foreach ($subq->get_order($dummyqa) as $ansid) {
79 $choices[] = $this->html_to_text($subq->answers[$ansid]->answer,
80 $subq->answers[$ansid]->answerformat);
81 }
82 $answerbit = '{' . implode('; ', $choices) . '}';
83 break;
84 case 'numerical':
85 case 'shortanswer':
86 $answerbit = '_____';
87 break;
88 default:
89 $answerbit = '{ERR unknown sub-question type}';
90 }
91 $summary = str_replace('{#' . $i . '}', $answerbit, $summary);
92 }
93 return $summary;
94 }
ab50232b
TH
95
96 public function get_min_fraction() {
97 $fractionsum = 0;
98 $fractionmax = 0;
99 foreach ($this->subquestions as $i => $subq) {
100 $fractionmax += $subq->defaultmark;
101 $fractionsum += $subq->defaultmark * $subq->get_min_fraction();
102 }
103 return $fractionsum / $fractionmax;
104 }
105
106 public function get_expected_data() {
107 $expected = array();
108 foreach ($this->subquestions as $i => $subq) {
109 $substep = $this->get_substep(null, $i);
110 foreach ($subq->get_expected_data() as $name => $type) {
111 $expected[$substep->add_prefix($name)] = $type;
112 }
113 }
114 return $expected;
115 }
116
117 public function get_correct_response() {
118 $right = array();
119 foreach ($this->subquestions as $i => $subq) {
120 $substep = $this->get_substep(null, $i);
fa6c8620 121 foreach ($subq->get_correct_response() as $name => $type) {
ab50232b
TH
122 $right[$substep->add_prefix($name)] = $type;
123 }
124 }
125 return $right;
126 }
127
128 public function is_complete_response(array $response) {
129 foreach ($this->subquestions as $i => $subq) {
130 $substep = $this->get_substep(null, $i);
131 if (!$subq->is_complete_response($substep->filter_array($response))) {
132 return false;
133 }
134 }
135 return true;
136 }
137
138 public function is_gradable_response(array $response) {
139 foreach ($this->subquestions as $i => $subq) {
140 $substep = $this->get_substep(null, $i);
141 if ($subq->is_gradable_response($substep->filter_array($response))) {
142 return true;
143 }
144 }
145 return false;
146 }
147
148 public function is_same_response(array $prevresponse, array $newresponse) {
149 foreach ($this->subquestions as $i => $subq) {
150 $substep = $this->get_substep(null, $i);
151 if (!$subq->is_same_response($substep->filter_array($prevresponse),
152 $substep->filter_array($newresponse))) {
153 return false;
154 }
155 }
156 return true;
157 }
158
159 public function get_validation_error(array $response) {
160 $errors = array();
161 foreach ($this->subquestions as $i => $subq) {
162 $substep = $this->get_substep(null, $i);
163 $errors[] = $subq->get_validation_error($substep->filter_array($response));
164 }
165 return implode('<br />', $errors);
166 }
167
168 /**
169 * Used by grade_response to combine the states of the subquestions.
170 * The combined state is accumulates in $overallstate. That will be right
171 * if all the separate states are right; and wrong if all the separate states
172 * are wrong, otherwise, it will be partially right.
173 * @param question_state $overallstate the result so far.
174 * @param question_state $newstate the new state to add to the combination.
175 * @return question_state the new combined state.
176 */
177 protected function combine_states($overallstate, $newstate) {
178 if (is_null($overallstate)) {
179 return $newstate;
180 } else if ($overallstate == question_state::$gaveup &&
181 $newstate == question_state::$gaveup) {
182 return question_state::$gaveup;
183 } else if ($overallstate == question_state::$gaveup &&
184 $newstate == question_state::$gradedwrong) {
185 return question_state::$gradedwrong;
186 } else if ($overallstate == question_state::$gradedwrong &&
187 $newstate == question_state::$gaveup) {
188 return question_state::$gradedwrong;
189 } else if ($overallstate == question_state::$gradedwrong &&
190 $newstate == question_state::$gradedwrong) {
191 return question_state::$gradedwrong;
192 } else if ($overallstate == question_state::$gradedright &&
193 $newstate == question_state::$gradedright) {
194 return question_state::$gradedright;
195 } else {
196 return question_state::$gradedpartial;
197 }
198 }
199
200 public function grade_response(array $response) {
201 $overallstate = null;
202 $fractionsum = 0;
203 $fractionmax = 0;
204 foreach ($this->subquestions as $i => $subq) {
205 $fractionmax += $subq->defaultmark;
206 $substep = $this->get_substep(null, $i);
207 $subresp = $substep->filter_array($response);
208 if (!$subq->is_gradable_response($subresp)) {
209 $overallstate = $this->combine_states($overallstate, question_state::$gaveup);
210 } else {
211 list($subfraction, $newstate) = $subq->grade_response($subresp);
212 $fractionsum += $subfraction * $subq->defaultmark;
213 $overallstate = $this->combine_states($overallstate, $newstate);
214 }
215 }
216 return array($fractionsum / $fractionmax, $overallstate);
217 }
218
219 public function summarise_response(array $response) {
220 $summary = array();
221 foreach ($this->subquestions as $i => $subq) {
222 $substep = $this->get_substep(null, $i);
223 $a = new stdClass();
224 $a->i = $i;
225 $a->response = $subq->summarise_response($substep->filter_array($response));
226 $summary[] = get_string('subqresponse', 'qtype_multianswer', $a);
227 }
228
229 return implode('; ', $summary);
230 }
231
232 public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) {
233 // TODO
234 }
235}