Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 /** @var bool */ 62 public $regenerate; 63 /** @var array */ 64 public $editas; 65 66 public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) { 67 $this->regenerate = true; 68 $this->reload = optional_param('reload', false, PARAM_BOOL); 69 70 $this->usedinquiz = false; 71 72 if (isset($question->id) && $question->id != 0) { 73 // TODO MDL-43779 should not have quiz-specific code here. 74 $this->savedquestiondisplay = fullclone($question); 75 $questiondata = question_bank::load_question($question->id); 76 $this->nbofquiz = \qbank_usage\helper::get_question_entry_usage_count($questiondata); 77 $this->usedinquiz = $this->nbofquiz > 0; 78 $this->nbofattempts = \qbank_usage\helper::get_question_attempts_count_in_quiz((int)$question->id); 79 } 80 81 parent::__construct($submiturl, $question, $category, $contexts, $formeditable); 82 } 83 84 protected function definition_inner($mform) { 85 $mform->addElement('hidden', 'reload', 1); 86 $mform->setType('reload', PARAM_INT); 87 88 // Remove meaningless defaultmark field. 89 $mform->removeElement('defaultmark'); 90 $this->confirm = optional_param('confirm', false, PARAM_BOOL); 91 92 // Display the questions from questiontext. 93 if ($questiontext = optional_param_array('questiontext', false, PARAM_RAW)) { 94 $this->questiondisplay = fullclone(qtype_multianswer_extract_question($questiontext)); 95 96 } else { 97 if (!$this->reload && !empty($this->savedquestiondisplay->id)) { 98 // Use database data as this is first pass 99 // question->id == 0 so no stored datasets. 100 $this->questiondisplay = fullclone($this->savedquestiondisplay); 101 foreach ($this->questiondisplay->options->questions as $subquestion) { 102 if (!empty($subquestion)) { 103 $subquestion->answer = array(''); 104 foreach ($subquestion->options->answers as $ans) { 105 $subquestion->answer[] = $ans->answer; 106 } 107 } 108 } 109 } else { 110 $this->questiondisplay = ""; 111 } 112 } 113 114 if (isset($this->savedquestiondisplay->options->questions) && 115 is_array($this->savedquestiondisplay->options->questions)) { 116 $countsavedsubquestions = 0; 117 foreach ($this->savedquestiondisplay->options->questions as $subquestion) { 118 if (!empty($subquestion)) { 119 $countsavedsubquestions++; 120 } 121 } 122 } else { 123 $countsavedsubquestions = 0; 124 } 125 if ($this->reload) { 126 if (isset($this->questiondisplay->options->questions) && 127 is_array($this->questiondisplay->options->questions)) { 128 $countsubquestions = 0; 129 foreach ($this->questiondisplay->options->questions as $subquestion) { 130 if (!empty($subquestion)) { 131 $countsubquestions++; 132 } 133 } 134 } else { 135 $countsubquestions = 0; 136 } 137 } else { 138 $countsubquestions = $countsavedsubquestions; 139 } 140 141 $mform->addElement('submit', 'analyzequestion', 142 get_string('decodeverifyquestiontext', 'qtype_multianswer')); 143 $mform->registerNoSubmitButton('analyzequestion'); 144 if ($this->reload) { 145 for ($sub = 1; $sub <= $countsubquestions; $sub++) { 146 147 if (isset($this->questiondisplay->options->questions[$sub]->qtype)) { 148 $this->editas[$sub] = $this->questiondisplay->options->questions[$sub]->qtype; 149 } else { 150 $this->editas[$sub] = optional_param('sub_'.$sub.'_qtype', 'unknown type', PARAM_PLUGIN); 151 } 152 153 $storemess = ''; 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->savedquestiondisplay->options->questions[$sub]->qtype != 'subquestion_replacement') { 158 $this->qtypechange = true; 159 $storemess = ' ' . html_writer::tag('span', get_string( 160 'storedqtype', 'qtype_multianswer', question_bank::get_qtype_name( 161 $this->savedquestiondisplay->options->questions[$sub]->qtype)), 162 array('class' => 'error')); 163 } 164 $mform->addElement('header', 'subhdr'.$sub, get_string('questionno', 'question', 165 '{#'.$sub.'}').' '.question_bank::get_qtype_name( 166 $this->questiondisplay->options->questions[$sub]->qtype).$storemess); 167 168 $mform->addElement('static', 'sub_'.$sub.'_questiontext', 169 get_string('questiondefinition', 'qtype_multianswer')); 170 171 if (isset ($this->questiondisplay->options->questions[$sub]->questiontext)) { 172 $mform->setDefault('sub_'.$sub.'_questiontext', 173 $this->questiondisplay->options->questions[$sub]->questiontext['text']); 174 } 175 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); 180 181 if ($this->questiondisplay->options->questions[$sub]->qtype == 'shortanswer') { 182 $mform->addElement('static', 'sub_'.$sub.'_usecase', 183 get_string('casesensitive', 'qtype_shortanswer')); 184 } 185 186 if ($this->questiondisplay->options->questions[$sub]->qtype == 'multichoice') { 187 $mform->addElement('static', 'sub_'.$sub.'_layout', 188 get_string('layout', 'qtype_multianswer')); 189 $mform->addElement('static', 'sub_'.$sub.'_shuffleanswers', 190 get_string('shuffleanswers', 'qtype_multichoice')); 191 } 192 193 foreach ($this->questiondisplay->options->questions[$sub]->answer as $key => $ans) { 194 $mform->addElement('static', 'sub_'.$sub.'_answer['.$key.']', 195 get_string('answer', 'question')); 196 197 if ($this->questiondisplay->options->questions[$sub]->qtype == 'numerical' && 198 $key == 0) { 199 $mform->addElement('static', 'sub_'.$sub.'_tolerance['.$key.']', 200 get_string('acceptederror', 'qtype_numerical')); 201 } 202 203 $mform->addElement('static', 'sub_'.$sub.'_fraction['.$key.']', 204 get_string('gradenoun')); 205 206 $mform->addElement('static', 'sub_'.$sub.'_feedback['.$key.']', 207 get_string('feedback', 'question')); 208 } 209 } 210 211 $this->negativediff = $countsavedsubquestions - $countsubquestions; 212 if (($this->negativediff > 0) ||$this->qtypechange || 213 ($this->usedinquiz && $this->negativediff != 0)) { 214 $mform->addElement('header', 'additemhdr', 215 get_string('warningquestionmodified', 'qtype_multianswer')); 216 } 217 if ($this->negativediff > 0) { 218 $mform->addElement('static', 'alert1', "<strong>". 219 get_string('questiondeleted', 'qtype_multianswer')."</strong>", 220 get_string('questionsless', 'qtype_multianswer', $this->negativediff)); 221 } 222 if ($this->qtypechange) { 223 $mform->addElement('static', 'alert1', "<strong>". 224 get_string('questiontypechanged', 'qtype_multianswer')."</strong>", 225 get_string('questiontypechangedcomment', 'qtype_multianswer')); 226 } 227 } 228 if ($this->usedinquiz) { 229 if ($this->negativediff < 0) { 230 $diff = $countsubquestions - $countsavedsubquestions; 231 $mform->addElement('static', 'alert1', "<strong>". 232 get_string('questionsadded', 'qtype_multianswer')."</strong>", 233 "<strong>".get_string('questionsmore', 'qtype_multianswer', $diff). 234 "</strong>"); 235 } 236 $a = new stdClass(); 237 $a->nb_of_quiz = $this->nbofquiz; 238 $a->nb_of_attempts = $this->nbofattempts; 239 $mform->addElement('header', 'additemhdr2', 240 get_string('questionusedinquiz', 'qtype_multianswer', $a)); 241 $mform->addElement('static', 'alertas', 242 get_string('youshouldnot', 'qtype_multianswer')); 243 } 244 if (($this->negativediff > 0 || $this->usedinquiz && 245 ($this->negativediff > 0 || $this->negativediff < 0 || $this->qtypechange)) && 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')); 251 $mform->setDefault('confirm', 0); 252 } else { 253 $mform->addElement('hidden', 'confirm', 0); 254 $mform->setType('confirm', PARAM_BOOL); 255 } 256 257 $this->add_interactive_settings(true, true); 258 } 259 260 261 public function set_data($question) { 262 global $DB; 263 $defaultvalues = array(); 264 if (isset($question->id) and $question->id and $question->qtype && 265 $question->questiontext) { 266 267 foreach ($question->options->questions as $key => $wrapped) { 268 if (!empty($wrapped)) { 269 // The old way of restoring the definitions is kept to gradually 270 // update all multianswer questions. 271 if (empty($wrapped->questiontext)) { 272 $parsableanswerdef = '{' . $wrapped->defaultmark . ':'; 273 switch ($wrapped->qtype) { 274 case 'multichoice': 275 $parsableanswerdef .= 'MULTICHOICE:'; 276 break; 277 case 'shortanswer': 278 $parsableanswerdef .= 'SHORTANSWER:'; 279 break; 280 case 'numerical': 281 $parsableanswerdef .= 'NUMERICAL:'; 282 break; 283 case 'subquestion_replacement': 284 continue 2; 285 default: 286 throw new \moodle_exception('unknownquestiontype', 'question', '', 287 $wrapped->qtype); 288 } 289 $separator = ''; 290 foreach ($wrapped->options->answers as $subanswer) { 291 $parsableanswerdef .= $separator 292 . '%' . round(100 * $subanswer->fraction) . '%'; 293 if (is_array($subanswer->answer)) { 294 $parsableanswerdef .= $subanswer->answer['text']; 295 } else { 296 $parsableanswerdef .= $subanswer->answer; 297 } 298 if (!empty($wrapped->options->tolerance)) { 299 // Special for numerical answers. 300 $parsableanswerdef .= ":{$wrapped->options->tolerance}"; 301 // We only want tolerance for the first alternative, it will 302 // be applied to all of the alternatives. 303 unset($wrapped->options->tolerance); 304 } 305 if ($subanswer->feedback) { 306 $parsableanswerdef .= "#{$subanswer->feedback}"; 307 } 308 $separator = '~'; 309 } 310 $parsableanswerdef .= '}'; 311 // Fix the questiontext fields of old questions. 312 $DB->set_field('question', 'questiontext', $parsableanswerdef, 313 array('id' => $wrapped->id)); 314 } else { 315 $parsableanswerdef = str_replace('&#', '&\#', $wrapped->questiontext); 316 } 317 $question->questiontext = str_replace("{#$key}", $parsableanswerdef, 318 $question->questiontext); 319 } 320 } 321 } 322 323 // Set default to $questiondisplay questions elements. 324 if ($this->reload) { 325 if (isset($this->questiondisplay->options->questions)) { 326 $subquestions = fullclone($this->questiondisplay->options->questions); 327 if (count($subquestions)) { 328 $sub = 1; 329 foreach ($subquestions as $subquestion) { 330 $prefix = 'sub_'.$sub.'_'; 331 332 // Validate parameters. 333 $answercount = 0; 334 $maxgrade = false; 335 $maxfraction = -1; 336 if ($subquestion->qtype == 'shortanswer') { 337 switch ($subquestion->usecase) { 338 case '1': 339 $defaultvalues[$prefix.'usecase'] = 340 get_string('caseyes', 'qtype_shortanswer'); 341 break; 342 case '0': 343 default : 344 $defaultvalues[$prefix.'usecase'] = 345 get_string('caseno', 'qtype_shortanswer'); 346 } 347 } 348 349 if ($subquestion->qtype == 'multichoice') { 350 $defaultvalues[$prefix.'layout'] = $subquestion->layout; 351 if ($subquestion->single == 1) { 352 switch ($subquestion->layout) { 353 case '0': 354 $defaultvalues[$prefix.'layout'] = 355 get_string('layoutselectinline', 'qtype_multianswer'); 356 break; 357 case '1': 358 $defaultvalues[$prefix.'layout'] = 359 get_string('layoutvertical', 'qtype_multianswer'); 360 break; 361 case '2': 362 $defaultvalues[$prefix.'layout'] = 363 get_string('layouthorizontal', 'qtype_multianswer'); 364 break; 365 default: 366 $defaultvalues[$prefix.'layout'] = 367 get_string('layoutundefined', 'qtype_multianswer'); 368 } 369 } else { 370 switch ($subquestion->layout) { 371 case '1': 372 $defaultvalues[$prefix.'layout'] = 373 get_string('layoutmultiple_vertical', 'qtype_multianswer'); 374 break; 375 case '2': 376 $defaultvalues[$prefix.'layout'] = 377 get_string('layoutmultiple_horizontal', 'qtype_multianswer'); 378 break; 379 default: 380 $defaultvalues[$prefix.'layout'] = 381 get_string('layoutundefined', 'qtype_multianswer'); 382 } 383 } 384 if ($subquestion->shuffleanswers ) { 385 $defaultvalues[$prefix.'shuffleanswers'] = get_string('yes', 'moodle'); 386 } else { 387 $defaultvalues[$prefix.'shuffleanswers'] = get_string('no', 'moodle'); 388 } 389 } 390 foreach ($subquestion->answer as $key => $answer) { 391 if ($subquestion->qtype == 'numerical' && $key == 0) { 392 $defaultvalues[$prefix.'tolerance['.$key.']'] = 393 $subquestion->tolerance[0]; 394 } 395 if (is_array($answer)) { 396 $answer = $answer['text']; 397 } 398 $trimmedanswer = trim($answer); 399 if ($trimmedanswer !== '') { 400 $answercount++; 401 if ($subquestion->qtype == 'numerical' && 402 !(qtype_numerical::is_valid_number($trimmedanswer) || $trimmedanswer == '*')) { 403 $this->_form->setElementError($prefix.'answer['.$key.']', 404 get_string('answermustbenumberorstar', 405 'qtype_numerical')); 406 } 407 if ($subquestion->fraction[$key] == 1) { 408 $maxgrade = true; 409 } 410 if ($subquestion->fraction[$key] > $maxfraction) { 411 $maxfraction = $subquestion->fraction[$key]; 412 } 413 // For 'multiresponse' we are OK if there is at least one fraction > 0. 414 if ($subquestion->qtype == 'multichoice' && $subquestion->single == 0 && 415 $subquestion->fraction[$key] > 0) { 416 $maxgrade = true; 417 } 418 } 419 420 $defaultvalues[$prefix.'answer['.$key.']'] = 421 htmlspecialchars($answer, ENT_COMPAT); 422 } 423 if ($answercount == 0) { 424 if ($subquestion->qtype == 'multichoice') { 425 $this->_form->setElementError($prefix.'answer[0]', 426 get_string('notenoughanswers', 'qtype_multichoice', 2)); 427 } else { 428 $this->_form->setElementError($prefix.'answer[0]', 429 get_string('notenoughanswers', 'question', 1)); 430 } 431 } 432 if ($maxgrade == false) { 433 $this->_form->setElementError($prefix.'fraction[0]', 434 get_string('fractionsnomax', 'question')); 435 } 436 foreach ($subquestion->feedback as $key => $answer) { 437 438 $defaultvalues[$prefix.'feedback['.$key.']'] = 439 htmlspecialchars ($answer['text'], ENT_COMPAT); 440 } 441 foreach ($subquestion->fraction as $key => $answer) { 442 $defaultvalues[$prefix.'fraction['.$key.']'] = $answer; 443 } 444 445 $sub++; 446 } 447 } 448 } 449 } 450 $defaultvalues['alertas'] = "<strong>".get_string('questioninquiz', 'qtype_multianswer'). 451 "</strong>"; 452 453 if ($defaultvalues != "") { 454 $question = (object)((array)$question + $defaultvalues); 455 } 456 $question = $this->data_preprocessing_hints($question, true, true); 457 parent::set_data($question); 458 } 459 460 public function validation($data, $files) { 461 $errors = parent::validation($data, $files); 462 463 $questiondisplay = qtype_multianswer_extract_question($data['questiontext']); 464 465 $errors = array_merge($errors, qtype_multianswer_validate_question($questiondisplay)); 466 467 if (($this->negativediff > 0 || $this->usedinquiz && 468 ($this->negativediff > 0 || $this->negativediff < 0 || 469 $this->qtypechange)) && !$this->confirm) { 470 $errors['confirm'] = 471 get_string('confirmsave', 'qtype_multianswer', $this->negativediff); 472 } 473 474 return $errors; 475 } 476 477 public function qtype() { 478 return 'multianswer'; 479 } 480 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body