MDL-71205 Default options for qtype_numerical
[moodle.git] / question / type / numerical / edit_numerical_form.php
CommitLineData
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
27defined('MOODLE_INTERNAL') || die();
28
90489407 29require_once($CFG->dirroot . '/question/type/edit_question_form.php');
18f9b2d2
TH
30require_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 39class 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}