See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * Multichoice 20 * 21 * @package mod_lesson 22 * @copyright 2009 Sam Hemelryk 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 **/ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 /** Multichoice question type */ 29 define("LESSON_PAGE_MULTICHOICE", "3"); 30 31 class lesson_page_type_multichoice extends lesson_page { 32 33 protected $type = lesson_page::TYPE_QUESTION; 34 protected $typeidstring = 'multichoice'; 35 protected $typeid = LESSON_PAGE_MULTICHOICE; 36 protected $string = null; 37 38 public function get_typeid() { 39 return $this->typeid; 40 } 41 public function get_typestring() { 42 if ($this->string===null) { 43 $this->string = get_string($this->typeidstring, 'lesson'); 44 } 45 return $this->string; 46 } 47 public function get_idstring() { 48 return $this->typeidstring; 49 } 50 51 /** 52 * Gets an array of the jumps used by the answers of this page 53 * 54 * @return array 55 */ 56 public function get_jumps() { 57 global $DB; 58 $jumps = array(); 59 if ($answers = $this->get_answers()) { 60 foreach ($answers as $answer) { 61 if ($answer->answer === '') { 62 // show only jumps for real branches (==have description) 63 continue; 64 } 65 $jumps[] = $this->get_jump_name($answer->jumpto); 66 } 67 } else { 68 // We get here is the lesson was created on a Moodle 1.9 site and 69 // the lesson contains question pages without any answers. 70 $jumps[] = $this->get_jump_name($this->properties->nextpageid); 71 } 72 return $jumps; 73 } 74 75 public function get_used_answers() { 76 $answers = $this->get_answers(); 77 foreach ($answers as $key=>$answer) { 78 if ($answer->answer === '') { 79 unset($answers[$key]); 80 } else { 81 $answers[$key] = parent::rewrite_answers_urls($answer); 82 } 83 } 84 return $answers; 85 } 86 87 public function display($renderer, $attempt) { 88 global $CFG, $PAGE; 89 $answers = $this->get_used_answers(); 90 shuffle($answers); 91 $action = $CFG->wwwroot.'/mod/lesson/continue.php'; 92 $params = array('answers'=>$answers, 'lessonid'=>$this->lesson->id, 'contents'=>$this->get_contents(), 'attempt'=>$attempt); 93 if ($this->properties->qoption) { 94 $mform = new lesson_display_answer_form_multichoice_multianswer($action, $params); 95 } else { 96 $mform = new lesson_display_answer_form_multichoice_singleanswer($action, $params); 97 } 98 $data = new stdClass; 99 $data->id = $PAGE->cm->id; 100 $data->pageid = $this->properties->id; 101 $mform->set_data($data); 102 103 // Trigger an event question viewed. 104 $eventparams = array( 105 'context' => context_module::instance($PAGE->cm->id), 106 'objectid' => $this->properties->id, 107 'other' => array( 108 'pagetype' => $this->get_typestring() 109 ) 110 ); 111 112 $event = \mod_lesson\event\question_viewed::create($eventparams); 113 $event->trigger(); 114 return $mform->display(); 115 } 116 117 public function check_answer() { 118 global $DB, $CFG, $PAGE; 119 $result = parent::check_answer(); 120 121 $formattextdefoptions = new stdClass(); 122 $formattextdefoptions->noclean = true; 123 $formattextdefoptions->para = false; 124 125 $answers = $this->get_used_answers(); 126 shuffle($answers); 127 $action = $CFG->wwwroot.'/mod/lesson/continue.php'; 128 $params = array('answers'=>$answers, 'lessonid'=>$this->lesson->id, 'contents'=>$this->get_contents()); 129 if ($this->properties->qoption) { 130 $mform = new lesson_display_answer_form_multichoice_multianswer($action, $params); 131 } else { 132 $mform = new lesson_display_answer_form_multichoice_singleanswer($action, $params); 133 } 134 $data = $mform->get_data(); 135 require_sesskey(); 136 137 if (!$data) { 138 $result->inmediatejump = true; 139 $result->newpageid = $this->properties->id; 140 return $result; 141 } 142 143 if ($this->properties->qoption) { 144 // Multianswer allowed, user's answer is an array 145 146 if (empty($data->answer) || !is_array($data->answer)) { 147 $result->noanswer = true; 148 return $result; 149 } 150 151 $studentanswers = array(); 152 foreach ($data->answer as $key=>$value) { 153 $studentanswers[] = (int)$key; 154 } 155 156 // get what the user answered 157 $result->userresponse = implode(",", $studentanswers); 158 159 // get the answers in a set order, the id order 160 $answers = $this->get_used_answers(); 161 $ncorrect = 0; 162 $nhits = 0; 163 $responses = array(); 164 $correctanswerid = 0; 165 $wronganswerid = 0; 166 // store student's answers for displaying on feedback page 167 $result->studentanswer = ''; 168 $result->studentanswerformat = FORMAT_HTML; 169 foreach ($answers as $answer) { 170 foreach ($studentanswers as $answerid) { 171 if ($answerid == $answer->id) { 172 $studentanswerarray[] = format_text($answer->answer, $answer->answerformat, $formattextdefoptions); 173 $responses[$answerid] = format_text($answer->response, $answer->responseformat, $formattextdefoptions); 174 } 175 } 176 } 177 $result->studentanswer = implode(self::MULTIANSWER_DELIMITER, $studentanswerarray); 178 $correctpageid = null; 179 $wrongpageid = null; 180 181 // Iterate over all the possible answers. 182 foreach ($answers as $answer) { 183 if ($this->lesson->custom) { 184 $iscorrectanswer = $answer->score > 0; 185 } else { 186 $iscorrectanswer = $this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto); 187 } 188 189 // Iterate over all the student answers to check if he selected the current possible answer. 190 foreach ($studentanswers as $answerid) { 191 if ($answerid == $answer->id) { 192 if ($iscorrectanswer) { 193 $nhits++; 194 } else { 195 // Always jump to the page related to the student's first wrong answer. 196 if (!isset($wrongpageid)) { 197 // Leave in its "raw" state - will be converted into a proper page id later. 198 $wrongpageid = $answer->jumpto; 199 } 200 // Save the answer id for scoring. 201 if ($wronganswerid == 0) { 202 $wronganswerid = $answer->id; 203 } 204 } 205 } 206 } 207 208 if ($iscorrectanswer) { 209 $ncorrect++; 210 211 // Save the first jumpto page id, may be needed! 212 if (!isset($correctpageid)) { 213 // Leave in its "raw" state - will be converted into a proper page id later. 214 $correctpageid = $answer->jumpto; 215 } 216 // Save the answer id for scoring. 217 if ($correctanswerid == 0) { 218 $correctanswerid = $answer->id; 219 } 220 } 221 } 222 223 if ((count($studentanswers) == $ncorrect) and ($nhits == $ncorrect)) { 224 $result->correctanswer = true; 225 $result->response = implode(self::MULTIANSWER_DELIMITER, $responses); 226 $result->newpageid = $correctpageid; 227 $result->answerid = $correctanswerid; 228 } else { 229 $result->response = implode(self::MULTIANSWER_DELIMITER, $responses); 230 $result->newpageid = $wrongpageid; 231 $result->answerid = $wronganswerid; 232 } 233 } else { 234 // only one answer allowed 235 if (!isset($data->answerid) || (empty($data->answerid) && !is_int($data->answerid))) { 236 $result->noanswer = true; 237 return $result; 238 } 239 $result->answerid = $data->answerid; 240 if (!$answer = $DB->get_record("lesson_answers", array("id" => $result->answerid))) { 241 print_error("Continue: answer record not found"); 242 } 243 $answer = parent::rewrite_answers_urls($answer); 244 if ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) { 245 $result->correctanswer = true; 246 } 247 if ($this->lesson->custom) { 248 if ($answer->score > 0) { 249 $result->correctanswer = true; 250 } else { 251 $result->correctanswer = false; 252 } 253 } 254 $result->newpageid = $answer->jumpto; 255 $result->response = format_text($answer->response, $answer->responseformat, $formattextdefoptions); 256 $result->userresponse = format_text($answer->answer, $answer->answerformat, $formattextdefoptions); 257 $result->studentanswer = $result->userresponse; 258 } 259 return $result; 260 } 261 262 public function option_description_string() { 263 if ($this->properties->qoption) { 264 return " - ".get_string("multianswer", "lesson"); 265 } 266 return parent::option_description_string(); 267 } 268 269 public function display_answers(html_table $table) { 270 $answers = $this->get_used_answers(); 271 $options = new stdClass; 272 $options->noclean = true; 273 $options->para = false; 274 $i = 1; 275 foreach ($answers as $answer) { 276 $answer = parent::rewrite_answers_urls($answer); 277 $cells = array(); 278 if ($this->lesson->custom && $answer->score > 0) { 279 // if the score is > 0, then it is correct 280 $cells[] = '<label class="correct">' . get_string('answer', 'lesson') . " {$i}</label>: \n"; 281 } else if ($this->lesson->custom) { 282 $cells[] = '<label>' . get_string('answer', 'lesson') . " {$i}</label>: \n"; 283 } else if ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto)) { 284 // underline correct answers 285 $cells[] = '<span class="correct">' . get_string('answer', 'lesson') . " {$i}</span>: \n"; 286 } else { 287 $cells[] = '<label class="correct">' . get_string('answer', 'lesson') . " {$i}</label>: \n"; 288 } 289 $cells[] = format_text($answer->answer, $answer->answerformat, $options); 290 $table->data[] = new html_table_row($cells); 291 292 $cells = array(); 293 $cells[] = '<label>' . get_string('response', 'lesson') . " {$i} </label>:\n"; 294 $cells[] = format_text($answer->response, $answer->responseformat, $options); 295 $table->data[] = new html_table_row($cells); 296 297 $cells = array(); 298 $cells[] = '<label>' . get_string('score', 'lesson') . '</label>:'; 299 $cells[] = $answer->score; 300 $table->data[] = new html_table_row($cells); 301 302 $cells = array(); 303 $cells[] = '<label>' . get_string('jump', 'lesson') . '</label>:'; 304 $cells[] = $this->get_jump_name($answer->jumpto); 305 $table->data[] = new html_table_row($cells); 306 if ($i === 1){ 307 $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;'; 308 } 309 $i++; 310 } 311 return $table; 312 } 313 public function stats(array &$pagestats, $tries) { 314 if(count($tries) > $this->lesson->maxattempts) { // if there are more tries than the max that is allowed, grab the last "legal" attempt 315 $temp = $tries[$this->lesson->maxattempts - 1]; 316 } else { 317 // else, user attempted the question less than the max, so grab the last one 318 $temp = end($tries); 319 } 320 if ($this->properties->qoption) { 321 $userresponse = explode(",", $temp->useranswer); 322 foreach ($userresponse as $response) { 323 if (isset($pagestats[$temp->pageid][$response])) { 324 $pagestats[$temp->pageid][$response]++; 325 } else { 326 $pagestats[$temp->pageid][$response] = 1; 327 } 328 } 329 } else { 330 if (isset($pagestats[$temp->pageid][$temp->answerid])) { 331 $pagestats[$temp->pageid][$temp->answerid]++; 332 } else { 333 $pagestats[$temp->pageid][$temp->answerid] = 1; 334 } 335 } 336 if (isset($pagestats[$temp->pageid]["total"])) { 337 $pagestats[$temp->pageid]["total"]++; 338 } else { 339 $pagestats[$temp->pageid]["total"] = 1; 340 } 341 return true; 342 } 343 344 public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) { 345 $answers = $this->get_used_answers(); 346 $formattextdefoptions = new stdClass; 347 $formattextdefoptions->para = false; //I'll use it widely in this page 348 $formattextdefoptions->context = $answerpage->context; 349 350 foreach ($answers as $answer) { 351 if ($this->properties->qoption) { 352 if ($useranswer == null) { 353 $userresponse = array(); 354 } else { 355 $userresponse = explode(",", $useranswer->useranswer); 356 } 357 if (in_array($answer->id, $userresponse)) { 358 // make checked 359 $data = "<input readonly=\"readonly\" disabled=\"disabled\" name=\"answer[$i]\" checked=\"checked\" type=\"checkbox\" value=\"1\" />"; 360 if (!isset($answerdata->response)) { 361 if ($answer->response == null) { 362 if ($useranswer->correct) { 363 $answerdata->response = get_string("thatsthecorrectanswer", "lesson"); 364 } else { 365 $answerdata->response = get_string("thatsthewronganswer", "lesson"); 366 } 367 } else { 368 $answerdata->response = $answer->response; 369 } 370 } 371 if (!isset($answerdata->score)) { 372 if ($this->lesson->custom) { 373 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score; 374 } elseif ($useranswer->correct) { 375 $answerdata->score = get_string("receivedcredit", "lesson"); 376 } else { 377 $answerdata->score = get_string("didnotreceivecredit", "lesson"); 378 } 379 } 380 } else { 381 // unchecked 382 $data = "<input type=\"checkbox\" readonly=\"readonly\" name=\"answer[$i]\" value=\"0\" disabled=\"disabled\" />"; 383 } 384 if (($answer->score > 0 && $this->lesson->custom) || ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto) && !$this->lesson->custom)) { 385 $data = "<div class=highlight>".$data.' '.format_text($answer->answer,$answer->answerformat,$formattextdefoptions)."</div>"; 386 } else { 387 $data .= format_text($answer->answer,$answer->answerformat,$formattextdefoptions); 388 } 389 } else { 390 if ($useranswer != null and $answer->id == $useranswer->answerid) { 391 // make checked 392 $data = "<input readonly=\"readonly\" disabled=\"disabled\" name=\"answer[$i]\" checked=\"checked\" type=\"checkbox\" value=\"1\" />"; 393 if ($answer->response == null) { 394 if ($useranswer->correct) { 395 $answerdata->response = get_string("thatsthecorrectanswer", "lesson"); 396 } else { 397 $answerdata->response = get_string("thatsthewronganswer", "lesson"); 398 } 399 } else { 400 $answerdata->response = $answer->response; 401 } 402 if ($this->lesson->custom) { 403 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score; 404 } elseif ($useranswer->correct) { 405 $answerdata->score = get_string("receivedcredit", "lesson"); 406 } else { 407 $answerdata->score = get_string("didnotreceivecredit", "lesson"); 408 } 409 } else { 410 // unchecked 411 $data = "<input type=\"checkbox\" readonly=\"readonly\" name=\"answer[$i]\" value=\"0\" disabled=\"disabled\" />"; 412 } 413 if (($answer->score > 0 && $this->lesson->custom) || ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto) && !$this->lesson->custom)) { 414 $data = "<div class=\"highlight\">".$data.' '.format_text($answer->answer,FORMAT_MOODLE,$formattextdefoptions)."</div>"; 415 } else { 416 $data .= format_text($answer->answer,$answer->answerformat,$formattextdefoptions); 417 } 418 } 419 if (isset($pagestats[$this->properties->id][$answer->id])) { 420 $percent = $pagestats[$this->properties->id][$answer->id] / $pagestats[$this->properties->id]["total"] * 100; 421 $percent = round($percent, 2); 422 $percent .= "% ".get_string("checkedthisone", "lesson"); 423 } else { 424 $percent = get_string("noonecheckedthis", "lesson"); 425 } 426 427 $answerdata->answers[] = array($data, $percent); 428 $answerpage->answerdata = $answerdata; 429 } 430 return $answerpage; 431 } 432 } 433 434 435 class lesson_add_page_form_multichoice extends lesson_add_page_form_base { 436 437 public $qtype = 'multichoice'; 438 public $qtypestring = 'multichoice'; 439 protected $answerformat = LESSON_ANSWER_HTML; 440 protected $responseformat = LESSON_ANSWER_HTML; 441 442 public function custom_definition() { 443 444 $this->_form->addElement('checkbox', 'qoption', get_string('options', 'lesson'), get_string('multianswer', 'lesson')); 445 $this->_form->setDefault('qoption', 0); 446 $this->_form->addHelpButton('qoption', 'multianswer', 'lesson'); 447 448 for ($i = 0; $i < $this->_customdata['lesson']->maxanswers; $i++) { 449 $this->_form->addElement('header', 'answertitle'.$i, get_string('answer').' '.($i+1)); 450 $this->add_answer($i, null, ($i<2), $this->get_answer_format()); 451 $this->add_response($i); 452 $this->add_jumpto($i, null, ($i == 0 ? LESSON_NEXTPAGE : LESSON_THISPAGE)); 453 $this->add_score($i, null, ($i===0)?1:0); 454 } 455 } 456 } 457 458 class lesson_display_answer_form_multichoice_singleanswer extends moodleform { 459 460 public function definition() { 461 global $USER, $OUTPUT; 462 $mform = $this->_form; 463 $answers = $this->_customdata['answers']; 464 $lessonid = $this->_customdata['lessonid']; 465 $contents = $this->_customdata['contents']; 466 if (array_key_exists('attempt', $this->_customdata)) { 467 $attempt = $this->_customdata['attempt']; 468 } else { 469 $attempt = new stdClass(); 470 $attempt->answerid = null; 471 } 472 473 // Disable shortforms. 474 $mform->setDisableShortforms(); 475 476 $mform->addElement('header', 'pageheader'); 477 478 $mform->addElement('html', $OUTPUT->container($contents, 'contents')); 479 480 $hasattempt = false; 481 $disabled = ''; 482 if (isset($USER->modattempts[$lessonid]) && !empty($USER->modattempts[$lessonid])) { 483 $hasattempt = true; 484 $disabled = array('disabled' => 'disabled'); 485 } 486 487 $options = new stdClass; 488 $options->para = false; 489 $options->noclean = true; 490 491 $mform->addElement('hidden', 'id'); 492 $mform->setType('id', PARAM_INT); 493 494 $mform->addElement('hidden', 'pageid'); 495 $mform->setType('pageid', PARAM_INT); 496 497 $i = 0; 498 foreach ($answers as $answer) { 499 $mform->addElement('html', '<div class="answeroption">'); 500 $answer->answer = preg_replace('#>$#', '> ', $answer->answer); 501 $mform->addElement('radio','answerid',null,format_text($answer->answer, $answer->answerformat, $options),$answer->id, $disabled); 502 $mform->setType('answer'.$i, PARAM_INT); 503 if ($hasattempt && $answer->id == $USER->modattempts[$lessonid]->answerid) { 504 $mform->setDefault('answerid', $USER->modattempts[$lessonid]->answerid); 505 } 506 $mform->addElement('html', '</div>'); 507 $i++; 508 } 509 510 if ($hasattempt) { 511 $this->add_action_buttons(null, get_string("nextpage", "lesson")); 512 } else { 513 $this->add_action_buttons(null, get_string("submit", "lesson")); 514 } 515 } 516 517 } 518 519 class lesson_display_answer_form_multichoice_multianswer extends moodleform { 520 521 public function definition() { 522 global $USER, $OUTPUT; 523 $mform = $this->_form; 524 $answers = $this->_customdata['answers']; 525 526 $lessonid = $this->_customdata['lessonid']; 527 $contents = $this->_customdata['contents']; 528 529 // Disable shortforms. 530 $mform->setDisableShortforms(); 531 532 $mform->addElement('header', 'pageheader'); 533 534 $mform->addElement('html', $OUTPUT->container($contents, 'contents')); 535 536 $hasattempt = false; 537 $disabled = ''; 538 $useranswers = array(); 539 if (isset($USER->modattempts[$lessonid]) && !empty($USER->modattempts[$lessonid])) { 540 $hasattempt = true; 541 $disabled = array('disabled' => 'disabled'); 542 $useranswers = explode(',', $USER->modattempts[$lessonid]->useranswer); 543 } 544 545 $options = new stdClass; 546 $options->para = false; 547 $options->noclean = true; 548 549 $mform->addElement('hidden', 'id'); 550 $mform->setType('id', PARAM_INT); 551 552 $mform->addElement('hidden', 'pageid'); 553 $mform->setType('pageid', PARAM_INT); 554 555 foreach ($answers as $answer) { 556 $mform->addElement('html', '<div class="answeroption">'); 557 $answerid = 'answer['.$answer->id.']'; 558 if ($hasattempt && in_array($answer->id, $useranswers)) { 559 $answerid = 'answer_'.$answer->id; 560 $mform->addElement('hidden', 'answer['.$answer->id.']', $answer->answer); 561 $mform->setType('answer['.$answer->id.']', PARAM_NOTAGS); 562 $mform->setDefault($answerid, true); 563 $mform->setDefault('answer['.$answer->id.']', true); 564 } 565 // NOTE: our silly checkbox supports only value '1' - we can not use it like the radiobox above!!!!!! 566 $answer->answer = preg_replace('#>$#', '> ', $answer->answer); 567 $mform->addElement('checkbox', $answerid, null, format_text($answer->answer, $answer->answerformat, $options), $disabled); 568 $mform->setType($answerid, PARAM_INT); 569 570 $mform->addElement('html', '</div>'); 571 } 572 573 if ($hasattempt) { 574 $this->add_action_buttons(null, get_string("nextpage", "lesson")); 575 } else { 576 $this->add_action_buttons(null, get_string("submit", "lesson")); 577 } 578 } 579 580 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body