Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]
1 <?php 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 17 /** 18 * Defines the editing form for the multi-answer question type. 19 * 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 24 */ 25 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 require_once($CFG->dirroot . '/question/type/numerical/questiontype.php'); 30 31 32 /** 33 * Form for editing multi-answer questions. 34 * 35 * @copyright 2007 Jamie Pratt me@jamiep.org 36 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 37 */ 38 class qtype_multianswer_edit_form extends question_edit_form { 39 40 // The variable $questiondisplay will contain the qtype_multianswer_extract_question from 41 // the questiontext. 42 public $questiondisplay; 43 // The variable $savedquestiondisplay will contain the qtype_multianswer_extract_question 44 // from the questiontext in database. 45 public $savedquestion; 46 public $savedquestiondisplay; 47 /** @var bool this question is used in quiz */ 48 public $usedinquiz = false; 49 /** @var bool the qtype has been changed */ 50 public $qtypechange = false; 51 /** @var integer number of questions that have been deleted */ 52 public $negativediff = 0; 53 /** @var integer number of quiz that used this question */ 54 public $nbofquiz = 0; 55 /** @var integer number of attempts that used this question */ 56 public $nbofattempts = 0; 57 public $confirm = 0; 58 public $reload = false; 59 /** @var qtype_numerical_answer_processor used when validating numerical answers. */ 60 protected $ap = null; 61 62 63 public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) { 64 global $SESSION, $CFG, $DB; 65 $this->regenerate = true; 66 $this->reload = optional_param('reload', false, PARAM_BOOL); 67 68 $this->usedinquiz = false; 69 70 if (isset($question->id) && $question->id != 0) { 71 // TODO MDL-43779 should not have quiz-specific code here. 72 $this->savedquestiondisplay = fullclone($question); 73 $this->nbofquiz = $DB->count_records('quiz_slots', array('questionid' => $question->id)); 74 $this->usedinquiz = $this->nbofquiz > 0; 75 $this->nbofattempts = $DB->count_records_sql(" 76 SELECT count(1) 77 FROM {quiz_slots} slot 78 JOIN {quiz_attempts} quiza ON quiza.quiz = slot.quizid 79 WHERE slot.questionid = ? 80 AND quiza.preview = 0", array($question->id)); 81 } 82 83 parent::__construct($submiturl, $question, $category, $contexts, $formeditable); 84 } 85 86 protected function definition_inner($mform) { 87 $mform->addElement('hidden', 'reload', 1); 88 $mform->setType('reload', PARAM_INT); 89 90 // Remove meaningless defaultmark field. 91 $mform->removeElement('defaultmark'); 92 $this->confirm = optional_param('confirm', false, PARAM_BOOL); 93 94 // Display the questions from questiontext. 95 if ($questiontext = optional_param_array('questiontext', false, PARAM_RAW)) { 96 $this->questiondisplay = fullclone(qtype_multianswer_extract_question($questiontext)); 97 98 } else { 99 if (!$this->reload && !empty($this->savedquestiondisplay->id)) { 100 // Use database data as this is first pass 101 // question->id == 0 so no stored datasets. 102 $this->questiondisplay = fullclone($this->savedquestiondisplay); 103 foreach ($this->questiondisplay->options->questions as $subquestion) { 104 if (!empty($subquestion)) { 105 $subquestion->answer = array(''); 106 foreach ($subquestion->options->answers as $ans) { 107 $subquestion->answer[] = $ans->answer; 108 } 109 } 110 } 111 } else { 112 $this->questiondisplay = ""; 113 } 114 } 115 116 if (isset($this->savedquestiondisplay->options->questions) && 117 is_array($this->savedquestiondisplay->options->questions)) { 118 $countsavedsubquestions = 0; 119 foreach ($this->savedquestiondisplay->options->questions as $subquestion) { 120 if (!empty($subquestion)) { 121 $countsavedsubquestions++; 122 } 123 } 124 } else { 125 $countsavedsubquestions = 0; 126 } 127 if ($this->reload) { 128 if (isset($this->questiondisplay->options->questions) && 129 is_array($this->questiondisplay->options->questions)) { 130 $countsubquestions = 0; 131 foreach ($this->questiondisplay->options->questions as $subquestion) { 132 if (!empty($subquestion)) { 133 $countsubquestions++; 134 } 135 } 136 } else { 137 $countsubquestions = 0; 138 } 139 } else { 140 $countsubquestions = $countsavedsubquestions; 141 } 142 143 $mform->addElement('submit', 'analyzequestion', 144 get_string('decodeverifyquestiontext', 'qtype_multianswer')); 145 $mform->registerNoSubmitButton('analyzequestion'); 146 if ($this->reload) { 147 for ($sub = 1; $sub <= $countsubquestions; $sub++) { 148 149 if (isset($this->questiondisplay->options->questions[$sub]->qtype)) { 150 $this->editas[$sub] = $this->questiondisplay->options->questions[$sub]->qtype; 151 } else { 152 $this->editas[$sub] = optional_param('sub_'.$sub.'_qtype', 'unknown type', PARAM_PLUGIN); 153 } 154 155 $storemess = ''; 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->qtypechange = true; 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')); 164 } 165 $mform->addElement('header', 'subhdr'.$sub, get_string('questionno', 'question', 166 '{#'.$sub.'}').' '.question_bank::get_qtype_name( 167 $this->questiondisplay->options->questions[$sub]->qtype).$storemess); 168 169 $mform->addElement('static', 'sub_'.$sub.'_questiontext', 170 get_string('questiondefinition', 'qtype_multianswer')); 171 172 if (isset ($this->questiondisplay->options->questions[$sub]->questiontext)) { 173 $mform->setDefault('sub_'.$sub.'_questiontext', 174 $this->questiondisplay->options->questions[$sub]->questiontext['text']); 175 } 176 177 $mform->addElement('static', 'sub_'.$sub.'_defaultmark', 178 get_string('defaultmark', 'question')); 179 $mform->setDefault('sub_'.$sub.'_defaultmark', 180 $this->questiondisplay->options->questions[$sub]->defaultmark); 181 182 if ($this->questiondisplay->options->questions[$sub]->qtype == 'shortanswer') { 183 $mform->addElement('static', 'sub_'.$sub.'_usecase', 184 get_string('casesensitive', 'qtype_shortanswer')); 185 } 186 187 if ($this->questiondisplay->options->questions[$sub]->qtype == 'multichoice') { 188 $mform->addElement('static', 'sub_'.$sub.'_layout', 189 get_string('layout', 'qtype_multianswer')); 190 $mform->addElement('static', 'sub_'.$sub.'_shuffleanswers', 191 get_string('shuffleanswers', 'qtype_multichoice')); 192 } 193 194 foreach ($this->questiondisplay->options->questions[$sub]->answer as $key => $ans) { 195 $mform->addElement('static', 'sub_'.$sub.'_answer['.$key.']', 196 get_string('answer', 'question')); 197 198 if ($this->questiondisplay->options->questions[$sub]->qtype == 'numerical' && 199 $key == 0) { 200 $mform->addElement('static', 'sub_'.$sub.'_tolerance['.$key.']', 201 get_string('acceptederror', 'qtype_numerical')); 202 } 203 204 $mform->addElement('static', 'sub_'.$sub.'_fraction['.$key.']', 205 get_string('grade')); 206 207 $mform->addElement('static', 'sub_'.$sub.'_feedback['.$key.']', 208 get_string('feedback', 'question')); 209 } 210 } 211 212 $this->negativediff = $countsavedsubquestions - $countsubquestions; 213 if (($this->negativediff > 0) ||$this->qtypechange || 214 ($this->usedinquiz && $this->negativediff != 0)) { 215 $mform->addElement('header', 'additemhdr', 216 get_string('warningquestionmodified', 'qtype_multianswer')); 217 } 218 if ($this->negativediff > 0) { 219 $mform->addElement('static', 'alert1', "<strong>". 220 get_string('questiondeleted', 'qtype_multianswer')."</strong>", 221 get_string('questionsless', 'qtype_multianswer', $this->negativediff)); 222 } 223 if ($this->qtypechange) { 224 $mform->addElement('static', 'alert1', "<strong>". 225 get_string('questiontypechanged', 'qtype_multianswer')."</strong>", 226 get_string('questiontypechangedcomment', 'qtype_multianswer')); 227 } 228 } 229 if ($this->usedinquiz) { 230 if ($this->negativediff < 0) { 231 $diff = $countsubquestions - $countsavedsubquestions; 232 $mform->addElement('static', 'alert1', "<strong>". 233 get_string('questionsadded', 'qtype_multianswer')."</strong>", 234 "<strong>".get_string('questionsmore', 'qtype_multianswer', $diff). 235 "</strong>"); 236 } 237 $a = new stdClass(); 238 $a->nb_of_quiz = $this->nbofquiz; 239 $a->nb_of_attempts = $this->nbofattempts; 240 $mform->addElement('header', 'additemhdr2', 241 get_string('questionusedinquiz', 'qtype_multianswer', $a)); 242 $mform->addElement('static', 'alertas', 243 get_string('youshouldnot', 'qtype_multianswer')); 244 } 245 if (($this->negativediff > 0 || $this->usedinquiz && 246 ($this->negativediff > 0 || $this->negativediff < 0 || $this->qtypechange)) && 247 $this->reload) { 248 $mform->addElement('header', 'additemhdr', 249 get_string('questionsaveasedited', 'qtype_multianswer')); 250 $mform->addElement('checkbox', 'confirm', '', 251 get_string('confirmquestionsaveasedited', 'qtype_multianswer')); 252 $mform->setDefault('confirm', 0); 253 } else { 254 $mform->addElement('hidden', 'confirm', 0); 255 $mform->setType('confirm', PARAM_BOOL); 256 } 257 258 $this->add_interactive_settings(true, true); 259 } 260 261 262 public function set_data($question) { 263 global $DB; 264 $defaultvalues = array(); 265 if (isset($question->id) and $question->id and $question->qtype && 266 $question->questiontext) { 267 268 foreach ($question->options->questions as $key => $wrapped) { 269 if (!empty($wrapped)) { 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->defaultmark . ':'; 274 switch ($wrapped->qtype) { 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); 287 } 288 $separator = ''; 289 foreach ($wrapped->options->answers as $subanswer) { 290 $parsableanswerdef .= $separator 291 . '%' . round(100 * $subanswer->fraction) . '%'; 292 if (is_array($subanswer->answer)) { 293 $parsableanswerdef .= $subanswer->answer['text']; 294 } else { 295 $parsableanswerdef .= $subanswer->answer; 296 } 297 if (!empty($wrapped->options->tolerance)) { 298 // Special for numerical answers. 299 $parsableanswerdef .= ":{$wrapped->options->tolerance}"; 300 // We only want tolerance for the first alternative, it will 301 // be applied to all of the alternatives. 302 unset($wrapped->options->tolerance); 303 } 304 if ($subanswer->feedback) { 305 $parsableanswerdef .= "#{$subanswer->feedback}"; 306 } 307 $separator = '~'; 308 } 309 $parsableanswerdef .= '}'; 310 // Fix the questiontext fields of old questions. 311 $DB->set_field('question', 'questiontext', $parsableanswerdef, 312 array('id' => $wrapped->id)); 313 } else { 314 $parsableanswerdef = str_replace('&#', '&\#', $wrapped->questiontext); 315 } 316 $question->questiontext = str_replace("{#$key}", $parsableanswerdef, 317 $question->questiontext); 318 } 319 } 320 } 321 322 // Set default to $questiondisplay questions elements. 323 if ($this->reload) { 324 if (isset($this->questiondisplay->options->questions)) { 325 $subquestions = fullclone($this->questiondisplay->options->questions); 326 if (count($subquestions)) { 327 $sub = 1; 328 foreach ($subquestions as $subquestion) { 329 $prefix = 'sub_'.$sub.'_'; 330 331 // Validate parameters. 332 $answercount = 0; 333 $maxgrade = false; 334 $maxfraction = -1; 335 if ($subquestion->qtype == 'shortanswer') { 336 switch ($subquestion->usecase) { 337 case '1': 338 $defaultvalues[$prefix.'usecase'] = 339 get_string('caseyes', 'qtype_shortanswer'); 340 break; 341 case '0': 342 default : 343 $defaultvalues[$prefix.'usecase'] = 344 get_string('caseno', 'qtype_shortanswer'); 345 } 346 } 347 348 if ($subquestion->qtype == 'multichoice') { 349 $defaultvalues[$prefix.'layout'] = $subquestion->layout; 350 if ($subquestion->single == 1) { 351 switch ($subquestion->layout) { 352 case '0': 353 $defaultvalues[$prefix.'layout'] = 354 get_string('layoutselectinline', 'qtype_multianswer'); 355 break; 356 case '1': 357 $defaultvalues[$prefix.'layout'] = 358 get_string('layoutvertical', 'qtype_multianswer'); 359 break; 360 case '2': 361 $defaultvalues[$prefix.'layout'] = 362 get_string('layouthorizontal', 'qtype_multianswer'); 363 break; 364 default: 365 $defaultvalues[$prefix.'layout'] = 366 get_string('layoutundefined', 'qtype_multianswer'); 367 } 368 } else { 369 switch ($subquestion->layout) { 370 case '1': 371 $defaultvalues[$prefix.'layout'] = 372 get_string('layoutmultiple_vertical', 'qtype_multianswer'); 373 break; 374 case '2': 375 $defaultvalues[$prefix.'layout'] = 376 get_string('layoutmultiple_horizontal', 'qtype_multianswer'); 377 break; 378 default: 379 $defaultvalues[$prefix.'layout'] = 380 get_string('layoutundefined', 'qtype_multianswer'); 381 } 382 } 383 if ($subquestion->shuffleanswers ) { 384 $defaultvalues[$prefix.'shuffleanswers'] = get_string('yes', 'moodle'); 385 } else { 386 $defaultvalues[$prefix.'shuffleanswers'] = get_string('no', 'moodle'); 387 } 388 } 389 foreach ($subquestion->answer as $key => $answer) { 390 if ($subquestion->qtype == 'numerical' && $key == 0) { 391 $defaultvalues[$prefix.'tolerance['.$key.']'] = 392 $subquestion->tolerance[0]; 393 } 394 if (is_array($answer)) { 395 $answer = $answer['text']; 396 } 397 $trimmedanswer = trim($answer); 398 if ($trimmedanswer !== '') { 399 $answercount++; 400 if ($subquestion->qtype == 'numerical' && 401 !(qtype_numerical::is_valid_number($trimmedanswer) || $trimmedanswer == '*')) { 402 $this->_form->setElementError($prefix.'answer['.$key.']', 403 get_string('answermustbenumberorstar', 404 'qtype_numerical')); 405 } 406 if ($subquestion->fraction[$key] == 1) { 407 $maxgrade = true; 408 } 409 if ($subquestion->fraction[$key] > $maxfraction) { 410 $maxfraction = $subquestion->fraction[$key]; 411 } 412 // For 'multiresponse' we are OK if there is at least one fraction > 0. 413 if ($subquestion->qtype == 'multichoice' && $subquestion->single == 0 && 414 $subquestion->fraction[$key] > 0) { 415 $maxgrade = true; 416 } 417 } 418 419 $defaultvalues[$prefix.'answer['.$key.']'] = 420 htmlspecialchars($answer); 421 } 422 if ($answercount == 0) { 423 if ($subquestion->qtype == 'multichoice') { 424 $this->_form->setElementError($prefix.'answer[0]', 425 get_string('notenoughanswers', 'qtype_multichoice', 2)); 426 } else { 427 $this->_form->setElementError($prefix.'answer[0]', 428 get_string('notenoughanswers', 'question', 1)); 429 } 430 } 431 if ($maxgrade == false) { 432 $this->_form->setElementError($prefix.'fraction[0]', 433 get_string('fractionsnomax', 'question')); 434 } 435 foreach ($subquestion->feedback as $key => $answer) { 436 437 $defaultvalues[$prefix.'feedback['.$key.']'] = 438 htmlspecialchars ($answer['text']); 439 } 440 foreach ($subquestion->fraction as $key => $answer) { 441 $defaultvalues[$prefix.'fraction['.$key.']'] = $answer; 442 } 443 444 $sub++; 445 } 446 } 447 } 448 } 449 $defaultvalues['alertas'] = "<strong>".get_string('questioninquiz', 'qtype_multianswer'). 450 "</strong>"; 451 452 if ($defaultvalues != "") { 453 $question = (object)((array)$question + $defaultvalues); 454 } 455 $question = $this->data_preprocessing_hints($question, true, true); 456 parent::set_data($question); 457 } 458 459 public function validation($data, $files) { 460 $errors = parent::validation($data, $files); 461 462 $questiondisplay = qtype_multianswer_extract_question($data['questiontext']); 463 464 $errors = array_merge($errors, qtype_multianswer_validate_question($questiondisplay)); 465 466 if (($this->negativediff > 0 || $this->usedinquiz && 467 ($this->negativediff > 0 || $this->negativediff < 0 || 468 $this->qtypechange)) && !$this->confirm) { 469 $errors['confirm'] = 470 get_string('confirmsave', 'qtype_multianswer', $this->negativediff); 471 } 472 473 return $errors; 474 } 475 476 public function qtype() { 477 return 'multianswer'; 478 } 479 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body