MDL-20636 a bit of clode clean-up in calculated*.
[moodle.git] / question / type / multianswer / edit_multianswer_form.php
CommitLineData
aeb15530 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
271ffe3f 17/**
d3603157 18 * Defines the editing form for the multi-answer question type.
271ffe3f 19 *
b04a4319
TH
20 * @package qtype
21 * @subpackage multianswer
22 * @copyright 2007 Jamie Pratt me@jamiep.org
23 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
271ffe3f 24 */
25
d3603157 26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
29
271ffe3f 30/**
d3603157 31 * Form for editing multi-answer questions.
b04a4319
TH
32 *
33 * @copyright 2007 Jamie Pratt me@jamiep.org
34 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
271ffe3f 35 */
36class question_edit_multianswer_form extends question_edit_form {
37
59a3fcd3
TH
38 // $questiondisplay will contain the qtype_multianswer_extract_question from
39 // the questiontext
40 public $questiondisplay;
41 // $savedquestiondisplay will contain the qtype_multianswer_extract_question
42 // from the questiontext in database
43 public $savedquestion;
44 public $savedquestiondisplay;
45 public $used_in_quiz = false;
46 public $qtype_change = false;
47 public $negative_diff = 0;
fe6ce234
DC
48 public $nb_of_quiz = 0;
49 public $nb_of_attempts = 0;
59a3fcd3
TH
50 public $confirm = 0;
51 public $reload = false;
aeb15530 52
59a3fcd3
TH
53 public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) {
54 global $SESSION, $CFG, $DB;
347fb175 55 $this->regenerate = true;
59a3fcd3
TH
56 if ("1" == optional_param('reload', '', PARAM_INT)) {
57 $this->reload = true;
58 } else {
59 $this->reload = false;
d3e3bd6c 60 }
59a3fcd3
TH
61
62 $this->used_in_quiz = false;
63
64 if (isset($question->id) && $question->id != 0) {
65 $this->savedquestiondisplay = fullclone($question);
66 if ($list = $DB->get_records('quiz_question_instances',
67 array('question' => $question->id))) {
68 foreach ($list as $key => $li) {
d3e3bd6c 69 $this->nb_of_quiz ++;
59a3fcd3
TH
70 if ($att = $DB->get_records('quiz_attempts',
71 array('quiz' => $li->quiz, 'preview' => '0'))) {
72 $this->nb_of_attempts += count($att);
d3e3bd6c 73 $this->used_in_quiz = true;
74 }
75 }
76 }
77 }
78
59a3fcd3 79 parent::__construct($submiturl, $question, $category, $contexts, $formeditable);
d3e3bd6c 80 }
81
c7df5006 82 protected function definition_inner($mform) {
347fb175
PP
83 $mform->addElement('hidden', 'reload', 1);
84 $mform->setType('reload', PARAM_INT);
705b5874 85
fe648028 86 // Remove meaningless defaultgrade field.
87 $mform->removeElement('defaultgrade');
59a3fcd3 88 $this->confirm = optional_param('confirm', '0', PARAM_RAW);
f34488b2 89
fe6ce234 90 // display the questions from questiontext;
59a3fcd3
TH
91 if ("" != optional_param('questiontext', '', PARAM_RAW)) {
92 $this->questiondisplay = fullclone(qtype_multianswer_extract_question(
93 optional_param('questiontext', '', PARAM_RAW)));
f34488b2 94
59a3fcd3
TH
95 } else {
96 if (!$this->reload && !empty($this->savedquestiondisplay->id)) {
aeb15530 97 // use database data as this is first pass
d3e3bd6c 98 // question->id == 0 so no stored datasets
99 $this->questiondisplay = fullclone($this->savedquestiondisplay);
59a3fcd3
TH
100 foreach ($this->questiondisplay->options->questions as $subquestion) {
101 if (!empty($subquestion)) {
fe6ce234 102 $subquestion->answer = array('');
59a3fcd3
TH
103 foreach ($subquestion->options->answers as $ans) {
104 $subquestion->answer[] = $ans->answer;
fe6ce234 105 }
d3e3bd6c 106 }
aeb15530 107 }
59a3fcd3 108 } else {
d3e3bd6c 109 $this->questiondisplay = "";
110 }
f34488b2 111 }
705b5874 112
59a3fcd3
TH
113 if (isset($this->savedquestiondisplay->options->questions) &&
114 is_array($this->savedquestiondisplay->options->questions)) {
115 $countsavedsubquestions = 0;
116 foreach ($this->savedquestiondisplay->options->questions as $subquestion) {
117 if (!empty($subquestion)) {
fe6ce234 118 $countsavedsubquestions++;
df79079f 119 }
f34488b2 120 }
705b5874 121 } else {
59a3fcd3 122 $countsavedsubquestions = 0;
705b5874 123 }
59a3fcd3
TH
124 if ($this->reload) {
125 if (isset($this->questiondisplay->options->questions) &&
126 is_array($this->questiondisplay->options->questions)) {
127 $countsubquestions = 0;
128 foreach ($this->questiondisplay->options->questions as $subquestion) {
129 if (!empty($subquestion)) {
fe6ce234 130 $countsubquestions++;
d3e3bd6c 131 }
132 }
fe6ce234 133 } else {
59a3fcd3 134 $countsubquestions = 0;
705b5874 135 }
59a3fcd3
TH
136 } else {
137 $countsubquestions = $countsavedsubquestions;
fe6ce234 138 }
aeb15530 139
59a3fcd3
TH
140 $mform->addElement('submit', 'analyzequestion',
141 get_string('decodeverifyquestiontext', 'qtype_multianswer'));
fe6ce234 142 $mform->registerNoSubmitButton('analyzequestion');
59a3fcd3 143 if ($this->reload) {
5bb96cf6
TH
144 $mform->addElement('html', '<div class="ablock clearfix">');
145 $mform->addElement('html', '<div class=" clearfix">');
59a3fcd3 146 for ($sub = 1; $sub <= $countsubquestions; $sub++) {
aeb15530 147
d3e3bd6c 148 $this->editas[$sub] = 'unknown type';
59a3fcd3
TH
149 if (isset($this->questiondisplay->options->questions[$sub]->qtype)) {
150 $this->editas[$sub] = $this->questiondisplay->options->questions[$sub]->qtype;
d3e3bd6c 151 } else if (optional_param('sub_'.$sub."_".'qtype', '', PARAM_RAW) != '') {
152 $this->editas[$sub] = optional_param('sub_'.$sub."_".'qtype', '', PARAM_RAW);
fd97082c 153 }
d3e3bd6c 154 $storemess = '';
59a3fcd3
TH
155 if (isset($this->savedquestiondisplay->options->questions[$sub]->qtype) &&
156 $this->savedquestiondisplay->options->questions[$sub]->qtype !=
157 $this->questiondisplay->options->questions[$sub]->qtype) {
158 $this->qtype_change = true;
159 $storemess = "<font class=\"error\"> STORED QTYPE " .
160 question_bank::get_qtype_name(
161 $this->savedquestiondisplay->options->questions[$sub]->qtype).
162 "</font >";
163 }
aeb15530 164
5e8a85aa 165 $mform->addElement('header', 'subhdr'.$sub, get_string('questionno', 'question',
59a3fcd3
TH
166 '{#'.$sub.'}').'&nbsp;'.question_bank::get_qtype_name(
167 $this->questiondisplay->options->questions[$sub]->qtype).$storemess);
aeb15530 168
59a3fcd3
TH
169 $mform->addElement('static', 'sub_'.$sub."_".'questiontext',
170 get_string('questiondefinition', 'qtype_multianswer'),
171 array('cols' => 60, 'rows' => 3));
aeb15530 172
59a3fcd3
TH
173 if (isset ($this->questiondisplay->options->questions[$sub]->questiontext)) {
174 $mform->setDefault('sub_'.$sub."_".'questiontext',
175 $this->questiondisplay->options->questions[$sub]->questiontext['text']);
f34488b2 176 }
aeb15530 177
59a3fcd3
TH
178 $mform->addElement('static', 'sub_'.$sub."_".'defaultgrade',
179 get_string('defaultgrade', 'question'));
180 $mform->setDefault('sub_'.$sub."_".'defaultgrade',
181 $this->questiondisplay->options->questions[$sub]->defaultgrade);
aeb15530 182
59a3fcd3
TH
183 if ($this->questiondisplay->options->questions[$sub]->qtype == 'shortanswer') {
184 $mform->addElement('static', 'sub_'.$sub."_".'usecase',
185 get_string('casesensitive', 'question'));
fe6ce234 186 }
aeb15530 187
59a3fcd3
TH
188 if ($this->questiondisplay->options->questions[$sub]->qtype == 'multichoice') {
189 $mform->addElement('static', 'sub_'.$sub."_".'layout',
190 get_string('layout', 'qtype_multianswer'),
191 array('cols' => 60, 'rows' => 1));
fe6ce234 192 }
59a3fcd3 193 foreach ($this->questiondisplay->options->questions[$sub]->answer as $key => $ans) {
aeb15530 194
59a3fcd3
TH
195 $mform->addElement('static', 'sub_'.$sub."_".'answer['.$key.']',
196 get_string('answer', 'question'), array('cols' => 60, 'rows' => 1));
aeb15530 197
59a3fcd3
TH
198 if ($this->questiondisplay->options->questions[$sub]->qtype == 'numerical' &&
199 $key == 0) {
200 $mform->addElement('static', 'sub_'.$sub."_".'tolerance['.$key.']',
201 get_string('acceptederror', 'quiz'));
d3e3bd6c 202 }
aeb15530 203
59a3fcd3
TH
204 $mform->addElement('static', 'sub_'.$sub."_".'fraction['.$key.']',
205 get_string('grade'));
aeb15530 206
59a3fcd3
TH
207 $mform->addElement('static', 'sub_'.$sub."_".'feedback['.$key.']',
208 get_string('feedback', 'question'));
d3e3bd6c 209 }
aeb15530 210
f34488b2 211 }
5bb96cf6 212 $mform->addElement('html', '</div>');
59a3fcd3
TH
213 $this->negative_diff = $countsavedsubquestions - $countsubquestions;
214 if (($this->negative_diff > 0) ||$this->qtype_change ||
215 ($this->used_in_quiz && $this->negative_diff != 0)) {
216 $mform->addElement('header', 'additemhdr',
217 get_string('warningquestionmodified', 'qtype_multianswer'));
fe6ce234 218 }
59a3fcd3
TH
219 if ($this->negative_diff > 0) {
220 $mform->addElement('static', 'alert1', "<strong>".
221 get_string('questiondeleted', 'qtype_multianswer')."</strong>",
222 get_string('questionsless', 'qtype_multianswer', $this->negative_diff));
d3e3bd6c 223 }
59a3fcd3
TH
224 if ($this->qtype_change) {
225 $mform->addElement('static', 'alert1', "<strong>".
226 get_string('questiontypechanged', 'qtype_multianswer')."</strong>",
227 get_string('questiontypechangedcomment', 'qtype_multianswer'));
d3e3bd6c 228 }
5bb96cf6 229 $mform->addElement('html', '</div>');
d3e3bd6c 230 }
59a3fcd3
TH
231 if ($this->used_in_quiz) {
232 if ($this->negative_diff < 0) {
c4979f02 233 $diff = $countsubquestions - $countsavedsubquestions;
59a3fcd3
TH
234 $mform->addElement('static', 'alert1', "<strong>".
235 get_string('questionsadded', 'qtype_multianswer')."</strong>",
236 "<strong>".get_string('questionsmore', 'qtype_multianswer', $diff).
237 "</strong>");
c4979f02 238 }
59a3fcd3 239 $a = new stdClass();
c4979f02
PP
240 $a->nb_of_quiz = $this->nb_of_quiz;
241 $a->nb_of_attempts = $this->nb_of_attempts;
59a3fcd3
TH
242 $mform->addElement('header', 'additemhdr2',
243 get_string('questionusedinquiz', 'qtype_multianswer', $a));
244 $mform->addElement('static', 'alertas',
245 get_string('youshouldnot', 'qtype_multianswer'));
d3e3bd6c 246 }
59a3fcd3
TH
247 if (($this->negative_diff > 0 || $this->used_in_quiz &&
248 ($this->negative_diff > 0 || $this->negative_diff < 0 || $this->qtype_change)) &&
249 $this->reload) {
250 $mform->addElement('header', 'additemhdr',
251 get_string('questionsaveasedited', 'qtype_multianswer'));
252 $mform->addElement('checkbox', 'confirm', '',
253 get_string('confirmquestionsaveasedited', 'qtype_multianswer'));
d3e3bd6c 254 $mform->setDefault('confirm', 0);
59a3fcd3
TH
255 } else {
256 $mform->addElement('hidden', 'confirm', 0);
705b5874 257 }
347fb175 258
120e5cbf 259 }
705b5874 260
f34488b2 261
59a3fcd3 262 public function set_data($question) {
f34488b2 263 global $DB;
59a3fcd3
TH
264 $default_values = array();
265 if (isset($question->id) and $question->id and $question->qtype &&
266 $question->questiontext) {
c6fc9988 267
268 foreach ($question->options->questions as $key => $wrapped) {
59a3fcd3 269 if (!empty($wrapped)) {
fe6ce234
DC
270 // The old way of restoring the definitions is kept to gradually
271 // update all multianswer questions
272 if (empty($wrapped->questiontext)) {
273 $parsableanswerdef = '{' . $wrapped->defaultgrade . ':';
274 switch ($wrapped->qtype) {
59a3fcd3
TH
275 case 'multichoice':
276 $parsableanswerdef .= 'MULTICHOICE:';
277 break;
278 case 'shortanswer':
279 $parsableanswerdef .= 'SHORTANSWER:';
280 break;
281 case 'numerical':
282 $parsableanswerdef .= 'NUMERICAL:';
283 break;
284 default:
285 print_error('unknownquestiontype', 'question', '',
286 $wrapped->qtype);
c6fc9988 287 }
59a3fcd3 288 $separator = '';
fe6ce234
DC
289 foreach ($wrapped->options->answers as $subanswer) {
290 $parsableanswerdef .= $separator
291 . '%' . round(100*$subanswer->fraction) . '%';
292 $parsableanswerdef .= $subanswer->answer;
293 if (!empty($wrapped->options->tolerance)) {
294 // Special for numerical answers:
295 $parsableanswerdef .= ":{$wrapped->options->tolerance}";
296 // We only want tolerance for the first alternative, it will
297 // be applied to all of the alternatives.
298 unset($wrapped->options->tolerance);
299 }
300 if ($subanswer->feedback) {
301 $parsableanswerdef .= "#$subanswer->feedback";
302 }
303 $separator = '~';
c6fc9988 304 }
fe6ce234
DC
305 $parsableanswerdef .= '}';
306 // Fix the questiontext fields of old questions
59a3fcd3
TH
307 $DB->set_field('question', 'questiontext', $parsableanswerdef,
308 array('id' => $wrapped->id));
fe6ce234
DC
309 } else {
310 $parsableanswerdef = str_replace('&#', '&\#', $wrapped->questiontext);
c6fc9988 311 }
59a3fcd3
TH
312 $question->questiontext = str_replace("{#$key}", $parsableanswerdef,
313 $question->questiontext);
c6fc9988 314 }
c6fc9988 315 }
316 }
347fb175 317
705b5874 318 // set default to $questiondisplay questions elements
59a3fcd3 319 if ($this->reload) {
fe6ce234 320 if (isset($this->questiondisplay->options->questions)) {
59a3fcd3 321 $subquestions = fullclone($this->questiondisplay->options->questions);
fe6ce234 322 if (count($subquestions)) {
59a3fcd3 323 $sub = 1;
fe6ce234 324 foreach ($subquestions as $subquestion) {
59a3fcd3 325 $prefix = 'sub_'.$sub.'_';
fe6ce234
DC
326
327 // validate parameters
328 $answercount = 0;
329 $maxgrade = false;
330 $maxfraction = -1;
59a3fcd3 331 if ($subquestion->qtype == 'shortanswer') {
fe6ce234 332 switch ($subquestion->usecase) {
59a3fcd3
TH
333 case '1':
334 $default_values[$prefix.'usecase'] =
335 get_string('caseyes', 'qtype_shortanswer');
336 break;
337 case '0':
338 default :
339 $default_values[$prefix.'usecase'] =
340 get_string('caseno', 'qtype_shortanswer');
fe6ce234 341 }
fd97082c 342 }
fd97082c 343
59a3fcd3
TH
344 if ($subquestion->qtype == 'multichoice') {
345 $default_values[$prefix.'layout'] = $subquestion->layout;
fe6ce234 346 switch ($subquestion->layout) {
59a3fcd3
TH
347 case '0':
348 $default_values[$prefix.'layout'] =
349 get_string('layoutselectinline', 'qtype_multianswer');
350 break;
351 case '1':
352 $default_values[$prefix.'layout'] =
353 get_string('layoutvertical', 'qtype_multianswer');
354 break;
355 case '2':
356 $default_values[$prefix.'layout'] =
357 get_string('layouthorizontal', 'qtype_multianswer');
358 break;
359 default:
360 $default_values[$prefix.'layout'] =
361 get_string('layoutundefined', 'qtype_multianswer');
fe6ce234 362 }
aeb15530 363 }
59a3fcd3
TH
364 foreach ($subquestion->answer as $key => $answer) {
365 if ($subquestion->qtype == 'numerical' && $key == 0) {
366 $default_values[$prefix.'tolerance['.$key.']'] =
367 $subquestion->tolerance[0];
705b5874 368 }
fe6ce234
DC
369 $trimmedanswer = trim($answer);
370 if ($trimmedanswer !== '') {
371 $answercount++;
59a3fcd3
TH
372 if ($subquestion->qtype == 'numerical' &&
373 !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) {
374 $this->_form->setElementError($prefix.'answer['.$key.']',
375 get_string('answermustbenumberorstar',
376 'qtype_numerical'));
fe6ce234
DC
377 }
378 if ($subquestion->fraction[$key] == 1) {
379 $maxgrade = true;
380 }
381 if ($subquestion->fraction[$key] > $maxfraction) {
59a3fcd3 382 $maxfraction = $subquestion->fraction[$key];
fe6ce234 383 }
f34488b2 384 }
fe6ce234 385
59a3fcd3
TH
386 $default_values[$prefix.'answer['.$key.']'] =
387 htmlspecialchars($answer);
fe6ce234
DC
388 }
389 if ($answercount == 0) {
59a3fcd3
TH
390 if ($subquestion->qtype == 'multichoice') {
391 $this->_form->setElementError($prefix.'answer[0]',
392 get_string('notenoughanswers', 'qtype_multichoice', 2));
fe6ce234 393 } else {
59a3fcd3
TH
394 $this->_form->setElementError($prefix.'answer[0]',
395 get_string('notenoughanswers', 'question', 1));
705b5874 396 }
397 }
fe6ce234 398 if ($maxgrade == false) {
59a3fcd3
TH
399 $this->_form->setElementError($prefix.'fraction[0]',
400 get_string('fractionsnomax', 'question'));
705b5874 401 }
59a3fcd3 402 foreach ($subquestion->feedback as $key => $answer) {
f34488b2 403
59a3fcd3
TH
404 $default_values[$prefix.'feedback['.$key.']'] =
405 htmlspecialchars ($answer['text']);
fe6ce234 406 }
59a3fcd3
TH
407 foreach ($subquestion->fraction as $key => $answer) {
408 $default_values[$prefix.'fraction['.$key.']'] = $answer;
fe6ce234 409 }
f34488b2 410
fe6ce234
DC
411 $sub++;
412 }
705b5874 413 }
414 }
415 }
59a3fcd3
TH
416 $default_values['alertas']= "<strong>".get_string('questioninquiz', 'qtype_multianswer').
417 "</strong>";
347fb175 418
59a3fcd3 419 if ($default_values != "") {
705b5874 420 $question = (object)((array)$question + $default_values);
421 }
120e5cbf 422 parent::set_data($question);
423 }
424
59a3fcd3 425 public function validation($data, $files) {
a78890d5 426 $errors = parent::validation($data, $files);
8e8f9f3d 427
0bc0a265 428 $questiondisplay = qtype_multianswer_extract_question($data['questiontext']);
f34488b2 429
347fb175 430 if (isset($questiondisplay->options->questions)) {
59a3fcd3 431 $subquestions = fullclone($questiondisplay->options->questions);
705b5874 432 if (count($subquestions)) {
59a3fcd3 433 $sub = 1;
f34488b2 434 foreach ($subquestions as $subquestion) {
59a3fcd3 435 $prefix = 'sub_'.$sub.'_';
705b5874 436 $answercount = 0;
437 $maxgrade = false;
438 $maxfraction = -1;
59a3fcd3
TH
439 if (isset($this->savedquestiondisplay->options->questions[$sub]->qtype) &&
440 $this->savedquestiondisplay->options->questions[$sub]->qtype !=
441 $questiondisplay->options->questions[$sub]->qtype) {
442 $storemess = " STORED QTYPE ".question_bank::get_qtype_name(
443 $this->savedquestiondisplay->options->questions[$sub]->qtype);
444 }
445 foreach ($subquestion->answer as $key => $answer) {
705b5874 446 $trimmedanswer = trim($answer);
447 if ($trimmedanswer !== '') {
448 $answercount++;
59a3fcd3
TH
449 if ($subquestion->qtype == 'numerical' &&
450 !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) {
451 $errors[$prefix.'answer['.$key.']'] =
452 get_string('answermustbenumberorstar', 'qtype_numerical');
fe6ce234 453 }
705b5874 454 if ($subquestion->fraction[$key] == 1) {
455 $maxgrade = true;
f34488b2 456 }
705b5874 457 if ($subquestion->fraction[$key] > $maxfraction) {
59a3fcd3 458 $maxfraction = $subquestion->fraction[$key];
705b5874 459 }
f34488b2 460 }
461 }
59a3fcd3
TH
462 if ($answercount == 0) {
463 if ($subquestion->qtype == 'multichoice') {
464 $errors[$prefix.'answer[0]'] =
465 get_string('notenoughanswers', 'qtype_multichoice', 2);
466 } else {
467 $errors[$prefix.'answer[0]'] =
468 get_string('notenoughanswers', 'question', 1);
705b5874 469 }
470 }
471 if ($maxgrade == false) {
59a3fcd3
TH
472 $errors[$prefix.'fraction[0]'] =
473 get_string('fractionsnomax', 'question');
f34488b2 474 }
475 $sub++;
705b5874 476 }
477 } else {
59a3fcd3 478 $errors['questiontext'] = get_string('questionsmissing', 'qtype_multianswer');
705b5874 479 }
120e5cbf 480 }
705b5874 481
59a3fcd3
TH
482 if (($this->negative_diff > 0 || $this->used_in_quiz &&
483 ($this->negative_diff > 0 || $this->negative_diff < 0 ||
484 $this->qtype_change))&& $this->confirm == 0) {
485 $errors['confirm'] =
486 get_string('confirmsave', 'qtype_multianswer', $this->negative_diff);
487 }
fe6ce234
DC
488
489 return $errors;
c6fc9988 490 }
271ffe3f 491
59a3fcd3 492 public function qtype() {
271ffe3f 493 return 'multianswer';
494 }
495}