MDL-47494 gapselect: Fix file-permissions.
[moodle.git] / question / type / gapselect / questionbase.php
CommitLineData
0d24b17a 1<?php
0d24b17a
TH
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
0d24b17a 17/**
43df6cac
TH
18 * Definition class for embedded element in question text question. Parent of
19 * gap-select, drag and drop and possibly others.
0d24b17a 20 *
9df0480d 21 * @package qtype
0d24b17a 22 * @subpackage gapselect
9df0480d
TH
23 * @copyright 2011 The Open University
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0d24b17a
TH
25 */
26
27
b28ad86a
TH
28defined('MOODLE_INTERNAL') || die();
29
30
0d24b17a
TH
31/**
32 * Represents embedded element in question text question. Parent of drag and drop and select from
33 * drop down list and ?others?
34 *
9df0480d
TH
35 * @copyright 2011 The Open University
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0d24b17a 37 */
43df6cac 38abstract class qtype_gapselect_question_base extends question_graded_automatically_with_countback {
0d24b17a
TH
39 /** @var boolean Whether the question stems should be shuffled. */
40 public $shufflechoices;
41
42 public $correctfeedback;
43 public $partiallycorrectfeedback;
44 public $incorrectfeedback;
45
46 /** @var array of arrays. The keys are the choice group numbers. The values
47 * are arrays of qtype_gapselect_choice objects. */
48 public $choices;
49
50 /**
51 * @var array place number => group number of the places in the question
52 * text where choices can be put. Places are numbered from 1.
53 */
54 public $places;
55
56 /**
57 * @var array of strings, one longer than $places, which is achieved by
58 * indexing from 0. The bits of question text that go between the placeholders.
59 */
60 public $textfragments;
61
62 /** @var array index of the right choice for each stem. */
63 public $rightchoices;
64
65 /** @var array shuffled choice indexes. */
66 protected $choiceorder;
67
a2d6ea7a 68 public function start_attempt(question_attempt_step $step, $variant) {
0d24b17a 69 foreach ($this->choices as $group => $choices) {
6ec101ac
TH
70 $choiceorder = array_keys($choices);
71 if ($this->shufflechoices) {
72 shuffle($choiceorder);
0d24b17a 73 }
6ec101ac
TH
74 $step->set_qt_var('_choiceorder' . $group, implode(',', $choiceorder));
75 $this->set_choiceorder($group, $choiceorder);
76 }
77 }
0d24b17a 78
6ec101ac
TH
79 public function apply_attempt_state(question_attempt_step $step) {
80 foreach ($this->choices as $group => $choices) {
81 $this->set_choiceorder($group, explode(',',
82 $step->get_qt_var('_choiceorder' . $group)));
83 }
84 }
0d24b17a 85
6ec101ac
TH
86 /**
87 * Helper method used by both {@link start_attempt()} and
88 * {@link apply_attempt_state()}.
89 * @param int $group the group number.
90 * @param array $choiceorder the choices, in order.
91 */
92 protected function set_choiceorder($group, $choiceorder) {
93 foreach ($choiceorder as $key => $value) {
94 $this->choiceorder[$group][$key + 1] = $value;
0d24b17a
TH
95 }
96 }
97
98 public function get_question_summary() {
4ffe7ac4 99 $question = $this->html_to_text($this->questiontext, $this->questiontextformat);
0d24b17a
TH
100 $groups = array();
101 foreach ($this->choices as $group => $choices) {
102 $cs = array();
103 foreach ($choices as $choice) {
4ffe7ac4 104 $cs[] = html_to_text($choice->text, 0, false);
0d24b17a
TH
105 }
106 $groups[] = '[[' . $group . ']] -> {' . implode(' / ', $cs) . '}';
107 }
108 return $question . '; ' . implode('; ', $groups);
109 }
110
111 protected function get_selected_choice($group, $shuffledchoicenumber) {
112 $choiceno = $this->choiceorder[$group][$shuffledchoicenumber];
113 return $this->choices[$group][$choiceno];
114 }
115
116 public function summarise_response(array $response) {
117 $matches = array();
118 $allblank = true;
119 foreach ($this->places as $place => $group) {
120 if (array_key_exists($this->field($place), $response) &&
121 $response[$this->field($place)]) {
4ffe7ac4
TH
122 $choices[] = '{' . html_to_text($this->get_selected_choice(
123 $group, $response[$this->field($place)])->text, 0, false) . '}';
0d24b17a
TH
124 $allblank = false;
125 } else {
126 $choices[] = '{}';
127 }
128 }
129 if ($allblank) {
130 return null;
131 }
132 return implode(' ', $choices);
133 }
134
135 public function get_random_guess_score() {
136 $accum = 0;
137
138 foreach ($this->places as $placegroup) {
139 $accum += 1 / count($this->choices[$placegroup]);
140 }
141
142 return $accum / count($this->places);
143 }
144
145 public function clear_wrong_from_response(array $response) {
146 foreach ($this->places as $place => $notused) {
147 if (array_key_exists($this->field($place), $response) &&
148 $response[$this->field($place)] != $this->get_right_choice_for($place)) {
149 $response[$this->field($place)] = '0';
150 }
151 }
152 return $response;
153 }
154
155 public function get_num_parts_right(array $response) {
156 $numright = 0;
157 foreach ($this->places as $place => $notused) {
158 if (!array_key_exists($this->field($place), $response)) {
159 continue;
160 }
161 if ($response[$this->field($place)] == $this->get_right_choice_for($place)) {
162 $numright += 1;
163 }
164 }
165 return array($numright, count($this->places));
166 }
167
168 /**
462c5539 169 * @param int $key stem number
0d24b17a
TH
170 * @return string the question-type variable name.
171 */
172 public function field($place) {
173 return 'p' . $place;
174 }
175
176 public function get_expected_data() {
177 $vars = array();
178 foreach ($this->places as $place => $notused) {
179 $vars[$this->field($place)] = PARAM_INTEGER;
180 }
181 return $vars;
182 }
183
184 public function get_correct_response() {
185 $response = array();
186 foreach ($this->places as $place => $notused) {
187 $response[$this->field($place)] = $this->get_right_choice_for($place);
188 }
189 return $response;
190 }
191
192 public function get_right_choice_for($place) {
193 $group = $this->places[$place];
194 foreach ($this->choiceorder[$group] as $choicekey => $choiceid) {
195 if ($this->rightchoices[$place] == $choiceid) {
196 return $choicekey;
197 }
198 }
199 }
200
201 public function get_ordered_choices($group) {
202 $choices = array();
203 foreach ($this->choiceorder[$group] as $choicekey => $choiceid) {
204 $choices[$choicekey] = $this->choices[$group][$choiceid];
205 }
206 return $choices;
207 }
208
209 public function is_complete_response(array $response) {
210 $complete = true;
211 foreach ($this->places as $place => $notused) {
212 $complete = $complete && !empty($response[$this->field($place)]);
213 }
214 return $complete;
215 }
216
217 public function is_gradable_response(array $response) {
218 foreach ($this->places as $place => $notused) {
219 if (!empty($response[$this->field($place)])) {
220 return true;
221 }
222 }
223 return false;
224 }
225
226 public function is_same_response(array $prevresponse, array $newresponse) {
227 foreach ($this->places as $place => $notused) {
228 $fieldname = $this->field($place);
229 if (!question_utils::arrays_same_at_key_integer(
230 $prevresponse, $newresponse, $fieldname)) {
231 return false;
232 }
233 }
234 return true;
235 }
236
237 public function get_validation_error(array $response) {
238 if ($this->is_complete_response($response)) {
239 return '';
240 }
241 return get_string('pleaseputananswerineachbox', 'qtype_gapselect');
242 }
243
244 public function grade_response(array $response) {
245 list($right, $total) = $this->get_num_parts_right($response);
246 $fraction = $right / $total;
247 return array($fraction, question_state::graded_state_for_fraction($fraction));
248 }
249
250 public function compute_final_grade($responses, $totaltries) {
251 $totalscore = 0;
252 foreach ($this->places as $place => $notused) {
253 $fieldname = $this->field($place);
254
255 $lastwrongindex = -1;
256 $finallyright = false;
257 foreach ($responses as $i => $response) {
258 if (!array_key_exists($fieldname, $response) ||
259 $response[$fieldname] != $this->get_right_choice_for($place)) {
260 $lastwrongindex = $i;
261 $finallyright = false;
262 } else {
263 $finallyright = true;
264 }
265 }
266
267 if ($finallyright) {
268 $totalscore += max(0, 1 - ($lastwrongindex + 1) * $this->penalty);
269 }
270 }
271
272 return $totalscore / count($this->places);
273 }
274
275 public function classify_response(array $response) {
276 $parts = array();
277 foreach ($this->places as $place => $group) {
278 if (!array_key_exists($this->field($place), $response) ||
279 !$response[$this->field($place)]) {
280 $parts[$place] = question_classified_response::no_response();
281 continue;
282 }
283
284 $fieldname = $this->field($place);
285 $choiceno = $this->choiceorder[$group][$response[$fieldname]];
286 $choice = $this->choices[$group][$choiceno];
287 $parts[$place] = new question_classified_response(
4ffe7ac4 288 $choiceno, html_to_text($choice->text, 0, false),
f1f181f4
TH
289 ($this->get_right_choice_for($place) == $response[$fieldname])
290 / count($this->places));
0d24b17a
TH
291 }
292 return $parts;
293 }
e45d37bb
TH
294
295 public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) {
296 if ($component == 'question' && in_array($filearea,
297 array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) {
298 return $this->check_combined_feedback_file_access($qa, $options, $filearea);
299
300 } else if ($component == 'question' && $filearea == 'hint') {
301 return $this->check_hint_file_access($qa, $options, $args);
302
303 } else {
060e0294
TH
304 return parent::check_file_access($qa, $options, $component, $filearea,
305 $args, $forcedownload);
e45d37bb
TH
306 }
307 }
0d24b17a 308}