MDL-27413 start conversion of the multianswer qtype to the new question engine.
[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
71 // TODO get_question_summary ???
72
73 public function get_min_fraction() {
74 $fractionsum = 0;
75 $fractionmax = 0;
76 foreach ($this->subquestions as $i => $subq) {
77 $fractionmax += $subq->defaultmark;
78 $fractionsum += $subq->defaultmark * $subq->get_min_fraction();
79 }
80 return $fractionsum / $fractionmax;
81 }
82
83 public function get_expected_data() {
84 $expected = array();
85 foreach ($this->subquestions as $i => $subq) {
86 $substep = $this->get_substep(null, $i);
87 foreach ($subq->get_expected_data() as $name => $type) {
88 $expected[$substep->add_prefix($name)] = $type;
89 }
90 }
91 return $expected;
92 }
93
94 public function get_correct_response() {
95 $right = array();
96 foreach ($this->subquestions as $i => $subq) {
97 $substep = $this->get_substep(null, $i);
98 foreach ($subq->get_expected_data() as $name => $type) {
99 $right[$substep->add_prefix($name)] = $type;
100 }
101 }
102 return $right;
103 }
104
105 public function is_complete_response(array $response) {
106 foreach ($this->subquestions as $i => $subq) {
107 $substep = $this->get_substep(null, $i);
108 if (!$subq->is_complete_response($substep->filter_array($response))) {
109 return false;
110 }
111 }
112 return true;
113 }
114
115 public function is_gradable_response(array $response) {
116 foreach ($this->subquestions as $i => $subq) {
117 $substep = $this->get_substep(null, $i);
118 if ($subq->is_gradable_response($substep->filter_array($response))) {
119 return true;
120 }
121 }
122 return false;
123 }
124
125 public function is_same_response(array $prevresponse, array $newresponse) {
126 foreach ($this->subquestions as $i => $subq) {
127 $substep = $this->get_substep(null, $i);
128 if (!$subq->is_same_response($substep->filter_array($prevresponse),
129 $substep->filter_array($newresponse))) {
130 return false;
131 }
132 }
133 return true;
134 }
135
136 public function get_validation_error(array $response) {
137 $errors = array();
138 foreach ($this->subquestions as $i => $subq) {
139 $substep = $this->get_substep(null, $i);
140 $errors[] = $subq->get_validation_error($substep->filter_array($response));
141 }
142 return implode('<br />', $errors);
143 }
144
145 /**
146 * Used by grade_response to combine the states of the subquestions.
147 * The combined state is accumulates in $overallstate. That will be right
148 * if all the separate states are right; and wrong if all the separate states
149 * are wrong, otherwise, it will be partially right.
150 * @param question_state $overallstate the result so far.
151 * @param question_state $newstate the new state to add to the combination.
152 * @return question_state the new combined state.
153 */
154 protected function combine_states($overallstate, $newstate) {
155 if (is_null($overallstate)) {
156 return $newstate;
157 } else if ($overallstate == question_state::$gaveup &&
158 $newstate == question_state::$gaveup) {
159 return question_state::$gaveup;
160 } else if ($overallstate == question_state::$gaveup &&
161 $newstate == question_state::$gradedwrong) {
162 return question_state::$gradedwrong;
163 } else if ($overallstate == question_state::$gradedwrong &&
164 $newstate == question_state::$gaveup) {
165 return question_state::$gradedwrong;
166 } else if ($overallstate == question_state::$gradedwrong &&
167 $newstate == question_state::$gradedwrong) {
168 return question_state::$gradedwrong;
169 } else if ($overallstate == question_state::$gradedright &&
170 $newstate == question_state::$gradedright) {
171 return question_state::$gradedright;
172 } else {
173 return question_state::$gradedpartial;
174 }
175 }
176
177 public function grade_response(array $response) {
178 $overallstate = null;
179 $fractionsum = 0;
180 $fractionmax = 0;
181 foreach ($this->subquestions as $i => $subq) {
182 $fractionmax += $subq->defaultmark;
183 $substep = $this->get_substep(null, $i);
184 $subresp = $substep->filter_array($response);
185 if (!$subq->is_gradable_response($subresp)) {
186 $overallstate = $this->combine_states($overallstate, question_state::$gaveup);
187 } else {
188 list($subfraction, $newstate) = $subq->grade_response($subresp);
189 $fractionsum += $subfraction * $subq->defaultmark;
190 $overallstate = $this->combine_states($overallstate, $newstate);
191 }
192 }
193 return array($fractionsum / $fractionmax, $overallstate);
194 }
195
196 public function summarise_response(array $response) {
197 $summary = array();
198 foreach ($this->subquestions as $i => $subq) {
199 $substep = $this->get_substep(null, $i);
200 $a = new stdClass();
201 $a->i = $i;
202 $a->response = $subq->summarise_response($substep->filter_array($response));
203 $summary[] = get_string('subqresponse', 'qtype_multianswer', $a);
204 }
205
206 return implode('; ', $summary);
207 }
208
209 public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) {
210 // TODO
211 }
212}