|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 00036 class qtype_multianswer_edit_form extends question_edit_form { 00037 00038 // $questiondisplay will contain the qtype_multianswer_extract_question from 00039 // the questiontext 00040 public $questiondisplay; 00041 // $savedquestiondisplay will contain the qtype_multianswer_extract_question 00042 // from the questiontext in database 00043 public $savedquestion; 00044 public $savedquestiondisplay; 00045 public $used_in_quiz = false; 00046 public $qtype_change = false; 00047 public $negative_diff = 0; 00048 public $nb_of_quiz = 0; 00049 public $nb_of_attempts = 0; 00050 public $confirm = 0; 00051 public $reload = false; 00052 00053 public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) { 00054 global $SESSION, $CFG, $DB; 00055 $this->regenerate = true; 00056 $this->reload = optional_param('reload', false, PARAM_BOOL); 00057 00058 $this->used_in_quiz = false; 00059 00060 if (isset($question->id) && $question->id != 0) { 00061 $this->savedquestiondisplay = fullclone($question); 00062 if ($list = $DB->get_records('quiz_question_instances', 00063 array('question' => $question->id))) { 00064 foreach ($list as $key => $li) { 00065 $this->nb_of_quiz ++; 00066 if ($att = $DB->get_records('quiz_attempts', 00067 array('quiz' => $li->quiz, 'preview' => '0'))) { 00068 $this->nb_of_attempts += count($att); 00069 $this->used_in_quiz = true; 00070 } 00071 } 00072 } 00073 } 00074 00075 parent::__construct($submiturl, $question, $category, $contexts, $formeditable); 00076 } 00077 00078 protected function definition_inner($mform) { 00079 $mform->addElement('hidden', 'reload', 1); 00080 $mform->setType('reload', PARAM_INT); 00081 00082 // Remove meaningless defaultmark field. 00083 $mform->removeElement('defaultmark'); 00084 $this->confirm = optional_param('confirm', false, PARAM_BOOL); 00085 00086 // Make questiontext a required field for this question type. 00087 $mform->addRule('questiontext', null, 'required', null, 'client'); 00088 00089 // display the questions from questiontext; 00090 if ($questiontext = optional_param_array('questiontext', false, PARAM_RAW)) { 00091 $this->questiondisplay = fullclone(qtype_multianswer_extract_question($questiontext)); 00092 00093 } else { 00094 if (!$this->reload && !empty($this->savedquestiondisplay->id)) { 00095 // use database data as this is first pass 00096 // question->id == 0 so no stored datasets 00097 $this->questiondisplay = fullclone($this->savedquestiondisplay); 00098 foreach ($this->questiondisplay->options->questions as $subquestion) { 00099 if (!empty($subquestion)) { 00100 $subquestion->answer = array(''); 00101 foreach ($subquestion->options->answers as $ans) { 00102 $subquestion->answer[] = $ans->answer; 00103 } 00104 } 00105 } 00106 } else { 00107 $this->questiondisplay = ""; 00108 } 00109 } 00110 00111 if (isset($this->savedquestiondisplay->options->questions) && 00112 is_array($this->savedquestiondisplay->options->questions)) { 00113 $countsavedsubquestions = 0; 00114 foreach ($this->savedquestiondisplay->options->questions as $subquestion) { 00115 if (!empty($subquestion)) { 00116 $countsavedsubquestions++; 00117 } 00118 } 00119 } else { 00120 $countsavedsubquestions = 0; 00121 } 00122 if ($this->reload) { 00123 if (isset($this->questiondisplay->options->questions) && 00124 is_array($this->questiondisplay->options->questions)) { 00125 $countsubquestions = 0; 00126 foreach ($this->questiondisplay->options->questions as $subquestion) { 00127 if (!empty($subquestion)) { 00128 $countsubquestions++; 00129 } 00130 } 00131 } else { 00132 $countsubquestions = 0; 00133 } 00134 } else { 00135 $countsubquestions = $countsavedsubquestions; 00136 } 00137 00138 $mform->addElement('submit', 'analyzequestion', 00139 get_string('decodeverifyquestiontext', 'qtype_multianswer')); 00140 $mform->registerNoSubmitButton('analyzequestion'); 00141 if ($this->reload) { 00142 for ($sub = 1; $sub <= $countsubquestions; $sub++) { 00143 00144 if (isset($this->questiondisplay->options->questions[$sub]->qtype)) { 00145 $this->editas[$sub] = $this->questiondisplay->options->questions[$sub]->qtype; 00146 } else { 00147 $this->editas[$sub] = optional_param('sub_'.$sub.'_qtype', 'unknown type', PARAM_PLUGIN); 00148 } 00149 00150 $storemess = ''; 00151 if (isset($this->savedquestiondisplay->options->questions[$sub]->qtype) && 00152 $this->savedquestiondisplay->options->questions[$sub]->qtype != 00153 $this->questiondisplay->options->questions[$sub]->qtype) { 00154 $this->qtype_change = true; 00155 $storemess = ' ' . html_writer::tag('span', get_string( 00156 'storedqtype', 'qtype_multianswer', question_bank::get_qtype_name( 00157 $this->savedquestiondisplay->options->questions[$sub]->qtype)), 00158 array('class' => 'error')); 00159 } 00160 00161 $mform->addElement('header', 'subhdr'.$sub, get_string('questionno', 'question', 00162 '{#'.$sub.'}').' '.question_bank::get_qtype_name( 00163 $this->questiondisplay->options->questions[$sub]->qtype).$storemess); 00164 00165 $mform->addElement('static', 'sub_'.$sub.'_questiontext', 00166 get_string('questiondefinition', 'qtype_multianswer')); 00167 00168 if (isset ($this->questiondisplay->options->questions[$sub]->questiontext)) { 00169 $mform->setDefault('sub_'.$sub.'_questiontext', 00170 $this->questiondisplay->options->questions[$sub]->questiontext['text']); 00171 } 00172 00173 $mform->addElement('static', 'sub_'.$sub.'_defaultmark', 00174 get_string('defaultmark', 'question')); 00175 $mform->setDefault('sub_'.$sub.'_defaultmark', 00176 $this->questiondisplay->options->questions[$sub]->defaultmark); 00177 00178 if ($this->questiondisplay->options->questions[$sub]->qtype == 'shortanswer') { 00179 $mform->addElement('static', 'sub_'.$sub.'_usecase', 00180 get_string('casesensitive', 'qtype_shortanswer')); 00181 } 00182 00183 if ($this->questiondisplay->options->questions[$sub]->qtype == 'multichoice') { 00184 $mform->addElement('static', 'sub_'.$sub.'_layout', 00185 get_string('layout', 'qtype_multianswer')); 00186 } 00187 00188 foreach ($this->questiondisplay->options->questions[$sub]->answer as $key => $ans) { 00189 $mform->addElement('static', 'sub_'.$sub.'_answer['.$key.']', 00190 get_string('answer', 'question')); 00191 00192 if ($this->questiondisplay->options->questions[$sub]->qtype == 'numerical' && 00193 $key == 0) { 00194 $mform->addElement('static', 'sub_'.$sub.'_tolerance['.$key.']', 00195 get_string('acceptederror', 'qtype_numerical')); 00196 } 00197 00198 $mform->addElement('static', 'sub_'.$sub.'_fraction['.$key.']', 00199 get_string('grade')); 00200 00201 $mform->addElement('static', 'sub_'.$sub.'_feedback['.$key.']', 00202 get_string('feedback', 'question')); 00203 } 00204 } 00205 00206 $this->negative_diff = $countsavedsubquestions - $countsubquestions; 00207 if (($this->negative_diff > 0) ||$this->qtype_change || 00208 ($this->used_in_quiz && $this->negative_diff != 0)) { 00209 $mform->addElement('header', 'additemhdr', 00210 get_string('warningquestionmodified', 'qtype_multianswer')); 00211 } 00212 if ($this->negative_diff > 0) { 00213 $mform->addElement('static', 'alert1', "<strong>". 00214 get_string('questiondeleted', 'qtype_multianswer')."</strong>", 00215 get_string('questionsless', 'qtype_multianswer', $this->negative_diff)); 00216 } 00217 if ($this->qtype_change) { 00218 $mform->addElement('static', 'alert1', "<strong>". 00219 get_string('questiontypechanged', 'qtype_multianswer')."</strong>", 00220 get_string('questiontypechangedcomment', 'qtype_multianswer')); 00221 } 00222 } 00223 if ($this->used_in_quiz) { 00224 if ($this->negative_diff < 0) { 00225 $diff = $countsubquestions - $countsavedsubquestions; 00226 $mform->addElement('static', 'alert1', "<strong>". 00227 get_string('questionsadded', 'qtype_multianswer')."</strong>", 00228 "<strong>".get_string('questionsmore', 'qtype_multianswer', $diff). 00229 "</strong>"); 00230 } 00231 $a = new stdClass(); 00232 $a->nb_of_quiz = $this->nb_of_quiz; 00233 $a->nb_of_attempts = $this->nb_of_attempts; 00234 $mform->addElement('header', 'additemhdr2', 00235 get_string('questionusedinquiz', 'qtype_multianswer', $a)); 00236 $mform->addElement('static', 'alertas', 00237 get_string('youshouldnot', 'qtype_multianswer')); 00238 } 00239 if (($this->negative_diff > 0 || $this->used_in_quiz && 00240 ($this->negative_diff > 0 || $this->negative_diff < 0 || $this->qtype_change)) && 00241 $this->reload) { 00242 $mform->addElement('header', 'additemhdr', 00243 get_string('questionsaveasedited', 'qtype_multianswer')); 00244 $mform->addElement('checkbox', 'confirm', '', 00245 get_string('confirmquestionsaveasedited', 'qtype_multianswer')); 00246 $mform->setDefault('confirm', 0); 00247 } else { 00248 $mform->addElement('hidden', 'confirm', 0); 00249 } 00250 00251 $this->add_interactive_settings(); 00252 } 00253 00254 00255 public function set_data($question) { 00256 global $DB; 00257 $default_values = array(); 00258 if (isset($question->id) and $question->id and $question->qtype && 00259 $question->questiontext) { 00260 00261 foreach ($question->options->questions as $key => $wrapped) { 00262 if (!empty($wrapped)) { 00263 // The old way of restoring the definitions is kept to gradually 00264 // update all multianswer questions 00265 if (empty($wrapped->questiontext)) { 00266 $parsableanswerdef = '{' . $wrapped->defaultmark . ':'; 00267 switch ($wrapped->qtype) { 00268 case 'multichoice': 00269 $parsableanswerdef .= 'MULTICHOICE:'; 00270 break; 00271 case 'shortanswer': 00272 $parsableanswerdef .= 'SHORTANSWER:'; 00273 break; 00274 case 'numerical': 00275 $parsableanswerdef .= 'NUMERICAL:'; 00276 break; 00277 default: 00278 print_error('unknownquestiontype', 'question', '', 00279 $wrapped->qtype); 00280 } 00281 $separator = ''; 00282 foreach ($wrapped->options->answers as $subanswer) { 00283 $parsableanswerdef .= $separator 00284 . '%' . round(100*$subanswer->fraction) . '%'; 00285 if (is_array($subanswer->answer)) { 00286 $parsableanswerdef .= $subanswer->answer['text']; 00287 } else { 00288 $parsableanswerdef .= $subanswer->answer; 00289 } 00290 if (!empty($wrapped->options->tolerance)) { 00291 // Special for numerical answers: 00292 $parsableanswerdef .= ":{$wrapped->options->tolerance}"; 00293 // We only want tolerance for the first alternative, it will 00294 // be applied to all of the alternatives. 00295 unset($wrapped->options->tolerance); 00296 } 00297 if ($subanswer->feedback) { 00298 $parsableanswerdef .= "#$subanswer->feedback"; 00299 } 00300 $separator = '~'; 00301 } 00302 $parsableanswerdef .= '}'; 00303 // Fix the questiontext fields of old questions 00304 $DB->set_field('question', 'questiontext', $parsableanswerdef, 00305 array('id' => $wrapped->id)); 00306 } else { 00307 $parsableanswerdef = str_replace('&#', '&\#', $wrapped->questiontext); 00308 } 00309 $question->questiontext = str_replace("{#$key}", $parsableanswerdef, 00310 $question->questiontext); 00311 } 00312 } 00313 } 00314 00315 // set default to $questiondisplay questions elements 00316 if ($this->reload) { 00317 if (isset($this->questiondisplay->options->questions)) { 00318 $subquestions = fullclone($this->questiondisplay->options->questions); 00319 if (count($subquestions)) { 00320 $sub = 1; 00321 foreach ($subquestions as $subquestion) { 00322 $prefix = 'sub_'.$sub.'_'; 00323 00324 // validate parameters 00325 $answercount = 0; 00326 $maxgrade = false; 00327 $maxfraction = -1; 00328 if ($subquestion->qtype == 'shortanswer') { 00329 switch ($subquestion->usecase) { 00330 case '1': 00331 $default_values[$prefix.'usecase'] = 00332 get_string('caseyes', 'qtype_shortanswer'); 00333 break; 00334 case '0': 00335 default : 00336 $default_values[$prefix.'usecase'] = 00337 get_string('caseno', 'qtype_shortanswer'); 00338 } 00339 } 00340 00341 if ($subquestion->qtype == 'multichoice') { 00342 $default_values[$prefix.'layout'] = $subquestion->layout; 00343 switch ($subquestion->layout) { 00344 case '0': 00345 $default_values[$prefix.'layout'] = 00346 get_string('layoutselectinline', 'qtype_multianswer'); 00347 break; 00348 case '1': 00349 $default_values[$prefix.'layout'] = 00350 get_string('layoutvertical', 'qtype_multianswer'); 00351 break; 00352 case '2': 00353 $default_values[$prefix.'layout'] = 00354 get_string('layouthorizontal', 'qtype_multianswer'); 00355 break; 00356 default: 00357 $default_values[$prefix.'layout'] = 00358 get_string('layoutundefined', 'qtype_multianswer'); 00359 } 00360 } 00361 foreach ($subquestion->answer as $key => $answer) { 00362 if ($subquestion->qtype == 'numerical' && $key == 0) { 00363 $default_values[$prefix.'tolerance['.$key.']'] = 00364 $subquestion->tolerance[0]; 00365 } 00366 if (is_array($answer)) { 00367 $answer = $answer['text']; 00368 } 00369 $trimmedanswer = trim($answer); 00370 if ($trimmedanswer !== '') { 00371 $answercount++; 00372 if ($subquestion->qtype == 'numerical' && 00373 !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) { 00374 $this->_form->setElementError($prefix.'answer['.$key.']', 00375 get_string('answermustbenumberorstar', 00376 'qtype_numerical')); 00377 } 00378 if ($subquestion->fraction[$key] == 1) { 00379 $maxgrade = true; 00380 } 00381 if ($subquestion->fraction[$key] > $maxfraction) { 00382 $maxfraction = $subquestion->fraction[$key]; 00383 } 00384 } 00385 00386 $default_values[$prefix.'answer['.$key.']'] = 00387 htmlspecialchars($answer); 00388 } 00389 if ($answercount == 0) { 00390 if ($subquestion->qtype == 'multichoice') { 00391 $this->_form->setElementError($prefix.'answer[0]', 00392 get_string('notenoughanswers', 'qtype_multichoice', 2)); 00393 } else { 00394 $this->_form->setElementError($prefix.'answer[0]', 00395 get_string('notenoughanswers', 'question', 1)); 00396 } 00397 } 00398 if ($maxgrade == false) { 00399 $this->_form->setElementError($prefix.'fraction[0]', 00400 get_string('fractionsnomax', 'question')); 00401 } 00402 foreach ($subquestion->feedback as $key => $answer) { 00403 00404 $default_values[$prefix.'feedback['.$key.']'] = 00405 htmlspecialchars ($answer['text']); 00406 } 00407 foreach ($subquestion->fraction as $key => $answer) { 00408 $default_values[$prefix.'fraction['.$key.']'] = $answer; 00409 } 00410 00411 $sub++; 00412 } 00413 } 00414 } 00415 } 00416 $default_values['alertas']= "<strong>".get_string('questioninquiz', 'qtype_multianswer'). 00417 "</strong>"; 00418 00419 if ($default_values != "") { 00420 $question = (object)((array)$question + $default_values); 00421 } 00422 $question = $this->data_preprocessing_hints($question); 00423 parent::set_data($question); 00424 } 00425 00426 public function validation($data, $files) { 00427 $errors = parent::validation($data, $files); 00428 00429 $questiondisplay = qtype_multianswer_extract_question($data['questiontext']); 00430 00431 if (isset($questiondisplay->options->questions)) { 00432 $subquestions = fullclone($questiondisplay->options->questions); 00433 if (count($subquestions)) { 00434 $sub = 1; 00435 foreach ($subquestions as $subquestion) { 00436 $prefix = 'sub_'.$sub.'_'; 00437 $answercount = 0; 00438 $maxgrade = false; 00439 $maxfraction = -1; 00440 if (isset($this->savedquestiondisplay->options->questions[$sub]->qtype) && 00441 $this->savedquestiondisplay->options->questions[$sub]->qtype != 00442 $questiondisplay->options->questions[$sub]->qtype) { 00443 $storemess = " STORED QTYPE ".question_bank::get_qtype_name( 00444 $this->savedquestiondisplay->options->questions[$sub]->qtype); 00445 } 00446 foreach ($subquestion->answer as $key => $answer) { 00447 if (is_array($answer)) { 00448 $answer = $answer['text']; 00449 } 00450 $trimmedanswer = trim($answer); 00451 if ($trimmedanswer !== '') { 00452 $answercount++; 00453 if ($subquestion->qtype == 'numerical' && 00454 !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) { 00455 $errors[$prefix.'answer['.$key.']'] = 00456 get_string('answermustbenumberorstar', 'qtype_numerical'); 00457 } 00458 if ($subquestion->fraction[$key] == 1) { 00459 $maxgrade = true; 00460 } 00461 if ($subquestion->fraction[$key] > $maxfraction) { 00462 $maxfraction = $subquestion->fraction[$key]; 00463 } 00464 } 00465 } 00466 if ($answercount == 0) { 00467 if ($subquestion->qtype == 'multichoice') { 00468 $errors[$prefix.'answer[0]'] = 00469 get_string('notenoughanswers', 'qtype_multichoice', 2); 00470 } else { 00471 $errors[$prefix.'answer[0]'] = 00472 get_string('notenoughanswers', 'question', 1); 00473 } 00474 } 00475 if ($maxgrade == false) { 00476 $errors[$prefix.'fraction[0]'] = 00477 get_string('fractionsnomax', 'question'); 00478 } 00479 $sub++; 00480 } 00481 } else { 00482 $errors['questiontext'] = get_string('questionsmissing', 'qtype_multianswer'); 00483 } 00484 } 00485 00486 if (($this->negative_diff > 0 || $this->used_in_quiz && 00487 ($this->negative_diff > 0 || $this->negative_diff < 0 || 00488 $this->qtype_change)) && !$this->confirm) { 00489 $errors['confirm'] = 00490 get_string('confirmsave', 'qtype_multianswer', $this->negative_diff); 00491 } 00492 00493 return $errors; 00494 } 00495 00496 public function qtype() { 00497 return 'multianswer'; 00498 } 00499 }