on-demand release 2.1beta
[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
TH
185 public function data_preprocessing($question) {
186 $default_values['multichoice']= $this->editasmultichoice;
187 if (isset($question->options)) {
2d279432
PP
188 $answers = $question->options->answers;
189 if (count($answers)) {
190 $key = 0;
29b68914 191 foreach ($answers as $answer) {
fe6ce234 192 $draftid = file_get_submitted_draft_itemid('feedback['.$key.']');
2d279432
PP
193 $default_values['answer['.$key.']'] = $answer->answer;
194 $default_values['fraction['.$key.']'] = $answer->fraction;
195 $default_values['tolerance['.$key.']'] = $answer->tolerance;
196 $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype;
197 $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength;
198 $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat;
fe6ce234
DC
199 $default_values['feedback['.$key.']'] = array();
200 // prepare draftarea
29b68914
TH
201 $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area(
202 $draftid, $this->context->id, 'question', 'answerfeedback',
203 empty($answer->id) ? null : (int) $answer->id,
204 $this->fileoptions, $answer->feedback);
fe6ce234
DC
205 $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat;
206 $default_values['feedback['.$key.']']['itemid'] = $draftid;
2d279432
PP
207 $key++;
208 }
209 }
29b68914 210 $default_values['synchronize'] = $question->options->synchronize;
2d279432 211
29b68914 212 if (isset($question->options->units)) {
2d279432
PP
213 $units = array_values($question->options->units);
214 // make sure the default unit is at index 0
215 usort($units, create_function('$a, $b',
fe6ce234
DC
216 'if (1.0 === (float)$a->multiplier) { return -1; } else '.
217 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }'));
2d279432
PP
218 if (count($units)) {
219 $key = 0;
29b68914 220 foreach ($units as $unit) {
2d279432
PP
221 $default_values['unit['.$key.']'] = $unit->unit;
222 $default_values['multiplier['.$key.']'] = $unit->multiplier;
223 $key++;
224 }
225 }
226 }
227 }
29b68914 228 if (isset($question->options->single)) {
fe6ce234
DC
229 $default_values['single'] = $question->options->single;
230 $default_values['answernumbering'] = $question->options->answernumbering;
231 $default_values['shuffleanswers'] = $question->options->shuffleanswers;
232 }
2d279432
PP
233 $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated');
234 $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated');
fe6ce234
DC
235
236 // prepare draft files
29b68914
TH
237 foreach (array('correctfeedback', 'partiallycorrectfeedback',
238 'incorrectfeedback') as $feedbackname) {
fe6ce234
DC
239 if (!isset($question->options->$feedbackname)) {
240 continue;
241 }
242 $text = $question->options->$feedbackname;
243 $draftid = file_get_submitted_draft_itemid($feedbackname);
244 $feedbackformat = $feedbackname . 'format';
245 $format = $question->options->$feedbackformat;
246 $default_values[$feedbackname] = array();
247 $default_values[$feedbackname]['text'] = file_prepare_draft_area(
248 $draftid, // draftid
249 $this->context->id, // context
250 'qtype_calculatedmulti', // component
251 $feedbackname, // filarea
252 !empty($question->id)?(int)$question->id:null, // itemid
253 $this->fileoptions, // options
254 $text // text
255 );
256 $default_values[$feedbackname]['format'] = $format;
257 $default_values[$feedbackname]['itemid'] = $draftid;
258 }
259 /**
260 * set the wild cards category display given that on loading the category element is
261 * unselected when processing this function but have a valid value when processing the
262 * update category button. The value can be obtain by
29b68914
TH
263 * $qu->category = $this->_form->_elements[$this->_form->
264 * _elementIndex['category']]->_values[0];
fe6ce234
DC
265 * but is coded using existing functions
266 */
0ff4bd08
TH
267 $qu = new stdClass();
268 $el = new stdClass();
29b68914
TH
269 // no need to call elementExists() here.
270 if ($this->_form->elementExists('category')) {
271 $el = $this->_form->getElement('category');
fe6ce234 272 } else {
29b68914 273 $el = $this->_form->getElement('categorymoveto');
fe6ce234 274 }
29b68914
TH
275 if ($value = $el->getSelected()) {
276 $qu->category = $value[0];
277 } else {
278 // on load $question->category is set by question.php
279 $qu->category = $question->category;
2d279432
PP
280 }
281 $html2 = $this->qtypeobj->print_dataset_definitions_category($qu);
29b68914 282 $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2;
2d279432 283 $question = (object)((array)$question + $default_values);
fe6ce234 284 return $question;
2d279432
PP
285 }
286
29b68914 287 public function validation($data, $files) {
2d279432 288 $errors = parent::validation($data, $files);
29b68914 289
2d279432 290 //verifying for errors in {=...} in question text;
29b68914 291 $qtext = '';
fe6ce234
DC
292 $qtextremaining = $data['questiontext']['text'];
293 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
294 foreach ($possibledatasets as $name => $value) {
2d279432
PP
295 $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
296 }
29b68914 297
fe6ce234 298 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
2d279432 299 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
29b68914 300 $qtext = $qtext.$qtextsplits[0];
2d279432 301 $qtextremaining = $qtextsplits[1];
29b68914
TH
302 if (!empty($regs1[1]) && $formulaerrors =
303 qtype_calculated_find_formula_errors($regs1[1])) {
304 if (!isset($errors['questiontext'])) {
305 $errors['questiontext'] = $formulaerrors.':'.$regs1[1];
306 } else {
2d279432
PP
307 $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
308 }
309 }
310 }
311 $answers = $data['answer'];
312 $answercount = 0;
313 $maxgrade = false;
fe6ce234 314 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
2d279432 315 $mandatorydatasets = array();
29b68914 316 foreach ($answers as $key => $answer) {
2d279432
PP
317 $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
318 }
29b68914
TH
319 if (count($mandatorydatasets) == 0) {
320 foreach ($answers as $key => $answer) {
321 $errors['answer['.$key.']'] =
efe3e87b 322 get_string('atleastonewildcard', 'qtype_calculated');
2d279432
PP
323 }
324 }
29b68914
TH
325 if ($data['multichoice'] == 1) {
326 foreach ($answers as $key => $answer) {
2d279432 327 $trimmedanswer = trim($answer);
29b68914 328 if ($trimmedanswer != '' || $answercount == 0) {
2d279432 329 //verifying for errors in {=...} in answer text;
29b68914
TH
330 $qanswer = '';
331 $qanswerremaining = $trimmedanswer;
2d279432 332 $possibledatasets = $this->qtypeobj->find_dataset_names($trimmedanswer);
fe6ce234 333 foreach ($possibledatasets as $name => $value) {
2d279432
PP
334 $qanswerremaining = str_replace('{'.$name.'}', '1', $qanswerremaining);
335 }
29b68914
TH
336
337 while (preg_match('~\{=([^[:space:]}]*)}~', $qanswerremaining, $regs1)) {
2d279432 338 $qanswersplits = explode($regs1[0], $qanswerremaining, 2);
29b68914 339 $qanswer = $qanswer . $qanswersplits[0];
2d279432 340 $qanswerremaining = $qanswersplits[1];
29b68914
TH
341 if (!empty($regs1[1]) && $formulaerrors =
342 qtype_calculated_find_formula_errors($regs1[1])) {
343 if (!isset($errors['answer['.$key.']'])) {
344 $errors['answer['.$key.']'] = $formulaerrors.':'.$regs1[1];
345 } else {
2d279432
PP
346 $errors['answer['.$key.']'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
347 }
348 }
349 }
350 }
29b68914
TH
351 if ($trimmedanswer != '') {
352 if ('2' == $data['correctanswerformat'][$key] &&
353 '0' == $data['correctanswerlength'][$key]) {
354 $errors['correctanswerlength['.$key.']'] =
355 get_string('zerosignificantfiguresnotallowed', 'qtype_calculated');
356 }
357 if (!is_numeric($data['tolerance'][$key])) {
358 $errors['tolerance['.$key.']'] =
359 get_string('mustbenumeric', 'qtype_calculated');
2d279432
PP
360 }
361 if ($data['fraction'][$key] == 1) {
fe6ce234 362 $maxgrade = true;
2d279432 363 }
fe6ce234 364
2d279432
PP
365 $answercount++;
366 }
367 //check grades
29b68914
TH
368 $totalfraction = 0;
369 $maxfraction = 0;
2d279432
PP
370 if ($answer != '') {
371 if ($data['fraction'][$key] > 0) {
372 $totalfraction += $data['fraction'][$key];
373 }
374 if ($data['fraction'][$key] > $maxfraction) {
375 $maxfraction = $data['fraction'][$key];
376 }
fe6ce234 377 }
2d279432 378 }
29b68914 379 if ($answercount == 0) {
2d279432
PP
380 $errors['answer[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
381 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
29b68914 382 } else if ($answercount == 1) {
2d279432 383 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
fe6ce234 384
2d279432
PP
385 }
386
387 /// Perform sanity checks on fractional grades
388 if ($data['single']) {
29b68914 389 if ($maxfraction > 0.999) {
2d279432 390 $maxfraction = $maxfraction * 100;
29b68914
TH
391 $errors['fraction[0]'] =
392 get_string('errfractionsnomax', 'qtype_multichoice', $maxfraction);
2d279432
PP
393 }
394 } else {
29b68914 395 $totalfraction = round($totalfraction, 2);
2d279432
PP
396 if ($totalfraction != 1) {
397 $totalfraction = $totalfraction * 100;
29b68914
TH
398 $errors['fraction[0]'] =
399 get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
2d279432
PP
400 }
401 }
fe6ce234 402
29b68914 403 if ($answercount == 0) {
2d279432
PP
404 $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
405 }
406 if ($maxgrade == false) {
407 $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
408 }
fe6ce234 409
2d279432
PP
410 }
411 return $errors;
412 }
cdece95e
TH
413
414 public function qtype() {
415 return 'calculatedmulti';
416 }
2d279432 417}