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