weekly release 2.2dev
[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 */
ab50232b 36class qtype_multianswer_edit_form extends question_edit_form {
271ffe3f 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
ab50232b
TH
86 // Remove meaningless defaultmark field.
87 $mform->removeElement('defaultmark');
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) {
59a3fcd3 144 for ($sub = 1; $sub <= $countsubquestions; $sub++) {
aeb15530 145
ab50232b 146 $this->editas[$sub] = 'unknown type';
59a3fcd3 147 if (isset($this->questiondisplay->options->questions[$sub]->qtype)) {
ab50232b 148 $this->editas[$sub] = $this->questiondisplay->options->questions[$sub]->qtype;
d3e3bd6c 149 } else if (optional_param('sub_'.$sub."_".'qtype', '', PARAM_RAW) != '') {
150 $this->editas[$sub] = optional_param('sub_'.$sub."_".'qtype', '', PARAM_RAW);
fd97082c 151 }
928e7d2a 152
d3e3bd6c 153 $storemess = '';
59a3fcd3
TH
154 if (isset($this->savedquestiondisplay->options->questions[$sub]->qtype) &&
155 $this->savedquestiondisplay->options->questions[$sub]->qtype !=
156 $this->questiondisplay->options->questions[$sub]->qtype) {
157 $this->qtype_change = true;
635971c7
TH
158 $storemess = ' ' . html_writer::tag('span', get_string(
159 'storedqtype', 'qtype_multianswer', question_bank::get_qtype_name(
160 $this->savedquestiondisplay->options->questions[$sub]->qtype)),
161 array('class' => 'error'));
59a3fcd3 162 }
aeb15530 163
5e8a85aa 164 $mform->addElement('header', 'subhdr'.$sub, get_string('questionno', 'question',
59a3fcd3
TH
165 '{#'.$sub.'}').'&nbsp;'.question_bank::get_qtype_name(
166 $this->questiondisplay->options->questions[$sub]->qtype).$storemess);
aeb15530 167
59a3fcd3 168 $mform->addElement('static', 'sub_'.$sub."_".'questiontext',
928e7d2a 169 get_string('questiondefinition', 'qtype_multianswer'));
aeb15530 170
59a3fcd3
TH
171 if (isset ($this->questiondisplay->options->questions[$sub]->questiontext)) {
172 $mform->setDefault('sub_'.$sub."_".'questiontext',
173 $this->questiondisplay->options->questions[$sub]->questiontext['text']);
f34488b2 174 }
aeb15530 175
ab50232b
TH
176 $mform->addElement('static', 'sub_'.$sub."_".'defaultmark',
177 get_string('defaultmark', 'question'));
178 $mform->setDefault('sub_'.$sub."_".'defaultmark',
179 $this->questiondisplay->options->questions[$sub]->defaultmark);
aeb15530 180
59a3fcd3
TH
181 if ($this->questiondisplay->options->questions[$sub]->qtype == 'shortanswer') {
182 $mform->addElement('static', 'sub_'.$sub."_".'usecase',
ab50232b 183 get_string('casesensitive', 'qtype_shortanswer'));
fe6ce234 184 }
aeb15530 185
59a3fcd3
TH
186 if ($this->questiondisplay->options->questions[$sub]->qtype == 'multichoice') {
187 $mform->addElement('static', 'sub_'.$sub."_".'layout',
928e7d2a 188 get_string('layout', 'qtype_multianswer'));
fe6ce234 189 }
aeb15530 190
928e7d2a 191 foreach ($this->questiondisplay->options->questions[$sub]->answer as $key => $ans) {
59a3fcd3 192 $mform->addElement('static', 'sub_'.$sub."_".'answer['.$key.']',
928e7d2a 193 get_string('answer', 'question'));
aeb15530 194
59a3fcd3
TH
195 if ($this->questiondisplay->options->questions[$sub]->qtype == 'numerical' &&
196 $key == 0) {
197 $mform->addElement('static', 'sub_'.$sub."_".'tolerance['.$key.']',
ab50232b 198 get_string('acceptederror', 'qtype_numerical'));
d3e3bd6c 199 }
aeb15530 200
59a3fcd3
TH
201 $mform->addElement('static', 'sub_'.$sub."_".'fraction['.$key.']',
202 get_string('grade'));
aeb15530 203
59a3fcd3
TH
204 $mform->addElement('static', 'sub_'.$sub."_".'feedback['.$key.']',
205 get_string('feedback', 'question'));
d3e3bd6c 206 }
f34488b2 207 }
928e7d2a 208
59a3fcd3
TH
209 $this->negative_diff = $countsavedsubquestions - $countsubquestions;
210 if (($this->negative_diff > 0) ||$this->qtype_change ||
211 ($this->used_in_quiz && $this->negative_diff != 0)) {
212 $mform->addElement('header', 'additemhdr',
213 get_string('warningquestionmodified', 'qtype_multianswer'));
fe6ce234 214 }
59a3fcd3
TH
215 if ($this->negative_diff > 0) {
216 $mform->addElement('static', 'alert1', "<strong>".
217 get_string('questiondeleted', 'qtype_multianswer')."</strong>",
218 get_string('questionsless', 'qtype_multianswer', $this->negative_diff));
d3e3bd6c 219 }
59a3fcd3
TH
220 if ($this->qtype_change) {
221 $mform->addElement('static', 'alert1', "<strong>".
222 get_string('questiontypechanged', 'qtype_multianswer')."</strong>",
223 get_string('questiontypechangedcomment', 'qtype_multianswer'));
d3e3bd6c 224 }
d3e3bd6c 225 }
59a3fcd3
TH
226 if ($this->used_in_quiz) {
227 if ($this->negative_diff < 0) {
c4979f02 228 $diff = $countsubquestions - $countsavedsubquestions;
59a3fcd3
TH
229 $mform->addElement('static', 'alert1', "<strong>".
230 get_string('questionsadded', 'qtype_multianswer')."</strong>",
231 "<strong>".get_string('questionsmore', 'qtype_multianswer', $diff).
232 "</strong>");
c4979f02 233 }
59a3fcd3 234 $a = new stdClass();
c4979f02
PP
235 $a->nb_of_quiz = $this->nb_of_quiz;
236 $a->nb_of_attempts = $this->nb_of_attempts;
59a3fcd3
TH
237 $mform->addElement('header', 'additemhdr2',
238 get_string('questionusedinquiz', 'qtype_multianswer', $a));
239 $mform->addElement('static', 'alertas',
240 get_string('youshouldnot', 'qtype_multianswer'));
d3e3bd6c 241 }
59a3fcd3
TH
242 if (($this->negative_diff > 0 || $this->used_in_quiz &&
243 ($this->negative_diff > 0 || $this->negative_diff < 0 || $this->qtype_change)) &&
244 $this->reload) {
245 $mform->addElement('header', 'additemhdr',
246 get_string('questionsaveasedited', 'qtype_multianswer'));
247 $mform->addElement('checkbox', 'confirm', '',
248 get_string('confirmquestionsaveasedited', 'qtype_multianswer'));
d3e3bd6c 249 $mform->setDefault('confirm', 0);
59a3fcd3
TH
250 } else {
251 $mform->addElement('hidden', 'confirm', 0);
705b5874 252 }
347fb175 253
120e5cbf 254 }
705b5874 255
f34488b2 256
59a3fcd3 257 public function set_data($question) {
f34488b2 258 global $DB;
59a3fcd3
TH
259 $default_values = array();
260 if (isset($question->id) and $question->id and $question->qtype &&
261 $question->questiontext) {
c6fc9988 262
263 foreach ($question->options->questions as $key => $wrapped) {
59a3fcd3 264 if (!empty($wrapped)) {
fe6ce234
DC
265 // The old way of restoring the definitions is kept to gradually
266 // update all multianswer questions
267 if (empty($wrapped->questiontext)) {
ab50232b 268 $parsableanswerdef = '{' . $wrapped->defaultmark . ':';
fe6ce234 269 switch ($wrapped->qtype) {
59a3fcd3
TH
270 case 'multichoice':
271 $parsableanswerdef .= 'MULTICHOICE:';
272 break;
273 case 'shortanswer':
274 $parsableanswerdef .= 'SHORTANSWER:';
275 break;
276 case 'numerical':
277 $parsableanswerdef .= 'NUMERICAL:';
278 break;
279 default:
280 print_error('unknownquestiontype', 'question', '',
281 $wrapped->qtype);
c6fc9988 282 }
59a3fcd3 283 $separator = '';
fe6ce234
DC
284 foreach ($wrapped->options->answers as $subanswer) {
285 $parsableanswerdef .= $separator
286 . '%' . round(100*$subanswer->fraction) . '%';
287 $parsableanswerdef .= $subanswer->answer;
288 if (!empty($wrapped->options->tolerance)) {
289 // Special for numerical answers:
290 $parsableanswerdef .= ":{$wrapped->options->tolerance}";
291 // We only want tolerance for the first alternative, it will
292 // be applied to all of the alternatives.
293 unset($wrapped->options->tolerance);
294 }
295 if ($subanswer->feedback) {
296 $parsableanswerdef .= "#$subanswer->feedback";
297 }
298 $separator = '~';
c6fc9988 299 }
fe6ce234
DC
300 $parsableanswerdef .= '}';
301 // Fix the questiontext fields of old questions
59a3fcd3
TH
302 $DB->set_field('question', 'questiontext', $parsableanswerdef,
303 array('id' => $wrapped->id));
fe6ce234
DC
304 } else {
305 $parsableanswerdef = str_replace('&#', '&\#', $wrapped->questiontext);
c6fc9988 306 }
59a3fcd3
TH
307 $question->questiontext = str_replace("{#$key}", $parsableanswerdef,
308 $question->questiontext);
c6fc9988 309 }
c6fc9988 310 }
311 }
347fb175 312
705b5874 313 // set default to $questiondisplay questions elements
59a3fcd3 314 if ($this->reload) {
fe6ce234 315 if (isset($this->questiondisplay->options->questions)) {
59a3fcd3 316 $subquestions = fullclone($this->questiondisplay->options->questions);
fe6ce234 317 if (count($subquestions)) {
59a3fcd3 318 $sub = 1;
fe6ce234 319 foreach ($subquestions as $subquestion) {
59a3fcd3 320 $prefix = 'sub_'.$sub.'_';
fe6ce234
DC
321
322 // validate parameters
323 $answercount = 0;
324 $maxgrade = false;
325 $maxfraction = -1;
59a3fcd3 326 if ($subquestion->qtype == 'shortanswer') {
fe6ce234 327 switch ($subquestion->usecase) {
59a3fcd3
TH
328 case '1':
329 $default_values[$prefix.'usecase'] =
330 get_string('caseyes', 'qtype_shortanswer');
331 break;
332 case '0':
333 default :
334 $default_values[$prefix.'usecase'] =
335 get_string('caseno', 'qtype_shortanswer');
fe6ce234 336 }
fd97082c 337 }
fd97082c 338
59a3fcd3
TH
339 if ($subquestion->qtype == 'multichoice') {
340 $default_values[$prefix.'layout'] = $subquestion->layout;
fe6ce234 341 switch ($subquestion->layout) {
59a3fcd3
TH
342 case '0':
343 $default_values[$prefix.'layout'] =
344 get_string('layoutselectinline', 'qtype_multianswer');
345 break;
346 case '1':
347 $default_values[$prefix.'layout'] =
348 get_string('layoutvertical', 'qtype_multianswer');
349 break;
350 case '2':
351 $default_values[$prefix.'layout'] =
352 get_string('layouthorizontal', 'qtype_multianswer');
353 break;
354 default:
355 $default_values[$prefix.'layout'] =
356 get_string('layoutundefined', 'qtype_multianswer');
fe6ce234 357 }
aeb15530 358 }
59a3fcd3
TH
359 foreach ($subquestion->answer as $key => $answer) {
360 if ($subquestion->qtype == 'numerical' && $key == 0) {
361 $default_values[$prefix.'tolerance['.$key.']'] =
362 $subquestion->tolerance[0];
705b5874 363 }
fe6ce234
DC
364 $trimmedanswer = trim($answer);
365 if ($trimmedanswer !== '') {
366 $answercount++;
59a3fcd3
TH
367 if ($subquestion->qtype == 'numerical' &&
368 !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) {
369 $this->_form->setElementError($prefix.'answer['.$key.']',
370 get_string('answermustbenumberorstar',
371 'qtype_numerical'));
fe6ce234
DC
372 }
373 if ($subquestion->fraction[$key] == 1) {
374 $maxgrade = true;
375 }
376 if ($subquestion->fraction[$key] > $maxfraction) {
59a3fcd3 377 $maxfraction = $subquestion->fraction[$key];
fe6ce234 378 }
f34488b2 379 }
fe6ce234 380
59a3fcd3
TH
381 $default_values[$prefix.'answer['.$key.']'] =
382 htmlspecialchars($answer);
fe6ce234
DC
383 }
384 if ($answercount == 0) {
59a3fcd3
TH
385 if ($subquestion->qtype == 'multichoice') {
386 $this->_form->setElementError($prefix.'answer[0]',
387 get_string('notenoughanswers', 'qtype_multichoice', 2));
fe6ce234 388 } else {
59a3fcd3
TH
389 $this->_form->setElementError($prefix.'answer[0]',
390 get_string('notenoughanswers', 'question', 1));
705b5874 391 }
392 }
fe6ce234 393 if ($maxgrade == false) {
59a3fcd3
TH
394 $this->_form->setElementError($prefix.'fraction[0]',
395 get_string('fractionsnomax', 'question'));
705b5874 396 }
59a3fcd3 397 foreach ($subquestion->feedback as $key => $answer) {
f34488b2 398
59a3fcd3
TH
399 $default_values[$prefix.'feedback['.$key.']'] =
400 htmlspecialchars ($answer['text']);
fe6ce234 401 }
59a3fcd3
TH
402 foreach ($subquestion->fraction as $key => $answer) {
403 $default_values[$prefix.'fraction['.$key.']'] = $answer;
fe6ce234 404 }
f34488b2 405
fe6ce234
DC
406 $sub++;
407 }
705b5874 408 }
409 }
410 }
59a3fcd3
TH
411 $default_values['alertas']= "<strong>".get_string('questioninquiz', 'qtype_multianswer').
412 "</strong>";
347fb175 413
59a3fcd3 414 if ($default_values != "") {
705b5874 415 $question = (object)((array)$question + $default_values);
416 }
120e5cbf 417 parent::set_data($question);
418 }
419
59a3fcd3 420 public function validation($data, $files) {
a78890d5 421 $errors = parent::validation($data, $files);
8e8f9f3d 422
0bc0a265 423 $questiondisplay = qtype_multianswer_extract_question($data['questiontext']);
f34488b2 424
347fb175 425 if (isset($questiondisplay->options->questions)) {
59a3fcd3 426 $subquestions = fullclone($questiondisplay->options->questions);
705b5874 427 if (count($subquestions)) {
59a3fcd3 428 $sub = 1;
f34488b2 429 foreach ($subquestions as $subquestion) {
59a3fcd3 430 $prefix = 'sub_'.$sub.'_';
705b5874 431 $answercount = 0;
432 $maxgrade = false;
433 $maxfraction = -1;
59a3fcd3
TH
434 if (isset($this->savedquestiondisplay->options->questions[$sub]->qtype) &&
435 $this->savedquestiondisplay->options->questions[$sub]->qtype !=
436 $questiondisplay->options->questions[$sub]->qtype) {
437 $storemess = " STORED QTYPE ".question_bank::get_qtype_name(
438 $this->savedquestiondisplay->options->questions[$sub]->qtype);
439 }
440 foreach ($subquestion->answer as $key => $answer) {
705b5874 441 $trimmedanswer = trim($answer);
442 if ($trimmedanswer !== '') {
443 $answercount++;
59a3fcd3
TH
444 if ($subquestion->qtype == 'numerical' &&
445 !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) {
446 $errors[$prefix.'answer['.$key.']'] =
447 get_string('answermustbenumberorstar', 'qtype_numerical');
fe6ce234 448 }
705b5874 449 if ($subquestion->fraction[$key] == 1) {
450 $maxgrade = true;
f34488b2 451 }
705b5874 452 if ($subquestion->fraction[$key] > $maxfraction) {
59a3fcd3 453 $maxfraction = $subquestion->fraction[$key];
705b5874 454 }
f34488b2 455 }
456 }
59a3fcd3
TH
457 if ($answercount == 0) {
458 if ($subquestion->qtype == 'multichoice') {
459 $errors[$prefix.'answer[0]'] =
460 get_string('notenoughanswers', 'qtype_multichoice', 2);
461 } else {
462 $errors[$prefix.'answer[0]'] =
463 get_string('notenoughanswers', 'question', 1);
705b5874 464 }
465 }
466 if ($maxgrade == false) {
59a3fcd3
TH
467 $errors[$prefix.'fraction[0]'] =
468 get_string('fractionsnomax', 'question');
f34488b2 469 }
470 $sub++;
705b5874 471 }
472 } else {
59a3fcd3 473 $errors['questiontext'] = get_string('questionsmissing', 'qtype_multianswer');
705b5874 474 }
120e5cbf 475 }
705b5874 476
59a3fcd3
TH
477 if (($this->negative_diff > 0 || $this->used_in_quiz &&
478 ($this->negative_diff > 0 || $this->negative_diff < 0 ||
479 $this->qtype_change))&& $this->confirm == 0) {
480 $errors['confirm'] =
481 get_string('confirmsave', 'qtype_multianswer', $this->negative_diff);
482 }
fe6ce234
DC
483
484 return $errors;
c6fc9988 485 }
271ffe3f 486
59a3fcd3 487 public function qtype() {
271ffe3f 488 return 'multianswer';
489 }
490}