MDL-27412 Upgrade the calculatedmulti question type to the new question engine.
[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');
29b68914
TH
51 if (1 == optional_param('reload', '', PARAM_INT)) {
52 $this->reload = true;
53 } else {
54 $this->reload = false;
d90b016b 55 }
29b68914
TH
56 if (!$this->reload) {
57 // use database data as this is first pass
58 if (isset($this->question->id)) {
d90b016b 59 // remove prefix #{..}# if exists
29b68914 60 $this->initialname = $question->name;
d90b016b 61 $regs= array();
29b68914 62 if (preg_match('~#\{([^[:space:]]*)#~', $question->name , $regs)) {
d90b016b 63 $question->name = str_replace($regs[0], '', $question->name);
fe6ce234 64 };
d90b016b 65 }
fe6ce234 66 }
29b68914 67 parent::__construct($submiturl, $question, $category, $contexts, $formeditable);
2d279432
PP
68 }
69
29b68914
TH
70 public function get_per_answer_fields($mform, $label, $gradeoptions,
71 &$repeatedoptions, &$answersoption) {
fe6ce234 72 $repeated = array();
29b68914
TH
73 $repeated[] = $mform->createElement('header', 'answerhdr', $label);
74 $repeated[] = $mform->createElement('text', 'answer',
75 get_string('answer', 'question'), array('size' => 50));
76 $repeated[] = $mform->createElement('select', 'fraction',
77 get_string('grade'), $gradeoptions);
78 $repeated[] = $mform->createElement('editor', 'feedback',
79 get_string('feedback', 'question'), null, $this->editoroptions);
2d279432
PP
80 $repeatedoptions['answer']['type'] = PARAM_RAW;
81 $repeatedoptions['fraction']['default'] = 0;
82 $answersoption = 'answers';
83
84 $mform->setType('answer', PARAM_NOTAGS);
85
86 $addrepeated = array();
29b68914
TH
87 $addrepeated[] = $mform->createElement('hidden', 'tolerance');
88 $addrepeated[] = $mform->createElement('hidden', 'tolerancetype', 1);
2d279432
PP
89 $repeatedoptions['tolerance']['type'] = PARAM_NUMBER;
90 $repeatedoptions['tolerance']['default'] = 0.01;
91
29b68914
TH
92 $addrepeated[] = $mform->createElement('select', 'correctanswerlength',
93 get_string('correctanswershows', 'qtype_calculated'), range(0, 9));
2d279432
PP
94 $repeatedoptions['correctanswerlength']['default'] = 2;
95
29b68914
TH
96 $answerlengthformats = array(
97 '1' => get_string('decimalformat', 'qtype_numerical'),
98 '2' => get_string('significantfiguresformat', 'qtype_calculated')
99 );
100 $addrepeated[] = $mform->createElement('select', 'correctanswerformat',
101 get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
2d279432 102 array_splice($repeated, 3, 0, $addrepeated);
fe6ce234 103 $repeated[1]->setLabel('...<strong>{={x}+..}</strong>...');
2d279432
PP
104
105 return $repeated;
106 }
107
c7df5006 108 protected function definition_inner($mform) {
29b68914
TH
109
110 $label = get_string('sharedwildcards', 'qtype_calculated');
2d279432 111 $mform->addElement('hidden', 'initialcategory', 1);
d90b016b 112 $mform->addElement('hidden', 'reload', 1);
2d279432
PP
113 $mform->setType('initialcategory', PARAM_INT);
114
29b68914
TH
115 $html2 = '';
116 $mform->insertElementBefore(
117 $mform->createElement('static', 'listcategory', $label, $html2), 'name');
118 if (isset($this->question->id)) {
119 $mform->insertElementBefore($mform->createElement('static', 'initialname',
120 get_string('questionstoredname', 'qtype_calculated'),
121 $this->initialname), 'name');
d90b016b 122 };
29b68914
TH
123 $addfieldsname = 'updatecategory';
124 $addstring = get_string('updatecategory', 'qtype_calculated');
fe6ce234 125 $mform->registerNoSubmitButton($addfieldsname);
29b68914 126 $this->editasmultichoice = 1;
2d279432 127
29b68914
TH
128 $mform->insertElementBefore(
129 $mform->createElement('submit', $addfieldsname, $addstring), 'listcategory');
2d279432 130 $mform->registerNoSubmitButton('createoptionbutton');
29b68914 131 $mform->addElement('hidden', 'multichoice', $this->editasmultichoice);
fe6ce234 132 $mform->setType('multichoice', PARAM_INT);
2d279432 133
29b68914
TH
134 $menu = array(get_string('answersingleno', 'qtype_multichoice'),
135 get_string('answersingleyes', 'qtype_multichoice'));
136 $mform->addElement('select', 'single',
137 get_string('answerhowmany', 'qtype_multichoice'), $menu);
fe6ce234
DC
138 $mform->setDefault('single', 1);
139
29b68914
TH
140 $mform->addElement('advcheckbox', 'shuffleanswers',
141 get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0, 1));
ed14ad02 142 $mform->addHelpButton('shuffleanswers', 'shuffleanswers', 'qtype_multichoice');
fe6ce234
DC
143 $mform->setDefault('shuffleanswers', 1);
144
cdece95e 145 $numberingoptions = question_bank::get_qtype('multichoice')->get_numbering_styles();
29b68914 146 $mform->addElement('select', 'answernumbering',
cdece95e 147 get_string('answernumbering', 'qtype_multichoice'), $numberingoptions);
fe6ce234 148 $mform->setDefault('answernumbering', 'abc');
2d279432
PP
149
150 $creategrades = get_grade_options();
fe6ce234 151 $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'),
29b68914 152 $creategrades->gradeoptionsfull, max(5, QUESTION_NUMANS_START));
2d279432
PP
153
154 $repeated = array();
29b68914 155 // if ($this->editasmultichoice == 1) {
fe6ce234
DC
156 $nounits = optional_param('nounits', 1, PARAM_INT);
157 $mform->addElement('hidden', 'nounits', $nounits);
158 $mform->setType('nounits', PARAM_INT);
159 $mform->setConstants(array('nounits'=>$nounits));
29b68914
TH
160 for ($i = 0; $i < $nounits; $i++) {
161 $mform->addElement('hidden', 'unit'."[$i]",
162 optional_param('unit'."[$i]", '', PARAM_NOTAGS));
fe6ce234 163 $mform->setType('unit'."[$i]", PARAM_NOTAGS);
29b68914
TH
164 $mform->addElement('hidden', 'multiplier'."[$i]",
165 optional_param('multiplier'."[$i]", '', PARAM_NUMBER));
fe6ce234
DC
166 $mform->setType('multiplier'."[$i]", PARAM_NUMBER);
167 }
2d279432 168
29b68914
TH
169 $mform->setType('addunits', 'hidden');
170 $mform->addElement('header', 'overallfeedbackhdr',
171 get_string('overallfeedback', 'qtype_multichoice'));
2d279432 172
29b68914
TH
173 foreach (array('correctfeedback', 'partiallycorrectfeedback',
174 'incorrectfeedback') as $feedbackname) {
175 $mform->addElement('editor', $feedbackname,
176 get_string($feedbackname, 'qtype_multichoice'), null, $this->editoroptions);
fe6ce234
DC
177 $mform->setType($feedbackname, PARAM_RAW);
178 }
2d279432
PP
179 //hidden elements
180 $mform->addElement('hidden', 'synchronize', '');
181 $mform->setType('synchronize', PARAM_INT);
29b68914
TH
182 if (isset($this->question->options) && isset($this->question->options->synchronize)) {
183 $mform->setDefault('synchronize', $this->question->options->synchronize);
2d279432 184 } else {
29b68914 185 $mform->setDefault('synchronize', 0);
2d279432
PP
186 }
187 $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
188 $mform->setType('wizard', PARAM_ALPHA);
2d279432
PP
189 }
190
29b68914
TH
191 public function data_preprocessing($question) {
192 $default_values['multichoice']= $this->editasmultichoice;
193 if (isset($question->options)) {
2d279432
PP
194 $answers = $question->options->answers;
195 if (count($answers)) {
196 $key = 0;
29b68914 197 foreach ($answers as $answer) {
fe6ce234 198 $draftid = file_get_submitted_draft_itemid('feedback['.$key.']');
2d279432
PP
199 $default_values['answer['.$key.']'] = $answer->answer;
200 $default_values['fraction['.$key.']'] = $answer->fraction;
201 $default_values['tolerance['.$key.']'] = $answer->tolerance;
202 $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype;
203 $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength;
204 $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat;
fe6ce234
DC
205 $default_values['feedback['.$key.']'] = array();
206 // prepare draftarea
29b68914
TH
207 $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area(
208 $draftid, $this->context->id, 'question', 'answerfeedback',
209 empty($answer->id) ? null : (int) $answer->id,
210 $this->fileoptions, $answer->feedback);
fe6ce234
DC
211 $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat;
212 $default_values['feedback['.$key.']']['itemid'] = $draftid;
2d279432
PP
213 $key++;
214 }
215 }
29b68914 216 $default_values['synchronize'] = $question->options->synchronize;
2d279432 217
29b68914 218 if (isset($question->options->units)) {
2d279432
PP
219 $units = array_values($question->options->units);
220 // make sure the default unit is at index 0
221 usort($units, create_function('$a, $b',
fe6ce234
DC
222 'if (1.0 === (float)$a->multiplier) { return -1; } else '.
223 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }'));
2d279432
PP
224 if (count($units)) {
225 $key = 0;
29b68914 226 foreach ($units as $unit) {
2d279432
PP
227 $default_values['unit['.$key.']'] = $unit->unit;
228 $default_values['multiplier['.$key.']'] = $unit->multiplier;
229 $key++;
230 }
231 }
232 }
233 }
29b68914 234 if (isset($question->options->single)) {
fe6ce234
DC
235 $default_values['single'] = $question->options->single;
236 $default_values['answernumbering'] = $question->options->answernumbering;
237 $default_values['shuffleanswers'] = $question->options->shuffleanswers;
238 }
2d279432
PP
239 $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated');
240 $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated');
fe6ce234
DC
241
242 // prepare draft files
29b68914
TH
243 foreach (array('correctfeedback', 'partiallycorrectfeedback',
244 'incorrectfeedback') as $feedbackname) {
fe6ce234
DC
245 if (!isset($question->options->$feedbackname)) {
246 continue;
247 }
248 $text = $question->options->$feedbackname;
249 $draftid = file_get_submitted_draft_itemid($feedbackname);
250 $feedbackformat = $feedbackname . 'format';
251 $format = $question->options->$feedbackformat;
252 $default_values[$feedbackname] = array();
253 $default_values[$feedbackname]['text'] = file_prepare_draft_area(
254 $draftid, // draftid
255 $this->context->id, // context
256 'qtype_calculatedmulti', // component
257 $feedbackname, // filarea
258 !empty($question->id)?(int)$question->id:null, // itemid
259 $this->fileoptions, // options
260 $text // text
261 );
262 $default_values[$feedbackname]['format'] = $format;
263 $default_values[$feedbackname]['itemid'] = $draftid;
264 }
265 /**
266 * set the wild cards category display given that on loading the category element is
267 * unselected when processing this function but have a valid value when processing the
268 * update category button. The value can be obtain by
29b68914
TH
269 * $qu->category = $this->_form->_elements[$this->_form->
270 * _elementIndex['category']]->_values[0];
fe6ce234
DC
271 * but is coded using existing functions
272 */
0ff4bd08
TH
273 $qu = new stdClass();
274 $el = new stdClass();
29b68914
TH
275 // no need to call elementExists() here.
276 if ($this->_form->elementExists('category')) {
277 $el = $this->_form->getElement('category');
fe6ce234 278 } else {
29b68914 279 $el = $this->_form->getElement('categorymoveto');
fe6ce234 280 }
29b68914
TH
281 if ($value = $el->getSelected()) {
282 $qu->category = $value[0];
283 } else {
284 // on load $question->category is set by question.php
285 $qu->category = $question->category;
2d279432
PP
286 }
287 $html2 = $this->qtypeobj->print_dataset_definitions_category($qu);
29b68914 288 $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2;
2d279432 289 $question = (object)((array)$question + $default_values);
fe6ce234 290 return $question;
2d279432
PP
291 }
292
29b68914 293 public function validation($data, $files) {
2d279432 294 $errors = parent::validation($data, $files);
29b68914 295
2d279432 296 //verifying for errors in {=...} in question text;
29b68914 297 $qtext = '';
fe6ce234
DC
298 $qtextremaining = $data['questiontext']['text'];
299 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
300 foreach ($possibledatasets as $name => $value) {
2d279432
PP
301 $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
302 }
29b68914 303
fe6ce234 304 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
2d279432 305 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
29b68914 306 $qtext = $qtext.$qtextsplits[0];
2d279432 307 $qtextremaining = $qtextsplits[1];
29b68914
TH
308 if (!empty($regs1[1]) && $formulaerrors =
309 qtype_calculated_find_formula_errors($regs1[1])) {
310 if (!isset($errors['questiontext'])) {
311 $errors['questiontext'] = $formulaerrors.':'.$regs1[1];
312 } else {
2d279432
PP
313 $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
314 }
315 }
316 }
317 $answers = $data['answer'];
318 $answercount = 0;
319 $maxgrade = false;
fe6ce234 320 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
2d279432 321 $mandatorydatasets = array();
29b68914 322 foreach ($answers as $key => $answer) {
2d279432
PP
323 $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
324 }
29b68914
TH
325 if (count($mandatorydatasets) == 0) {
326 foreach ($answers as $key => $answer) {
327 $errors['answer['.$key.']'] =
efe3e87b 328 get_string('atleastonewildcard', 'qtype_calculated');
2d279432
PP
329 }
330 }
29b68914
TH
331 if ($data['multichoice'] == 1) {
332 foreach ($answers as $key => $answer) {
2d279432 333 $trimmedanswer = trim($answer);
29b68914 334 if ($trimmedanswer != '' || $answercount == 0) {
2d279432 335 //verifying for errors in {=...} in answer text;
29b68914
TH
336 $qanswer = '';
337 $qanswerremaining = $trimmedanswer;
2d279432 338 $possibledatasets = $this->qtypeobj->find_dataset_names($trimmedanswer);
fe6ce234 339 foreach ($possibledatasets as $name => $value) {
2d279432
PP
340 $qanswerremaining = str_replace('{'.$name.'}', '1', $qanswerremaining);
341 }
29b68914
TH
342
343 while (preg_match('~\{=([^[:space:]}]*)}~', $qanswerremaining, $regs1)) {
2d279432 344 $qanswersplits = explode($regs1[0], $qanswerremaining, 2);
29b68914 345 $qanswer = $qanswer . $qanswersplits[0];
2d279432 346 $qanswerremaining = $qanswersplits[1];
29b68914
TH
347 if (!empty($regs1[1]) && $formulaerrors =
348 qtype_calculated_find_formula_errors($regs1[1])) {
349 if (!isset($errors['answer['.$key.']'])) {
350 $errors['answer['.$key.']'] = $formulaerrors.':'.$regs1[1];
351 } else {
2d279432
PP
352 $errors['answer['.$key.']'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
353 }
354 }
355 }
356 }
29b68914
TH
357 if ($trimmedanswer != '') {
358 if ('2' == $data['correctanswerformat'][$key] &&
359 '0' == $data['correctanswerlength'][$key]) {
360 $errors['correctanswerlength['.$key.']'] =
361 get_string('zerosignificantfiguresnotallowed', 'qtype_calculated');
362 }
363 if (!is_numeric($data['tolerance'][$key])) {
364 $errors['tolerance['.$key.']'] =
365 get_string('mustbenumeric', 'qtype_calculated');
2d279432
PP
366 }
367 if ($data['fraction'][$key] == 1) {
fe6ce234 368 $maxgrade = true;
2d279432 369 }
fe6ce234 370
2d279432
PP
371 $answercount++;
372 }
373 //check grades
29b68914
TH
374 $totalfraction = 0;
375 $maxfraction = 0;
2d279432
PP
376 if ($answer != '') {
377 if ($data['fraction'][$key] > 0) {
378 $totalfraction += $data['fraction'][$key];
379 }
380 if ($data['fraction'][$key] > $maxfraction) {
381 $maxfraction = $data['fraction'][$key];
382 }
fe6ce234 383 }
2d279432 384 }
29b68914 385 if ($answercount == 0) {
2d279432
PP
386 $errors['answer[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
387 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
29b68914 388 } else if ($answercount == 1) {
2d279432 389 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
fe6ce234 390
2d279432
PP
391 }
392
393 /// Perform sanity checks on fractional grades
394 if ($data['single']) {
29b68914 395 if ($maxfraction > 0.999) {
2d279432 396 $maxfraction = $maxfraction * 100;
29b68914
TH
397 $errors['fraction[0]'] =
398 get_string('errfractionsnomax', 'qtype_multichoice', $maxfraction);
2d279432
PP
399 }
400 } else {
29b68914 401 $totalfraction = round($totalfraction, 2);
2d279432
PP
402 if ($totalfraction != 1) {
403 $totalfraction = $totalfraction * 100;
29b68914
TH
404 $errors['fraction[0]'] =
405 get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
2d279432
PP
406 }
407 }
fe6ce234 408
29b68914 409 if ($answercount == 0) {
2d279432
PP
410 $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
411 }
412 if ($maxgrade == false) {
413 $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
414 }
fe6ce234 415
2d279432
PP
416 }
417 return $errors;
418 }
cdece95e
TH
419
420 public function qtype() {
421 return 'calculatedmulti';
422 }
2d279432 423}