Commit | Line | Data |
---|---|---|
aeb15530 | 1 | <?php |
fe6ce234 DC |
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 | ||
6e9b6ba2 | 17 | /** |
18 | * Defines the editing form for the numerical question type. | |
19 | * | |
b04a4319 TH |
20 | * @package qtype |
21 | * @subpackage numerical | |
22 | * @copyright 2007 Jamie Pratt me@jamiep.org | |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
6e9b6ba2 | 24 | */ |
25 | ||
a17b297d TH |
26 | |
27 | defined('MOODLE_INTERNAL') || die(); | |
28 | ||
90489407 | 29 | require_once($CFG->dirroot . '/question/type/edit_question_form.php'); |
18f9b2d2 TH |
30 | require_once($CFG->dirroot . '/question/type/numerical/questiontype.php'); |
31 | ||
a17b297d | 32 | |
6e9b6ba2 | 33 | /** |
34 | * numerical editing form definition. | |
b04a4319 TH |
35 | * |
36 | * @copyright 2007 Jamie Pratt me@jamiep.org | |
37 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
6e9b6ba2 | 38 | */ |
1fa39364 | 39 | class qtype_numerical_edit_form extends question_edit_form { |
c3c65f41 TH |
40 | /** @var int we always show at least this many sets of unit fields. */ |
41 | const UNITS_MIN_REPEATS = 1; | |
42 | const UNITS_TO_ADD = 2; | |
43 | ||
90489407 | 44 | protected $ap = null; |
1fa39364 TH |
45 | |
46 | protected function definition_inner($mform) { | |
1fa39364 | 47 | $this->add_per_answer_fields($mform, get_string('answerno', 'qtype_numerical', '{no}'), |
92111e8d | 48 | question_bank::fraction_options()); |
1fa39364 | 49 | |
fdd015b7 TH |
50 | $this->add_unit_options($mform); |
51 | $this->add_unit_fields($mform); | |
1fa39364 TH |
52 | $this->add_interactive_settings(); |
53 | } | |
2aef1fe5 | 54 | |
fdd015b7 TH |
55 | protected function get_per_answer_fields($mform, $label, $gradeoptions, |
56 | &$repeatedoptions, &$answersoption) { | |
57 | $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, | |
58 | $repeatedoptions, $answersoption); | |
2aef1fe5 | 59 | |
688c8035 | 60 | $tolerance = $mform->createElement('float', 'tolerance', |
c7218aef | 61 | get_string('answererror', 'qtype_numerical'), array('size' => 15)); |
90489407 | 62 | $repeatedoptions['tolerance']['default'] = 0; |
c7218aef CC |
63 | $elements = $repeated[0]->getElements(); |
64 | $elements[0]->setSize(15); | |
65 | array_splice($elements, 1, 0, array($tolerance)); | |
66 | $repeated[0]->setElements($elements); | |
2aef1fe5 | 67 | |
68 | return $repeated; | |
69 | } | |
70 | ||
55748620 TH |
71 | protected function get_more_choices_string() { |
72 | return get_string('addmoreanswerblanks', 'qtype_numerical'); | |
73 | } | |
74 | ||
f77f47ec | 75 | /** |
1fa39364 TH |
76 | * Add the unit handling options to the form. |
77 | * @param object $mform the form being built. | |
f77f47ec | 78 | */ |
fdd015b7 | 79 | protected function add_unit_options($mform) { |
6e9b6ba2 | 80 | |
fdd015b7 TH |
81 | $mform->addElement('header', 'unithandling', |
82 | get_string('unithandling', 'qtype_numerical')); | |
1fa39364 TH |
83 | |
84 | $unitoptions = array( | |
85 | qtype_numerical::UNITNONE => get_string('onlynumerical', 'qtype_numerical'), | |
1fa39364 TH |
86 | qtype_numerical::UNITOPTIONAL => get_string('manynumerical', 'qtype_numerical'), |
87 | qtype_numerical::UNITGRADED => get_string('unitgraded', 'qtype_numerical'), | |
88 | ); | |
fdd015b7 TH |
89 | $mform->addElement('select', 'unitrole', |
90 | get_string('unithandling', 'qtype_numerical'), $unitoptions); | |
2d18de87 | 91 | $mform->setDefault('unitrole', $this->get_default_value('unitrole', qtype_numerical::UNITNONE)); |
1fa39364 TH |
92 | |
93 | $penaltygrp = array(); | |
688c8035 | 94 | $penaltygrp[] = $mform->createElement('float', 'unitpenalty', |
fdd015b7 | 95 | get_string('unitpenalty', 'qtype_numerical'), array('size' => 6)); |
2d18de87 | 96 | $mform->setDefault('unitpenalty', $this->get_default_value('unitpenalty', 0.1000000)); |
1fa39364 TH |
97 | |
98 | $unitgradingtypes = array( | |
fdd015b7 TH |
99 | qtype_numerical::UNITGRADEDOUTOFMARK => |
100 | get_string('decfractionofresponsegrade', 'qtype_numerical'), | |
101 | qtype_numerical::UNITGRADEDOUTOFMAX => | |
102 | get_string('decfractionofquestiongrade', 'qtype_numerical'), | |
1fa39364 | 103 | ); |
fdd015b7 | 104 | $penaltygrp[] = $mform->createElement('select', 'unitgradingtypes', '', $unitgradingtypes); |
2d18de87 MK |
105 | $mform->setDefault('unitgradingtypes', |
106 | $this->get_default_value('unitgradingtypes', qtype_numerical::UNITGRADEDOUTOFMARK)); | |
1fa39364 | 107 | |
fdd015b7 TH |
108 | $mform->addGroup($penaltygrp, 'penaltygrp', |
109 | get_string('unitpenalty', 'qtype_numerical'), ' ', false); | |
1fa39364 TH |
110 | $mform->addHelpButton('penaltygrp', 'unitpenalty', 'qtype_numerical'); |
111 | ||
112 | $unitinputoptions = array( | |
113 | qtype_numerical::UNITINPUT => get_string('editableunittext', 'qtype_numerical'), | |
8566369f TH |
114 | qtype_numerical::UNITRADIO => get_string('unitchoice', 'qtype_numerical'), |
115 | qtype_numerical::UNITSELECT => get_string('unitselect', 'qtype_numerical'), | |
1fa39364 | 116 | ); |
fdd015b7 TH |
117 | $mform->addElement('select', 'multichoicedisplay', |
118 | get_string('studentunitanswer', 'qtype_numerical'), $unitinputoptions); | |
2d18de87 MK |
119 | $mform->setDefault('multichoicedisplay', |
120 | $this->get_default_value('multichoicedisplay', qtype_numerical::UNITINPUT)); | |
1fa39364 | 121 | |
544de1c0 | 122 | $unitsleftoptions = array( |
1fa39364 TH |
123 | 0 => get_string('rightexample', 'qtype_numerical'), |
124 | 1 => get_string('leftexample', 'qtype_numerical') | |
125 | ); | |
fdd015b7 | 126 | $mform->addElement('select', 'unitsleft', |
544de1c0 | 127 | get_string('unitposition', 'qtype_numerical'), $unitsleftoptions); |
2d18de87 | 128 | $mform->setDefault('unitsleft', $this->get_default_value('unitsleft', 0)); |
1fa39364 | 129 | |
1fa39364 | 130 | $mform->disabledIf('penaltygrp', 'unitrole', 'eq', qtype_numerical::UNITNONE); |
1fa39364 TH |
131 | $mform->disabledIf('penaltygrp', 'unitrole', 'eq', qtype_numerical::UNITOPTIONAL); |
132 | ||
133 | $mform->disabledIf('unitsleft', 'unitrole', 'eq', qtype_numerical::UNITNONE); | |
134 | ||
135 | $mform->disabledIf('multichoicedisplay', 'unitrole', 'eq', qtype_numerical::UNITNONE); | |
1fa39364 TH |
136 | $mform->disabledIf('multichoicedisplay', 'unitrole', 'eq', qtype_numerical::UNITOPTIONAL); |
137 | } | |
138 | ||
139 | /** | |
140 | * Add the input areas for each unit. | |
141 | * @param object $mform the form being built. | |
142 | */ | |
fdd015b7 | 143 | protected function add_unit_fields($mform) { |
c4b2600d CC |
144 | $mform->addElement('header', 'unithdr', |
145 | get_string('units', 'qtype_numerical'), ''); | |
146 | ||
55748620 TH |
147 | $unitfields = array($mform->createElement('group', 'units', |
148 | get_string('unitx', 'qtype_numerical'), $this->unit_group($mform), null, false)); | |
1fa39364 | 149 | |
7d1904de FM |
150 | $repeatedoptions['unit']['disabledif'] = array('unitrole', 'eq', qtype_numerical::UNITNONE); |
151 | $repeatedoptions['unit']['type'] = PARAM_NOTAGS; | |
152 | $repeatedoptions['multiplier']['disabledif'] = array('unitrole', 'eq', qtype_numerical::UNITNONE); | |
7d1904de | 153 | |
c4b2600d | 154 | $mform->disabledIf('addunits', 'unitrole', 'eq', qtype_numerical::UNITNONE); |
1fa39364 TH |
155 | |
156 | if (isset($this->question->options->units)) { | |
c3c65f41 | 157 | $repeatsatstart = max(count($this->question->options->units), self::UNITS_MIN_REPEATS); |
1fa39364 | 158 | } else { |
c3c65f41 | 159 | $repeatsatstart = self::UNITS_MIN_REPEATS; |
1fa39364 | 160 | } |
c4b2600d | 161 | |
55748620 | 162 | $this->repeat_elements($unitfields, $repeatsatstart, $repeatedoptions, 'nounits', |
c3c65f41 | 163 | 'addunits', self::UNITS_TO_ADD, get_string('addmoreunitblanks', 'qtype_numerical', '{no}'), true); |
c4b2600d CC |
164 | |
165 | // The following strange-looking if statement is to do with when the | |
166 | // form is used to move questions between categories. See MDL-15159. | |
167 | if ($mform->elementExists('units[0]')) { | |
168 | $firstunit = $mform->getElement('units[0]'); | |
169 | $elements = $firstunit->getElements(); | |
170 | foreach ($elements as $element) { | |
171 | if ($element->getName() != 'multiplier[0]') { | |
172 | continue; | |
173 | } | |
174 | $element->freeze(); | |
175 | $element->setValue('1.0'); | |
176 | $element->setPersistantFreeze(true); | |
177 | } | |
178 | $mform->addHelpButton('units[0]', 'numericalmultiplier', 'qtype_numerical'); | |
1fa39364 | 179 | } |
6e9b6ba2 | 180 | } |
181 | ||
55748620 TH |
182 | /** |
183 | * Get the form fields needed to edit one unit. | |
184 | * @param MoodleQuickForm $mform the form being built. | |
185 | * @return array of form fields. | |
186 | */ | |
c4b2600d CC |
187 | protected function unit_group($mform) { |
188 | $grouparray = array(); | |
8766d198 | 189 | $grouparray[] = $mform->createElement('text', 'unit', get_string('unit', 'qtype_numerical'), array('size'=>10)); |
688c8035 | 190 | $grouparray[] = $mform->createElement('float', 'multiplier', |
8766d198 | 191 | get_string('multiplier', 'qtype_numerical'), array('size'=>10)); |
c4b2600d CC |
192 | |
193 | return $grouparray; | |
194 | } | |
195 | ||
c7df5006 | 196 | protected function data_preprocessing($question) { |
1fa39364 TH |
197 | $question = parent::data_preprocessing($question); |
198 | $question = $this->data_preprocessing_answers($question); | |
199 | $question = $this->data_preprocessing_hints($question); | |
200 | $question = $this->data_preprocessing_units($question); | |
fdd015b7 | 201 | $question = $this->data_preprocessing_unit_options($question); |
1fa39364 TH |
202 | return $question; |
203 | } | |
204 | ||
072db71c PS |
205 | protected function data_preprocessing_answers($question, $withanswerfiles = false) { |
206 | $question = parent::data_preprocessing_answers($question, $withanswerfiles); | |
1fa39364 TH |
207 | if (empty($question->options->answers)) { |
208 | return $question; | |
209 | } | |
210 | ||
211 | $key = 0; | |
212 | foreach ($question->options->answers as $answer) { | |
5cf69d7f | 213 | // See comment in the parent method about this hack. |
f4fe3968 | 214 | unset($this->_form->_defaultValues["tolerance[{$key}]"]); |
5cf69d7f | 215 | |
1fa39364 | 216 | $question->tolerance[$key] = $answer->tolerance; |
326bb547 SR |
217 | |
218 | if (is_numeric($question->answer[$key])) { | |
bb8d19b8 | 219 | $question->answer[$key] = format_float($question->answer[$key], -1); |
326bb547 SR |
220 | } |
221 | ||
1fa39364 TH |
222 | $key++; |
223 | } | |
4d2657c2 | 224 | |
1fa39364 TH |
225 | return $question; |
226 | } | |
227 | ||
228 | /** | |
fdd015b7 TH |
229 | * Perform the necessary preprocessing for the fields added by |
230 | * {@link add_unit_fields()}. | |
231 | * @param object $question the data being passed to the form. | |
232 | * @return object $question the modified data. | |
1fa39364 | 233 | */ |
fdd015b7 | 234 | protected function data_preprocessing_units($question) { |
1fa39364 TH |
235 | if (empty($question->options->units)) { |
236 | return $question; | |
237 | } | |
238 | ||
239 | foreach ($question->options->units as $key => $unit) { | |
240 | $question->unit[$key] = $unit->unit; | |
241 | $question->multiplier[$key] = $unit->multiplier; | |
242 | } | |
243 | ||
244 | return $question; | |
245 | } | |
246 | ||
247 | /** | |
fdd015b7 TH |
248 | * Perform the necessary preprocessing for the fields added by |
249 | * {@link add_unit_options()}. | |
250 | * @param object $question the data being passed to the form. | |
251 | * @return object $question the modified data. | |
1fa39364 | 252 | */ |
fdd015b7 | 253 | protected function data_preprocessing_unit_options($question) { |
1fa39364 TH |
254 | if (empty($question->options)) { |
255 | return $question; | |
256 | } | |
257 | ||
258 | $question->unitpenalty = $question->options->unitpenalty; | |
259 | $question->unitsleft = $question->options->unitsleft; | |
260 | ||
261 | if ($question->options->unitgradingtype) { | |
262 | $question->unitgradingtypes = $question->options->unitgradingtype; | |
263 | $question->multichoicedisplay = $question->options->showunits; | |
264 | $question->unitrole = qtype_numerical::UNITGRADED; | |
265 | } else { | |
266 | $question->unitrole = $question->options->showunits; | |
6e9b6ba2 | 267 | } |
f77f47ec | 268 | |
fe6ce234 | 269 | return $question; |
6e9b6ba2 | 270 | } |
fe6ce234 | 271 | |
c7df5006 | 272 | public function validation($data, $files) { |
fe93ba83 | 273 | $errors = parent::validation($data, $files); |
18f9b2d2 TH |
274 | $errors = $this->validate_answers($data, $errors); |
275 | $errors = $this->validate_numerical_options($data, $errors); | |
276 | return $errors; | |
277 | } | |
51e89d95 | 278 | |
18f9b2d2 TH |
279 | /** |
280 | * Validate the answers. | |
281 | * @param array $data the submitted data. | |
282 | * @param array $errors the errors array to add to. | |
283 | * @return array the updated errors array. | |
284 | */ | |
e0736817 | 285 | protected function validate_answers($data, $errors) { |
51e89d95 | 286 | // Check the answers. |
6e9b6ba2 | 287 | $answercount = 0; |
51e89d95 | 288 | $maxgrade = false; |
289 | $answers = $data['answer']; | |
290 | foreach ($answers as $key => $answer) { | |
6e9b6ba2 | 291 | $trimmedanswer = trim($answer); |
d95c82ab | 292 | if ($trimmedanswer != '') { |
6e9b6ba2 | 293 | $answercount++; |
18f9b2d2 | 294 | if (!$this->is_valid_answer($trimmedanswer, $data)) { |
11a66e2c | 295 | $errors['answeroptions[' . $key . ']'] = $this->valid_answer_message($trimmedanswer); |
d95c82ab | 296 | } |
51e89d95 | 297 | if ($data['fraction'][$key] == 1) { |
298 | $maxgrade = true; | |
299 | } | |
688c8035 | 300 | if ($answer !== '*' && $data['tolerance'][$key] === false) { |
11a66e2c | 301 | $errors['answeroptions['.$key.']'] = |
55748620 | 302 | get_string('xmustbenumeric', 'qtype_numerical', |
c4b2600d | 303 | get_string('acceptederror', 'qtype_numerical')); |
18f9b2d2 | 304 | } |
fdd015b7 TH |
305 | } else if ($data['fraction'][$key] != 0 || |
306 | !html_is_blank($data['feedback'][$key]['text'])) { | |
11a66e2c | 307 | $errors['answeroptions[' . $key . ']'] = $this->valid_answer_message($trimmedanswer); |
94a6d656 | 308 | $answercount++; |
6e9b6ba2 | 309 | } |
310 | } | |
26b26662 | 311 | if ($answercount == 0) { |
11a66e2c | 312 | $errors['answeroptions[0]'] = get_string('notenoughanswers', 'qtype_numerical'); |
6e9b6ba2 | 313 | } |
51e89d95 | 314 | if ($maxgrade == false) { |
11a66e2c | 315 | $errors['answeroptions[0]'] = get_string('fractionsnomax', 'question'); |
51e89d95 | 316 | } |
90489407 TH |
317 | |
318 | return $errors; | |
18f9b2d2 | 319 | } |
1fa39364 | 320 | |
18f9b2d2 TH |
321 | /** |
322 | * Validate a particular answer. | |
323 | * @param string $answer an answer to validate. Known to be non-blank and already trimmed. | |
324 | * @param array $data the submitted data. | |
325 | * @return bool whether this is a valid answer. | |
326 | */ | |
e0736817 | 327 | protected function is_valid_answer($answer, $data) { |
48aad79a | 328 | return $answer == '*' || qtype_numerical::is_valid_number($answer); |
18f9b2d2 | 329 | } |
1fa39364 | 330 | |
18f9b2d2 TH |
331 | /** |
332 | * @return string erre describing what an answer should be. | |
333 | */ | |
e0736817 | 334 | protected function valid_answer_message($answer) { |
18f9b2d2 | 335 | return get_string('answermustbenumberorstar', 'qtype_numerical'); |
1fa39364 TH |
336 | } |
337 | ||
338 | /** | |
18f9b2d2 TH |
339 | * Validate the answers. |
340 | * @param array $data the submitted data. | |
341 | * @param array $errors the errors array to add to. | |
342 | * @return array the updated errors array. | |
343 | */ | |
e0736817 | 344 | protected function validate_numerical_options($data, $errors) { |
1fa39364 | 345 | if ($data['unitrole'] != qtype_numerical::UNITNONE && trim($data['unit'][0]) == '') { |
c4b2600d | 346 | $errors['units[0]'] = get_string('unitonerequired', 'qtype_numerical'); |
1fa39364 TH |
347 | } |
348 | ||
c4b2600d | 349 | if (empty($data['unit']) || $data['unitrole'] == qtype_numerical::UNITNONE) { |
1fa39364 TH |
350 | return $errors; |
351 | } | |
352 | ||
353 | // Basic unit validation. | |
354 | foreach ($data['unit'] as $key => $unit) { | |
355 | if (is_numeric($unit)) { | |
c4b2600d | 356 | $errors['units[' . $key . ']'] = |
55748620 TH |
357 | get_string('xmustnotbenumeric', 'qtype_numerical', |
358 | get_string('unit', 'qtype_numerical')); | |
1fa39364 TH |
359 | } |
360 | ||
361 | $trimmedunit = trim($unit); | |
362 | if (empty($trimmedunit)) { | |
363 | continue; | |
364 | } | |
365 | ||
366 | $trimmedmultiplier = trim($data['multiplier'][$key]); | |
367 | if (empty($trimmedmultiplier)) { | |
c4b2600d | 368 | $errors['units[' . $key . ']'] = |
55748620 | 369 | get_string('youmustenteramultiplierhere', 'qtype_numerical'); |
688c8035 | 370 | } else if ($trimmedmultiplier === false) { |
c4b2600d | 371 | $errors['units[' . $key . ']'] = |
55748620 TH |
372 | get_string('xmustbenumeric', 'qtype_numerical', |
373 | get_string('multiplier', 'qtype_numerical')); | |
1fa39364 TH |
374 | } |
375 | } | |
376 | ||
377 | // Check for repeated units. | |
378 | $alreadyseenunits = array(); | |
379 | foreach ($data['unit'] as $key => $unit) { | |
380 | $trimmedunit = trim($unit); | |
381 | if ($trimmedunit == '') { | |
382 | continue; | |
383 | } | |
384 | ||
385 | if (in_array($trimmedunit, $alreadyseenunits)) { | |
c4b2600d | 386 | $errors['units[' . $key . ']'] = |
1fa39364 TH |
387 | get_string('errorrepeatedunit', 'qtype_numerical'); |
388 | } else { | |
389 | $alreadyseenunits[] = $trimmedunit; | |
390 | } | |
391 | } | |
51e89d95 | 392 | |
6e9b6ba2 | 393 | return $errors; |
394 | } | |
fe6ce234 | 395 | |
c7df5006 | 396 | public function qtype() { |
6e9b6ba2 | 397 | return 'numerical'; |
398 | } | |
399 | } |