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