Merge branch 'MDL-29701-master' of https://github.com/phuchau1509/moodle
[moodle.git] / question / type / numerical / edit_numerical_form.php
blobfd8eefdd6120400fe659c80595506b4d76b5efdb
1 <?php
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.
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/>.
17 /**
18 * Defines the editing form for the numerical question type.
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
27 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->dirroot . '/question/type/edit_question_form.php');
30 require_once($CFG->dirroot . '/question/type/numerical/questiontype.php');
33 /**
34 * numerical editing form definition.
36 * @copyright 2007 Jamie Pratt me@jamiep.org
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 class qtype_numerical_edit_form extends question_edit_form {
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;
44 protected $ap = null;
46 protected function definition_inner($mform) {
47 $this->add_per_answer_fields($mform, get_string('answerno', 'qtype_numerical', '{no}'),
48 question_bank::fraction_options());
50 $this->add_unit_options($mform);
51 $this->add_unit_fields($mform);
52 $this->add_interactive_settings();
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);
60 $tolerance = $mform->createElement('text', 'tolerance',
61 get_string('answererror', 'qtype_numerical'), array('size' => 15));
62 $repeatedoptions['tolerance']['type'] = PARAM_FLOAT;
63 $repeatedoptions['tolerance']['default'] = 0;
64 $elements = $repeated[0]->getElements();
65 $elements[0]->setSize(15);
66 array_splice($elements, 1, 0, array($tolerance));
67 $repeated[0]->setElements($elements);
69 return $repeated;
72 protected function get_more_choices_string() {
73 return get_string('addmoreanswerblanks', 'qtype_numerical');
76 /**
77 * Add the unit handling options to the form.
78 * @param object $mform the form being built.
80 protected function add_unit_options($mform) {
82 $mform->addElement('header', 'unithandling',
83 get_string('unithandling', 'qtype_numerical'));
85 $unitoptions = array(
86 qtype_numerical::UNITNONE => get_string('onlynumerical', 'qtype_numerical'),
87 qtype_numerical::UNITOPTIONAL => get_string('manynumerical', 'qtype_numerical'),
88 qtype_numerical::UNITGRADED => get_string('unitgraded', 'qtype_numerical'),
90 $mform->addElement('select', 'unitrole',
91 get_string('unithandling', 'qtype_numerical'), $unitoptions);
93 $penaltygrp = array();
94 $penaltygrp[] = $mform->createElement('text', 'unitpenalty',
95 get_string('unitpenalty', 'qtype_numerical'), array('size' => 6));
96 $mform->setType('unitpenalty', PARAM_FLOAT);
97 $mform->setDefault('unitpenalty', 0.1000000);
99 $unitgradingtypes = array(
100 qtype_numerical::UNITGRADEDOUTOFMARK =>
101 get_string('decfractionofresponsegrade', 'qtype_numerical'),
102 qtype_numerical::UNITGRADEDOUTOFMAX =>
103 get_string('decfractionofquestiongrade', 'qtype_numerical'),
105 $penaltygrp[] = $mform->createElement('select', 'unitgradingtypes', '', $unitgradingtypes);
106 $mform->setDefault('unitgradingtypes', 1);
108 $mform->addGroup($penaltygrp, 'penaltygrp',
109 get_string('unitpenalty', 'qtype_numerical'), ' ', false);
110 $mform->addHelpButton('penaltygrp', 'unitpenalty', 'qtype_numerical');
112 $unitinputoptions = array(
113 qtype_numerical::UNITINPUT => get_string('editableunittext', 'qtype_numerical'),
114 qtype_numerical::UNITRADIO => get_string('unitchoice', 'qtype_numerical'),
115 qtype_numerical::UNITSELECT => get_string('unitselect', 'qtype_numerical'),
117 $mform->addElement('select', 'multichoicedisplay',
118 get_string('studentunitanswer', 'qtype_numerical'), $unitinputoptions);
120 $unitsleftoptions = array(
121 0 => get_string('rightexample', 'qtype_numerical'),
122 1 => get_string('leftexample', 'qtype_numerical')
124 $mform->addElement('select', 'unitsleft',
125 get_string('unitposition', 'qtype_numerical'), $unitsleftoptions);
126 $mform->setDefault('unitsleft', 0);
128 $mform->disabledIf('penaltygrp', 'unitrole', 'eq', qtype_numerical::UNITNONE);
129 $mform->disabledIf('penaltygrp', 'unitrole', 'eq', qtype_numerical::UNITOPTIONAL);
131 $mform->disabledIf('unitsleft', 'unitrole', 'eq', qtype_numerical::UNITNONE);
133 $mform->disabledIf('multichoicedisplay', 'unitrole', 'eq', qtype_numerical::UNITNONE);
134 $mform->disabledIf('multichoicedisplay', 'unitrole', 'eq', qtype_numerical::UNITOPTIONAL);
138 * Add the input areas for each unit.
139 * @param object $mform the form being built.
141 protected function add_unit_fields($mform) {
142 $mform->addElement('header', 'unithdr',
143 get_string('units', 'qtype_numerical'), '');
145 $unitfields = array($mform->createElement('group', 'units',
146 get_string('unitx', 'qtype_numerical'), $this->unit_group($mform), null, false));
148 $repeatedoptions['unit']['disabledif'] = array('unitrole', 'eq', qtype_numerical::UNITNONE);
149 $repeatedoptions['unit']['type'] = PARAM_NOTAGS;
150 $repeatedoptions['multiplier']['disabledif'] = array('unitrole', 'eq', qtype_numerical::UNITNONE);
151 $repeatedoptions['multiplier']['type'] = PARAM_NUMBER;
153 $mform->disabledIf('addunits', 'unitrole', 'eq', qtype_numerical::UNITNONE);
155 if (isset($this->question->options->units)) {
156 $repeatsatstart = max(count($this->question->options->units), self::UNITS_MIN_REPEATS);
157 } else {
158 $repeatsatstart = self::UNITS_MIN_REPEATS;
161 $this->repeat_elements($unitfields, $repeatsatstart, $repeatedoptions, 'nounits',
162 'addunits', self::UNITS_TO_ADD, get_string('addmoreunitblanks', 'qtype_numerical', '{no}'), true);
164 // The following strange-looking if statement is to do with when the
165 // form is used to move questions between categories. See MDL-15159.
166 if ($mform->elementExists('units[0]')) {
167 $firstunit = $mform->getElement('units[0]');
168 $elements = $firstunit->getElements();
169 foreach ($elements as $element) {
170 if ($element->getName() != 'multiplier[0]') {
171 continue;
173 $element->freeze();
174 $element->setValue('1.0');
175 $element->setPersistantFreeze(true);
177 $mform->addHelpButton('units[0]', 'numericalmultiplier', 'qtype_numerical');
182 * Get the form fields needed to edit one unit.
183 * @param MoodleQuickForm $mform the form being built.
184 * @return array of form fields.
186 protected function unit_group($mform) {
187 $grouparray = array();
188 $grouparray[] = $mform->createElement('text', 'unit', get_string('unit', 'qtype_numerical'), array('size'=>10));
189 $grouparray[] = $mform->createElement('text', 'multiplier',
190 get_string('multiplier', 'qtype_numerical'), array('size'=>10));
192 return $grouparray;
195 protected function data_preprocessing($question) {
196 $question = parent::data_preprocessing($question);
197 $question = $this->data_preprocessing_answers($question);
198 $question = $this->data_preprocessing_hints($question);
199 $question = $this->data_preprocessing_units($question);
200 $question = $this->data_preprocessing_unit_options($question);
201 return $question;
204 protected function data_preprocessing_answers($question, $withanswerfiles = false) {
205 $question = parent::data_preprocessing_answers($question, $withanswerfiles);
206 if (empty($question->options->answers)) {
207 return $question;
210 $key = 0;
211 foreach ($question->options->answers as $answer) {
212 // See comment in the parent method about this hack.
213 unset($this->_form->_defaultValues["tolerance[{$key}]"]);
215 $question->tolerance[$key] = $answer->tolerance;
216 $key++;
219 return $question;
223 * Perform the necessary preprocessing for the fields added by
224 * {@link add_unit_fields()}.
225 * @param object $question the data being passed to the form.
226 * @return object $question the modified data.
228 protected function data_preprocessing_units($question) {
229 if (empty($question->options->units)) {
230 return $question;
233 foreach ($question->options->units as $key => $unit) {
234 $question->unit[$key] = $unit->unit;
235 $question->multiplier[$key] = $unit->multiplier;
238 return $question;
242 * Perform the necessary preprocessing for the fields added by
243 * {@link add_unit_options()}.
244 * @param object $question the data being passed to the form.
245 * @return object $question the modified data.
247 protected function data_preprocessing_unit_options($question) {
248 if (empty($question->options)) {
249 return $question;
252 $question->unitpenalty = $question->options->unitpenalty;
253 $question->unitsleft = $question->options->unitsleft;
255 if ($question->options->unitgradingtype) {
256 $question->unitgradingtypes = $question->options->unitgradingtype;
257 $question->multichoicedisplay = $question->options->showunits;
258 $question->unitrole = qtype_numerical::UNITGRADED;
259 } else {
260 $question->unitrole = $question->options->showunits;
263 return $question;
266 public function validation($data, $files) {
267 $errors = parent::validation($data, $files);
268 $errors = $this->validate_answers($data, $errors);
269 $errors = $this->validate_numerical_options($data, $errors);
270 return $errors;
274 * Validate the answers.
275 * @param array $data the submitted data.
276 * @param array $errors the errors array to add to.
277 * @return array the updated errors array.
279 protected function validate_answers($data, $errors) {
280 // Check the answers.
281 $answercount = 0;
282 $maxgrade = false;
283 $answers = $data['answer'];
284 foreach ($answers as $key => $answer) {
285 $trimmedanswer = trim($answer);
286 if ($trimmedanswer != '') {
287 $answercount++;
288 if (!$this->is_valid_answer($trimmedanswer, $data)) {
289 $errors['answeroptions[' . $key . ']'] = $this->valid_answer_message($trimmedanswer);
291 if ($data['fraction'][$key] == 1) {
292 $maxgrade = true;
294 if ($answer !== '*' && !is_numeric($data['tolerance'][$key])) {
295 $errors['answeroptions['.$key.']'] =
296 get_string('xmustbenumeric', 'qtype_numerical',
297 get_string('acceptederror', 'qtype_numerical'));
299 } else if ($data['fraction'][$key] != 0 ||
300 !html_is_blank($data['feedback'][$key]['text'])) {
301 $errors['answeroptions[' . $key . ']'] = $this->valid_answer_message($trimmedanswer);
302 $answercount++;
305 if ($answercount == 0) {
306 $errors['answeroptions[0]'] = get_string('notenoughanswers', 'qtype_numerical');
308 if ($maxgrade == false) {
309 $errors['answeroptions[0]'] = get_string('fractionsnomax', 'question');
312 return $errors;
316 * Validate a particular answer.
317 * @param string $answer an answer to validate. Known to be non-blank and already trimmed.
318 * @param array $data the submitted data.
319 * @return bool whether this is a valid answer.
321 protected function is_valid_answer($answer, $data) {
322 return $answer == '*' || $this->is_valid_number($answer);
326 * Validate that a string is a nubmer formatted correctly for the current locale.
327 * @param string $x a string
328 * @return bool whether $x is a number that the numerical question type can interpret.
330 protected function is_valid_number($x) {
331 if (is_null($this->ap)) {
332 $this->ap = new qtype_numerical_answer_processor(array());
335 list($value, $unit) = $this->ap->apply_units($x);
337 return !is_null($value) && !$unit;
341 * @return string erre describing what an answer should be.
343 protected function valid_answer_message($answer) {
344 return get_string('answermustbenumberorstar', 'qtype_numerical');
348 * Validate the answers.
349 * @param array $data the submitted data.
350 * @param array $errors the errors array to add to.
351 * @return array the updated errors array.
353 protected function validate_numerical_options($data, $errors) {
354 if ($data['unitrole'] != qtype_numerical::UNITNONE && trim($data['unit'][0]) == '') {
355 $errors['units[0]'] = get_string('unitonerequired', 'qtype_numerical');
358 if (empty($data['unit']) || $data['unitrole'] == qtype_numerical::UNITNONE) {
359 return $errors;
362 // Basic unit validation.
363 foreach ($data['unit'] as $key => $unit) {
364 if (is_numeric($unit)) {
365 $errors['units[' . $key . ']'] =
366 get_string('xmustnotbenumeric', 'qtype_numerical',
367 get_string('unit', 'qtype_numerical'));
370 $trimmedunit = trim($unit);
371 if (empty($trimmedunit)) {
372 continue;
375 $trimmedmultiplier = trim($data['multiplier'][$key]);
376 if (empty($trimmedmultiplier)) {
377 $errors['units[' . $key . ']'] =
378 get_string('youmustenteramultiplierhere', 'qtype_numerical');
379 } else if (!is_numeric($trimmedmultiplier)) {
380 $errors['units[' . $key . ']'] =
381 get_string('xmustbenumeric', 'qtype_numerical',
382 get_string('multiplier', 'qtype_numerical'));
386 // Check for repeated units.
387 $alreadyseenunits = array();
388 foreach ($data['unit'] as $key => $unit) {
389 $trimmedunit = trim($unit);
390 if ($trimmedunit == '') {
391 continue;
394 if (in_array($trimmedunit, $alreadyseenunits)) {
395 $errors['units[' . $key . ']'] =
396 get_string('errorrepeatedunit', 'qtype_numerical');
397 } else {
398 $alreadyseenunits[] = $trimmedunit;
402 return $errors;
405 public function qtype() {
406 return 'numerical';