See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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 throw new \moodle_exception("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 $temp = $this->lesson->get_last_attempt($tries); 315 if ($this->properties->qoption) { 316 $userresponse = explode(",", $temp->useranswer); 317 foreach ($userresponse as $response) { 318 if (isset($pagestats[$temp->pageid][$response])) { 319 $pagestats[$temp->pageid][$response]++; 320 } else { 321 $pagestats[$temp->pageid][$response] = 1; 322 } 323 } 324 } else { 325 if (isset($pagestats[$temp->pageid][$temp->answerid])) { 326 $pagestats[$temp->pageid][$temp->answerid]++; 327 } else { 328 $pagestats[$temp->pageid][$temp->answerid] = 1; 329 } 330 } 331 if (isset($pagestats[$temp->pageid]["total"])) { 332 $pagestats[$temp->pageid]["total"]++; 333 } else { 334 $pagestats[$temp->pageid]["total"] = 1; 335 } 336 return true; 337 } 338 339 public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) { 340 $answers = $this->get_used_answers(); 341 $formattextdefoptions = new stdClass; 342 $formattextdefoptions->para = false; //I'll use it widely in this page 343 $formattextdefoptions->context = $answerpage->context; 344 345 foreach ($answers as $answer) { 346 $answertext = format_text($answer->answer,$answer->answerformat,$formattextdefoptions); 347 $correctresponsetext = html_writer::div(get_string('correctresponse', 'lesson'), 'badge badge-success'); 348 if ($this->properties->qoption) { 349 if ($useranswer == null) { 350 $userresponse = array(); 351 } else { 352 $userresponse = explode(",", $useranswer->useranswer); 353 } 354 if (in_array($answer->id, $userresponse)) { 355 // make checked 356 $checkboxelement = "<input readonly=\"readonly\" disabled=\"disabled\" name=\"answer[$i]\" checked=\"checked\" type=\"checkbox\" value=\"1\" />"; 357 if (!isset($answerdata->response)) { 358 if ($answer->response == null) { 359 if ($useranswer->correct) { 360 $answerdata->response = get_string("thatsthecorrectanswer", "lesson"); 361 } else { 362 $answerdata->response = get_string("thatsthewronganswer", "lesson"); 363 } 364 } else { 365 $answerdata->response = $answer->response; 366 } 367 } 368 if (!isset($answerdata->score)) { 369 if ($this->lesson->custom) { 370 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score; 371 } elseif ($useranswer->correct) { 372 $answerdata->score = get_string("receivedcredit", "lesson"); 373 } else { 374 $answerdata->score = get_string("didnotreceivecredit", "lesson"); 375 } 376 } 377 } else { 378 // unchecked 379 $checkboxelement = "<input type=\"checkbox\" readonly=\"readonly\" name=\"answer[$i]\" value=\"0\" disabled=\"disabled\" />"; 380 } 381 $answercontent = html_writer::label($checkboxelement . ' ' . $answertext, null); 382 if (($answer->score > 0 && $this->lesson->custom) || ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto) && !$this->lesson->custom)) { 383 $data = html_writer::div($answercontent, 'text-success') . $correctresponsetext; 384 } else { 385 $data = $answercontent; 386 } 387 } else { 388 if ($useranswer != null and $answer->id == $useranswer->answerid) { 389 // make checked 390 $checkboxelement = "<input readonly=\"readonly\" disabled=\"disabled\" name=\"answer[$i]\" checked=\"checked\" type=\"checkbox\" value=\"1\" />"; 391 if ($answer->response == null) { 392 if ($useranswer->correct) { 393 $answerdata->response = get_string("thatsthecorrectanswer", "lesson"); 394 } else { 395 $answerdata->response = get_string("thatsthewronganswer", "lesson"); 396 } 397 } else { 398 $answerdata->response = $answer->response; 399 } 400 if ($this->lesson->custom) { 401 $answerdata->score = get_string("pointsearned", "lesson").": ".$answer->score; 402 } elseif ($useranswer->correct) { 403 $answerdata->score = get_string("receivedcredit", "lesson"); 404 } else { 405 $answerdata->score = get_string("didnotreceivecredit", "lesson"); 406 } 407 } else { 408 // unchecked 409 $checkboxelement = "<input type=\"checkbox\" readonly=\"readonly\" name=\"answer[$i]\" value=\"0\" disabled=\"disabled\" />"; 410 } 411 $answercontent = html_writer::label($checkboxelement . ' ' . $answertext, null); 412 if (($answer->score > 0 && $this->lesson->custom) || ($this->lesson->jumpto_is_correct($this->properties->id, $answer->jumpto) && !$this->lesson->custom)) { 413 $data = html_writer::div($answercontent, 'text-success') . $correctresponsetext; 414 } else { 415 $data = $answercontent; 416 } 417 } 418 if (isset($pagestats[$this->properties->id][$answer->id])) { 419 $percent = $pagestats[$this->properties->id][$answer->id] / $pagestats[$this->properties->id]["total"] * 100; 420 $percent = round($percent, 2); 421 $percent .= "% ".get_string("checkedthisone", "lesson"); 422 } else { 423 $percent = get_string("noonecheckedthis", "lesson"); 424 } 425 426 $answerdata->answers[] = array($data, $percent); 427 $answerpage->answerdata = $answerdata; 428 } 429 return $answerpage; 430 } 431 } 432 433 434 class lesson_add_page_form_multichoice extends lesson_add_page_form_base { 435 436 public $qtype = 'multichoice'; 437 public $qtypestring = 'multichoice'; 438 protected $answerformat = LESSON_ANSWER_HTML; 439 protected $responseformat = LESSON_ANSWER_HTML; 440 441 public function custom_definition() { 442 443 $this->_form->addElement('checkbox', 'qoption', get_string('options', 'lesson'), get_string('multianswer', 'lesson')); 444 $this->_form->setDefault('qoption', 0); 445 $this->_form->addHelpButton('qoption', 'multianswer', 'lesson'); 446 447 for ($i = 0; $i < $this->_customdata['lesson']->maxanswers; $i++) { 448 $this->_form->addElement('header', 'answertitle'.$i, get_string('answer').' '.($i+1)); 449 $this->add_answer($i, null, ($i<2), $this->get_answer_format()); 450 $this->add_response($i); 451 $this->add_jumpto($i, null, ($i == 0 ? LESSON_NEXTPAGE : LESSON_THISPAGE)); 452 $this->add_score($i, null, ($i===0)?1:0); 453 } 454 } 455 } 456 457 class lesson_display_answer_form_multichoice_singleanswer extends moodleform { 458 459 public function definition() { 460 global $USER, $OUTPUT; 461 $mform = $this->_form; 462 $answers = $this->_customdata['answers']; 463 $lessonid = $this->_customdata['lessonid']; 464 $contents = $this->_customdata['contents']; 465 if (array_key_exists('attempt', $this->_customdata)) { 466 $attempt = $this->_customdata['attempt']; 467 } else { 468 $attempt = new stdClass(); 469 $attempt->answerid = null; 470 } 471 472 // Disable shortforms. 473 $mform->setDisableShortforms(); 474 475 $mform->addElement('header', 'pageheader'); 476 477 $mform->addElement('html', $OUTPUT->container($contents, 'contents')); 478 479 $hasattempt = false; 480 $disabled = ''; 481 if (isset($USER->modattempts[$lessonid]) && !empty($USER->modattempts[$lessonid])) { 482 $hasattempt = true; 483 $disabled = array('disabled' => 'disabled'); 484 } 485 486 $options = new stdClass; 487 $options->para = false; 488 $options->noclean = true; 489 490 $mform->addElement('hidden', 'id'); 491 $mform->setType('id', PARAM_INT); 492 493 $mform->addElement('hidden', 'pageid'); 494 $mform->setType('pageid', PARAM_INT); 495 496 $i = 0; 497 foreach ($answers as $answer) { 498 $mform->addElement('html', '<div class="answeroption">'); 499 $answer->answer = preg_replace('#>$#', '> ', $answer->answer); 500 $mform->addElement('radio','answerid',null,format_text($answer->answer, $answer->answerformat, $options),$answer->id, $disabled); 501 $mform->setType('answer'.$i, PARAM_INT); 502 if ($hasattempt && $answer->id == $USER->modattempts[$lessonid]->answerid) { 503 $mform->setDefault('answerid', $USER->modattempts[$lessonid]->answerid); 504 } 505 $mform->addElement('html', '</div>'); 506 $i++; 507 } 508 509 if ($hasattempt) { 510 $this->add_action_buttons(null, get_string("nextpage", "lesson")); 511 } else { 512 $this->add_action_buttons(null, get_string("submit", "lesson")); 513 } 514 } 515 516 } 517 518 class lesson_display_answer_form_multichoice_multianswer extends moodleform { 519 520 public function definition() { 521 global $USER, $OUTPUT; 522 $mform = $this->_form; 523 $answers = $this->_customdata['answers']; 524 525 $lessonid = $this->_customdata['lessonid']; 526 $contents = $this->_customdata['contents']; 527 528 // Disable shortforms. 529 $mform->setDisableShortforms(); 530 531 $mform->addElement('header', 'pageheader'); 532 533 $mform->addElement('html', $OUTPUT->container($contents, 'contents')); 534 535 $hasattempt = false; 536 $disabled = ''; 537 $useranswers = array(); 538 if (isset($USER->modattempts[$lessonid]) && !empty($USER->modattempts[$lessonid])) { 539 $hasattempt = true; 540 $disabled = array('disabled' => 'disabled'); 541 $useranswers = explode(',', $USER->modattempts[$lessonid]->useranswer); 542 } 543 544 $options = new stdClass; 545 $options->para = false; 546 $options->noclean = true; 547 548 $mform->addElement('hidden', 'id'); 549 $mform->setType('id', PARAM_INT); 550 551 $mform->addElement('hidden', 'pageid'); 552 $mform->setType('pageid', PARAM_INT); 553 554 foreach ($answers as $answer) { 555 $mform->addElement('html', '<div class="answeroption">'); 556 $answerid = 'answer['.$answer->id.']'; 557 if ($hasattempt && in_array($answer->id, $useranswers)) { 558 $answerid = 'answer_'.$answer->id; 559 $mform->addElement('hidden', 'answer['.$answer->id.']', $answer->answer); 560 $mform->setType('answer['.$answer->id.']', PARAM_NOTAGS); 561 $mform->setDefault($answerid, true); 562 $mform->setDefault('answer['.$answer->id.']', true); 563 } 564 // NOTE: our silly checkbox supports only value '1' - we can not use it like the radiobox above!!!!!! 565 $answer->answer = preg_replace('#>$#', '> ', $answer->answer); 566 $mform->addElement('checkbox', $answerid, null, format_text($answer->answer, $answer->answerformat, $options), $disabled); 567 $mform->setType($answerid, PARAM_INT); 568 569 $mform->addElement('html', '</div>'); 570 } 571 572 if ($hasattempt) { 573 $this->add_action_buttons(null, get_string("nextpage", "lesson")); 574 } else { 575 $this->add_action_buttons(null, get_string("submit", "lesson")); 576 } 577 } 578 579 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body