on-demand release 2.2beta+
[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 149
fe6ce234 150 $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'),
92111e8d 151 question_bank::fraction_options_full(), max(5, QUESTION_NUMANS_START));
2d279432
PP
152
153 $repeated = array();
29b68914 154 // if ($this->editasmultichoice == 1) {
fe6ce234
DC
155 $nounits = optional_param('nounits', 1, PARAM_INT);
156 $mform->addElement('hidden', 'nounits', $nounits);
157 $mform->setType('nounits', PARAM_INT);
158 $mform->setConstants(array('nounits'=>$nounits));
29b68914
TH
159 for ($i = 0; $i < $nounits; $i++) {
160 $mform->addElement('hidden', 'unit'."[$i]",
161 optional_param('unit'."[$i]", '', PARAM_NOTAGS));
fe6ce234 162 $mform->setType('unit'."[$i]", PARAM_NOTAGS);
29b68914
TH
163 $mform->addElement('hidden', 'multiplier'."[$i]",
164 optional_param('multiplier'."[$i]", '', PARAM_NUMBER));
fe6ce234
DC
165 $mform->setType('multiplier'."[$i]", PARAM_NUMBER);
166 }
2d279432 167
b130270d
TH
168 $this->add_combined_feedback_fields(true);
169 $mform->disabledIf('shownumcorrect', 'single', 'eq', 1);
170
171 $this->add_interactive_settings(true, true);
172
2d279432
PP
173 //hidden elements
174 $mform->addElement('hidden', 'synchronize', '');
175 $mform->setType('synchronize', PARAM_INT);
29b68914
TH
176 if (isset($this->question->options) && isset($this->question->options->synchronize)) {
177 $mform->setDefault('synchronize', $this->question->options->synchronize);
2d279432 178 } else {
29b68914 179 $mform->setDefault('synchronize', 0);
2d279432
PP
180 }
181 $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
182 $mform->setType('wizard', PARAM_ALPHA);
2d279432
PP
183 }
184
29b68914 185 public function data_preprocessing($question) {
5cf69d7f
TH
186 $question = parent::data_preprocessing($question);
187 $question = $this->data_preprocessing_answers($question, true);
188 $question = $this->data_preprocessing_combined_feedback($question, true);
189 $question = $this->data_preprocessing_hints($question, true, true);
190
29b68914 191 if (isset($question->options)) {
5cf69d7f
TH
192 $question->synchronize = $question->options->synchronize;
193 $question->single = $question->options->single;
194 $question->answernumbering = $question->options->answernumbering;
195 $question->shuffleanswers = $question->options->shuffleanswers;
fe6ce234 196 }
5cf69d7f
TH
197
198 return $question;
199 }
200
201 protected function data_preprocessing_answers($question) {
202 $question = parent::data_preprocessing_answers($question);
203 if (empty($question->options->answers)) {
204 return $question;
fe6ce234 205 }
5cf69d7f
TH
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 unset($this->_form->_defaultValues["tolerancetype[$key]"]);
212 unset($this->_form->_defaultValues["correctanswerlength[$key]"]);
213 unset($this->_form->_defaultValues["correctanswerformat[$key]"]);
214
215 $question->tolerance[$key] = $answer->tolerance;
216 $question->tolerancetype[$key] = $answer->tolerancetype;
217 $question->correctanswerlength[$key] = $answer->correctanswerlength;
218 $question->correctanswerformat[$key] = $answer->correctanswerformat;
219 $key++;
2d279432 220 }
5cf69d7f 221
fe6ce234 222 return $question;
2d279432
PP
223 }
224
29b68914 225 public function validation($data, $files) {
2d279432 226 $errors = parent::validation($data, $files);
29b68914 227
2d279432 228 //verifying for errors in {=...} in question text;
29b68914 229 $qtext = '';
fe6ce234
DC
230 $qtextremaining = $data['questiontext']['text'];
231 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
232 foreach ($possibledatasets as $name => $value) {
2d279432
PP
233 $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
234 }
29b68914 235
fe6ce234 236 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
2d279432 237 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
29b68914 238 $qtext = $qtext.$qtextsplits[0];
2d279432 239 $qtextremaining = $qtextsplits[1];
29b68914
TH
240 if (!empty($regs1[1]) && $formulaerrors =
241 qtype_calculated_find_formula_errors($regs1[1])) {
242 if (!isset($errors['questiontext'])) {
243 $errors['questiontext'] = $formulaerrors.':'.$regs1[1];
244 } else {
2d279432
PP
245 $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
246 }
247 }
248 }
249 $answers = $data['answer'];
250 $answercount = 0;
251 $maxgrade = false;
fe6ce234 252 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
2d279432 253 $mandatorydatasets = array();
29b68914 254 foreach ($answers as $key => $answer) {
2d279432
PP
255 $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
256 }
29b68914
TH
257 if (count($mandatorydatasets) == 0) {
258 foreach ($answers as $key => $answer) {
259 $errors['answer['.$key.']'] =
efe3e87b 260 get_string('atleastonewildcard', 'qtype_calculated');
2d279432
PP
261 }
262 }
29b68914
TH
263 if ($data['multichoice'] == 1) {
264 foreach ($answers as $key => $answer) {
2d279432 265 $trimmedanswer = trim($answer);
29b68914 266 if ($trimmedanswer != '' || $answercount == 0) {
2d279432 267 //verifying for errors in {=...} in answer text;
29b68914
TH
268 $qanswer = '';
269 $qanswerremaining = $trimmedanswer;
2d279432 270 $possibledatasets = $this->qtypeobj->find_dataset_names($trimmedanswer);
fe6ce234 271 foreach ($possibledatasets as $name => $value) {
2d279432
PP
272 $qanswerremaining = str_replace('{'.$name.'}', '1', $qanswerremaining);
273 }
29b68914
TH
274
275 while (preg_match('~\{=([^[:space:]}]*)}~', $qanswerremaining, $regs1)) {
2d279432 276 $qanswersplits = explode($regs1[0], $qanswerremaining, 2);
29b68914 277 $qanswer = $qanswer . $qanswersplits[0];
2d279432 278 $qanswerremaining = $qanswersplits[1];
29b68914
TH
279 if (!empty($regs1[1]) && $formulaerrors =
280 qtype_calculated_find_formula_errors($regs1[1])) {
281 if (!isset($errors['answer['.$key.']'])) {
282 $errors['answer['.$key.']'] = $formulaerrors.':'.$regs1[1];
283 } else {
2d279432
PP
284 $errors['answer['.$key.']'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
285 }
286 }
287 }
288 }
29b68914
TH
289 if ($trimmedanswer != '') {
290 if ('2' == $data['correctanswerformat'][$key] &&
291 '0' == $data['correctanswerlength'][$key]) {
292 $errors['correctanswerlength['.$key.']'] =
293 get_string('zerosignificantfiguresnotallowed', 'qtype_calculated');
294 }
295 if (!is_numeric($data['tolerance'][$key])) {
296 $errors['tolerance['.$key.']'] =
297 get_string('mustbenumeric', 'qtype_calculated');
2d279432
PP
298 }
299 if ($data['fraction'][$key] == 1) {
fe6ce234 300 $maxgrade = true;
2d279432 301 }
fe6ce234 302
2d279432
PP
303 $answercount++;
304 }
305 //check grades
29b68914
TH
306 $totalfraction = 0;
307 $maxfraction = 0;
2d279432
PP
308 if ($answer != '') {
309 if ($data['fraction'][$key] > 0) {
310 $totalfraction += $data['fraction'][$key];
311 }
312 if ($data['fraction'][$key] > $maxfraction) {
313 $maxfraction = $data['fraction'][$key];
314 }
fe6ce234 315 }
2d279432 316 }
29b68914 317 if ($answercount == 0) {
2d279432
PP
318 $errors['answer[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
319 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
29b68914 320 } else if ($answercount == 1) {
2d279432 321 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
fe6ce234 322
2d279432
PP
323 }
324
325 /// Perform sanity checks on fractional grades
326 if ($data['single']) {
29b68914 327 if ($maxfraction > 0.999) {
2d279432 328 $maxfraction = $maxfraction * 100;
29b68914
TH
329 $errors['fraction[0]'] =
330 get_string('errfractionsnomax', 'qtype_multichoice', $maxfraction);
2d279432
PP
331 }
332 } else {
29b68914 333 $totalfraction = round($totalfraction, 2);
2d279432
PP
334 if ($totalfraction != 1) {
335 $totalfraction = $totalfraction * 100;
29b68914
TH
336 $errors['fraction[0]'] =
337 get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
2d279432
PP
338 }
339 }
fe6ce234 340
29b68914 341 if ($answercount == 0) {
2d279432
PP
342 $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
343 }
344 if ($maxgrade == false) {
345 $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
346 }
fe6ce234 347
2d279432
PP
348 }
349 return $errors;
350 }
cdece95e
TH
351
352 public function qtype() {
353 return 'calculatedmulti';
354 }
2d279432 355}