Merge branch 'MDL-70160-function-cache-310' of https://github.com/Peterburnett/moodle...
[moodle.git] / question / type / calculatedmulti / edit_calculatedmulti_form.php
CommitLineData
2d279432 1<?php
d3603157
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
2d279432 17/**
d3603157 18 * Defines the editing form for calculated multiple-choice questions.
2d279432 19 *
b04a4319 20 * @package qtype
d3603157 21 * @subpackage calculatedmulti
b04a4319
TH
22 * @copyright 2007 Jamie Pratt me@jamiep.org
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2d279432
PP
24 */
25
d3603157 26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
29
2d279432 30/**
d3603157 31 * Calculated multiple-choice question editing form.
b04a4319
TH
32 *
33 * @copyright 2007 Jamie Pratt me@jamiep.org
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2d279432 35 */
29b68914 36class qtype_calculatedmulti_edit_form extends question_edit_form {
2d279432
PP
37 /**
38 * Handle to the question type for this question.
39 *
40 * @var question_calculatedmulti_qtype
41 */
fe6ce234 42 public $qtypeobj;
29b68914 43 public $questiondisplay;
d90b016b 44 public $initialname = '';
29b68914 45 public $reload = false;
cdece95e 46
29b68914
TH
47 public function __construct($submiturl, $question, $category,
48 $contexts, $formeditable = true) {
2d279432 49 $this->question = $question;
cdece95e 50 $this->qtypeobj = question_bank::get_qtype('calculatedmulti');
7d087744 51 $this->reload = optional_param('reload', false, PARAM_BOOL);
29b68914 52 if (!$this->reload) {
c7218aef 53 // Use database data as this is first pass.
29b68914 54 if (isset($this->question->id)) {
c7218aef 55 // Remove prefix #{..}# if exists.
29b68914 56 $this->initialname = $question->name;
75aa674b
TH
57 $question->name = question_bank::get_qtype('calculated')
58 ->clean_technical_prefix_from_question_name($question->name);
d90b016b 59 }
fe6ce234 60 }
29b68914 61 parent::__construct($submiturl, $question, $category, $contexts, $formeditable);
2d279432
PP
62 }
63
8011be18
TH
64 protected function can_preview() {
65 return false; // Generally not possible for calculated multi-choice questions on this page.
66 }
67
29b68914
TH
68 public function get_per_answer_fields($mform, $label, $gradeoptions,
69 &$repeatedoptions, &$answersoption) {
fe6ce234 70 $repeated = array();
c7218aef
CC
71 $answeroptions = array();
72 $answeroptions[] = $mform->createElement('text', 'answer',
73 $label, array('size' => 50));
74 $answeroptions[] = $mform->createElement('select', 'fraction',
29b68914 75 get_string('grade'), $gradeoptions);
c7218aef
CC
76 $repeated[] = $mform->createElement('group', 'answeroptions',
77 $label, $answeroptions, null, false);
78
79 // Added answeroptions help button in definition_inner() after called to add_per_answer_fields.
80
2d279432
PP
81 $repeatedoptions['answer']['type'] = PARAM_RAW;
82 $repeatedoptions['fraction']['default'] = 0;
83 $answersoption = 'answers';
84
85 $mform->setType('answer', PARAM_NOTAGS);
86
c3f21ec5
CC
87 $repeated[] = $mform->createElement('hidden', 'tolerance');
88 $repeated[] = $mform->createElement('hidden', 'tolerancetype', 1);
61cca0b7 89 $repeatedoptions['tolerance']['type'] = PARAM_FLOAT;
2d279432 90 $repeatedoptions['tolerance']['default'] = 0.01;
dfd89b0b 91 $repeatedoptions['tolerancetype']['type'] = PARAM_INT;
2d279432 92
c7218aef
CC
93 // Create display group.
94 $answerdisplay = array();
95 $answerdisplay[] = $mform->createElement('select', 'correctanswerlength',
96 get_string('answerdisplay', 'qtype_calculated'), range(0, 9));
2d279432
PP
97 $repeatedoptions['correctanswerlength']['default'] = 2;
98
29b68914
TH
99 $answerlengthformats = array(
100 '1' => get_string('decimalformat', 'qtype_numerical'),
101 '2' => get_string('significantfiguresformat', 'qtype_calculated')
102 );
c7218aef 103 $answerdisplay[] = $mform->createElement('select', 'correctanswerformat',
29b68914 104 get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
c7218aef
CC
105 $repeated[] = $mform->createElement('group', 'answerdisplay',
106 get_string('answerdisplay', 'qtype_calculated'), $answerdisplay, null, false);
107
108 // Add feedback.
109 $repeated[] = $mform->createElement('editor', 'feedback',
110 get_string('feedback', 'question'), null, $this->editoroptions);
2d279432
PP
111
112 return $repeated;
113 }
114
c7df5006 115 protected function definition_inner($mform) {
29b68914
TH
116
117 $label = get_string('sharedwildcards', 'qtype_calculated');
2d279432 118 $mform->addElement('hidden', 'initialcategory', 1);
d90b016b 119 $mform->addElement('hidden', 'reload', 1);
2d279432 120 $mform->setType('initialcategory', PARAM_INT);
dfd89b0b 121 $mform->setType('reload', PARAM_BOOL);
2d279432 122
29b68914
TH
123 $html2 = '';
124 $mform->insertElementBefore(
125 $mform->createElement('static', 'listcategory', $label, $html2), 'name');
126 if (isset($this->question->id)) {
127 $mform->insertElementBefore($mform->createElement('static', 'initialname',
128 get_string('questionstoredname', 'qtype_calculated'),
c942cad7 129 format_string($this->initialname)), 'name');
d90b016b 130 };
29b68914
TH
131 $addfieldsname = 'updatecategory';
132 $addstring = get_string('updatecategory', 'qtype_calculated');
fe6ce234 133 $mform->registerNoSubmitButton($addfieldsname);
29b68914 134 $this->editasmultichoice = 1;
2d279432 135
29b68914
TH
136 $mform->insertElementBefore(
137 $mform->createElement('submit', $addfieldsname, $addstring), 'listcategory');
2d279432 138 $mform->registerNoSubmitButton('createoptionbutton');
29b68914 139 $mform->addElement('hidden', 'multichoice', $this->editasmultichoice);
fe6ce234 140 $mform->setType('multichoice', PARAM_INT);
2d279432 141
29b68914
TH
142 $menu = array(get_string('answersingleno', 'qtype_multichoice'),
143 get_string('answersingleyes', 'qtype_multichoice'));
144 $mform->addElement('select', 'single',
145 get_string('answerhowmany', 'qtype_multichoice'), $menu);
fe6ce234
DC
146 $mform->setDefault('single', 1);
147
29b68914
TH
148 $mform->addElement('advcheckbox', 'shuffleanswers',
149 get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0, 1));
ed14ad02 150 $mform->addHelpButton('shuffleanswers', 'shuffleanswers', 'qtype_multichoice');
fe6ce234
DC
151 $mform->setDefault('shuffleanswers', 1);
152
cdece95e 153 $numberingoptions = question_bank::get_qtype('multichoice')->get_numbering_styles();
29b68914 154 $mform->addElement('select', 'answernumbering',
cdece95e 155 get_string('answernumbering', 'qtype_multichoice'), $numberingoptions);
fe6ce234 156 $mform->setDefault('answernumbering', 'abc');
2d279432 157
fe6ce234 158 $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'),
92111e8d 159 question_bank::fraction_options_full(), max(5, QUESTION_NUMANS_START));
c7218aef 160 $mform->addHelpButton('answeroptions[0]', 'answeroptions', 'qtype_calculatedmulti');
2d279432
PP
161
162 $repeated = array();
fe6ce234
DC
163 $nounits = optional_param('nounits', 1, PARAM_INT);
164 $mform->addElement('hidden', 'nounits', $nounits);
165 $mform->setType('nounits', PARAM_INT);
166 $mform->setConstants(array('nounits'=>$nounits));
29b68914 167 for ($i = 0; $i < $nounits; $i++) {
f4fe3968
TH
168 $mform->addElement('hidden', 'unit'."[{$i}]",
169 optional_param("unit[{$i}]", '', PARAM_NOTAGS));
170 $mform->setType('unit'."[{$i}]", PARAM_NOTAGS);
171 $mform->addElement('hidden', 'multiplier'."[{$i}]",
172 optional_param("multiplier[{$i}]", '', PARAM_FLOAT));
173 $mform->setType("multiplier[{$i}]", PARAM_FLOAT);
fe6ce234 174 }
2d279432 175
b130270d
TH
176 $this->add_combined_feedback_fields(true);
177 $mform->disabledIf('shownumcorrect', 'single', 'eq', 1);
178
179 $this->add_interactive_settings(true, true);
180
c7218aef 181 // Hidden elements.
2d279432
PP
182 $mform->addElement('hidden', 'synchronize', '');
183 $mform->setType('synchronize', PARAM_INT);
29b68914
TH
184 if (isset($this->question->options) && isset($this->question->options->synchronize)) {
185 $mform->setDefault('synchronize', $this->question->options->synchronize);
2d279432 186 } else {
29b68914 187 $mform->setDefault('synchronize', 0);
2d279432
PP
188 }
189 $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
190 $mform->setType('wizard', PARAM_ALPHA);
2d279432
PP
191 }
192
29b68914 193 public function data_preprocessing($question) {
5cf69d7f 194 $question = parent::data_preprocessing($question);
0cba6a8d 195 $question = $this->data_preprocessing_answers($question, false);
5cf69d7f
TH
196 $question = $this->data_preprocessing_combined_feedback($question, true);
197 $question = $this->data_preprocessing_hints($question, true, true);
198
29b68914 199 if (isset($question->options)) {
5cf69d7f
TH
200 $question->synchronize = $question->options->synchronize;
201 $question->single = $question->options->single;
202 $question->answernumbering = $question->options->answernumbering;
203 $question->shuffleanswers = $question->options->shuffleanswers;
fe6ce234 204 }
5cf69d7f
TH
205
206 return $question;
207 }
208
072db71c
PS
209 protected function data_preprocessing_answers($question, $withanswerfiles = false) {
210 $question = parent::data_preprocessing_answers($question, $withanswerfiles);
5cf69d7f
TH
211 if (empty($question->options->answers)) {
212 return $question;
fe6ce234 213 }
5cf69d7f
TH
214
215 $key = 0;
216 foreach ($question->options->answers as $answer) {
217 // See comment in the parent method about this hack.
f4fe3968
TH
218 unset($this->_form->_defaultValues["tolerance[{$key}]"]);
219 unset($this->_form->_defaultValues["tolerancetype[{$key}]"]);
220 unset($this->_form->_defaultValues["correctanswerlength[{$key}]"]);
221 unset($this->_form->_defaultValues["correctanswerformat[{$key}]"]);
5cf69d7f
TH
222
223 $question->tolerance[$key] = $answer->tolerance;
224 $question->tolerancetype[$key] = $answer->tolerancetype;
225 $question->correctanswerlength[$key] = $answer->correctanswerlength;
226 $question->correctanswerformat[$key] = $answer->correctanswerformat;
227 $key++;
2d279432 228 }
5cf69d7f 229
fe6ce234 230 return $question;
2d279432
PP
231 }
232
88ec9f30
TH
233 /**
234 * Validate the equations in the some question content.
235 * @param array $errors where errors are being accumulated.
236 * @param string $field the field being validated.
237 * @param string $text the content of that field.
238 * @return array the updated $errors array.
239 */
240 protected function validate_text($errors, $field, $text) {
241 $problems = qtype_calculated_find_formula_errors_in_text($text);
242 if ($problems) {
243 $errors[$field] = $problems;
244 }
245 return $errors;
246 }
247
29b68914 248 public function validation($data, $files) {
2d279432 249 $errors = parent::validation($data, $files);
29b68914 250
c7218aef 251 // Verifying for errors in {=...} in question text.
88ec9f30
TH
252 $errors = $this->validate_text($errors, 'questiontext', $data['questiontext']['text']);
253 $errors = $this->validate_text($errors, 'generalfeedback', $data['generalfeedback']['text']);
254 $errors = $this->validate_text($errors, 'correctfeedback', $data['correctfeedback']['text']);
255 $errors = $this->validate_text($errors, 'partiallycorrectfeedback', $data['partiallycorrectfeedback']['text']);
256 $errors = $this->validate_text($errors, 'incorrectfeedback', $data['incorrectfeedback']['text']);
29b68914 257
2d279432
PP
258 $answers = $data['answer'];
259 $answercount = 0;
260 $maxgrade = false;
fe6ce234 261 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
2d279432 262 $mandatorydatasets = array();
29b68914 263 foreach ($answers as $key => $answer) {
2d279432
PP
264 $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
265 }
29b68914
TH
266 if (count($mandatorydatasets) == 0) {
267 foreach ($answers as $key => $answer) {
11a66e2c 268 $errors['answeroptions['.$key.']'] =
efe3e87b 269 get_string('atleastonewildcard', 'qtype_calculated');
2d279432
PP
270 }
271 }
88ec9f30 272
0cba6a8d 273 $totalfraction = 0;
274 $maxfraction = -1;
275 foreach ($answers as $key => $answer) {
276 $trimmedanswer = trim($answer);
277 $fraction = (float) $data['fraction'][$key];
278 if (empty($trimmedanswer) && $trimmedanswer != '0' && empty($fraction)) {
279 continue;
280 }
281 if (empty($trimmedanswer)) {
11a66e2c 282 $errors['answeroptions['.$key.']'] = get_string('errgradesetanswerblank', 'qtype_multichoice');
0cba6a8d 283 }
284 if ($trimmedanswer != '' || $answercount == 0) {
c7218aef 285 // Verifying for errors in {=...} in answer text.
88ec9f30
TH
286 $errors = $this->validate_text($errors, 'answeroptions[' . $key . ']', $answer);
287 $errors = $this->validate_text($errors, 'feedback[' . $key . ']',
288 $data['feedback'][$key]['text']);
0cba6a8d 289 }
88ec9f30 290
0cba6a8d 291 if ($trimmedanswer != '') {
292 if ('2' == $data['correctanswerformat'][$key] &&
293 '0' == $data['correctanswerlength'][$key]) {
294 $errors['correctanswerlength['.$key.']'] =
295 get_string('zerosignificantfiguresnotallowed', 'qtype_calculated');
2d279432 296 }
0cba6a8d 297 if (!is_numeric($data['tolerance'][$key])) {
298 $errors['tolerance['.$key.']'] =
55748620 299 get_string('xmustbenumeric', 'qtype_numerical',
c4b2600d 300 get_string('acceptederror', 'qtype_numerical'));
fe6ce234 301 }
0cba6a8d 302 if ($data['fraction'][$key] > 0) {
303 $totalfraction += $data['fraction'][$key];
2d279432 304 }
0cba6a8d 305 if ($data['fraction'][$key] > $maxfraction) {
306 $maxfraction = $data['fraction'][$key];
2d279432 307 }
0cba6a8d 308
309 $answercount++;
2d279432 310 }
0cba6a8d 311 }
312 if ($answercount == 0) {
11a66e2c
CC
313 $errors['answeroptions[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
314 $errors['answeroptions[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
0cba6a8d 315 } else if ($answercount == 1) {
11a66e2c 316 $errors['answeroptions[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
fe6ce234 317
0cba6a8d 318 }
c7218aef 319 // Perform sanity checks on fractional grades.
0cba6a8d 320 if ($data['single']== 1 ) {
321 if ($maxfraction != 1) {
11a66e2c 322 $errors['answeroptions[0]'] = get_string('errfractionsnomax', 'qtype_multichoice',
0cba6a8d 323 $maxfraction * 100);
2d279432 324 }
0cba6a8d 325 } else {
326 $totalfraction = round($totalfraction, 2);
327 if ($totalfraction != 1) {
328 $totalfraction = $totalfraction * 100;
11a66e2c 329 $errors['answeroptions[0]'] =
0cba6a8d 330 get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
2d279432 331 }
2d279432
PP
332 }
333 return $errors;
334 }
cdece95e
TH
335
336 public function qtype() {
337 return 'calculatedmulti';
338 }
2d279432 339}