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 // 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 renderer for the quiz module. 19 * 20 * @package mod_quiz 21 * @copyright 2011 The Open University 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 29 /** 30 * The renderer for the quiz module. 31 * 32 * @copyright 2011 The Open University 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class mod_quiz_renderer extends plugin_renderer_base { 36 /** 37 * Builds the review page 38 * 39 * @param quiz_attempt $attemptobj an instance of quiz_attempt. 40 * @param array $slots an array of intgers relating to questions. 41 * @param int $page the current page number 42 * @param bool $showall whether to show entire attempt on one page. 43 * @param bool $lastpage if true the current page is the last page. 44 * @param mod_quiz_display_options $displayoptions instance of mod_quiz_display_options. 45 * @param array $summarydata contains all table data 46 * @return $output containing html data. 47 */ 48 public function review_page(quiz_attempt $attemptobj, $slots, $page, $showall, 49 $lastpage, mod_quiz_display_options $displayoptions, 50 $summarydata) { 51 52 $output = ''; 53 $output .= $this->header(); 54 $output .= $this->review_summary_table($summarydata, $page); 55 $output .= $this->review_form($page, $showall, $displayoptions, 56 $this->questions($attemptobj, true, $slots, $page, $showall, $displayoptions), 57 $attemptobj); 58 59 $output .= $this->review_next_navigation($attemptobj, $page, $lastpage, $showall); 60 $output .= $this->footer(); 61 return $output; 62 } 63 64 /** 65 * Renders the review question pop-up. 66 * 67 * @param quiz_attempt $attemptobj an instance of quiz_attempt. 68 * @param int $slot which question to display. 69 * @param int $seq which step of the question attempt to show. null = latest. 70 * @param mod_quiz_display_options $displayoptions instance of mod_quiz_display_options. 71 * @param array $summarydata contains all table data 72 * @return $output containing html data. 73 */ 74 public function review_question_page(quiz_attempt $attemptobj, $slot, $seq, 75 mod_quiz_display_options $displayoptions, $summarydata) { 76 77 $output = ''; 78 $output .= $this->header(); 79 $output .= $this->review_summary_table($summarydata, 0); 80 81 if (!is_null($seq)) { 82 $output .= $attemptobj->render_question_at_step($slot, $seq, true, $this); 83 } else { 84 $output .= $attemptobj->render_question($slot, true, $this); 85 } 86 87 $output .= $this->close_window_button(); 88 $output .= $this->footer(); 89 return $output; 90 } 91 92 /** 93 * Renders the review question pop-up. 94 * 95 * @param quiz_attempt $attemptobj an instance of quiz_attempt. 96 * @param string $message Why the review is not allowed. 97 * @return string html to output. 98 */ 99 public function review_question_not_allowed(quiz_attempt $attemptobj, $message) { 100 $output = ''; 101 $output .= $this->header(); 102 $output .= $this->heading(format_string($attemptobj->get_quiz_name(), true, 103 array("context" => $attemptobj->get_quizobj()->get_context()))); 104 $output .= $this->notification($message); 105 $output .= $this->close_window_button(); 106 $output .= $this->footer(); 107 return $output; 108 } 109 110 /** 111 * Filters the summarydata array. 112 * 113 * @param array $summarydata contains row data for table 114 * @param int $page the current page number 115 * @return $summarydata containing filtered row data 116 */ 117 protected function filter_review_summary_table($summarydata, $page) { 118 if ($page == 0) { 119 return $summarydata; 120 } 121 122 // Only show some of summary table on subsequent pages. 123 foreach ($summarydata as $key => $rowdata) { 124 if (!in_array($key, array('user', 'attemptlist'))) { 125 unset($summarydata[$key]); 126 } 127 } 128 129 return $summarydata; 130 } 131 132 /** 133 * Outputs the table containing data from summary data array 134 * 135 * @param array $summarydata contains row data for table 136 * @param int $page contains the current page number 137 */ 138 public function review_summary_table($summarydata, $page) { 139 $summarydata = $this->filter_review_summary_table($summarydata, $page); 140 if (empty($summarydata)) { 141 return ''; 142 } 143 144 $output = ''; 145 $output .= html_writer::start_tag('table', array( 146 'class' => 'generaltable generalbox quizreviewsummary')); 147 $output .= html_writer::start_tag('tbody'); 148 foreach ($summarydata as $rowdata) { 149 if ($rowdata['title'] instanceof renderable) { 150 $title = $this->render($rowdata['title']); 151 } else { 152 $title = $rowdata['title']; 153 } 154 155 if ($rowdata['content'] instanceof renderable) { 156 $content = $this->render($rowdata['content']); 157 } else { 158 $content = $rowdata['content']; 159 } 160 161 $output .= html_writer::tag('tr', 162 html_writer::tag('th', $title, array('class' => 'cell', 'scope' => 'row')) . 163 html_writer::tag('td', $content, array('class' => 'cell')) 164 ); 165 } 166 167 $output .= html_writer::end_tag('tbody'); 168 $output .= html_writer::end_tag('table'); 169 return $output; 170 } 171 172 /** 173 * Renders each question 174 * 175 * @param quiz_attempt $attemptobj instance of quiz_attempt 176 * @param bool $reviewing 177 * @param array $slots array of intgers relating to questions 178 * @param int $page current page number 179 * @param bool $showall if true shows attempt on single page 180 * @param mod_quiz_display_options $displayoptions instance of mod_quiz_display_options 181 */ 182 public function questions(quiz_attempt $attemptobj, $reviewing, $slots, $page, $showall, 183 mod_quiz_display_options $displayoptions) { 184 $output = ''; 185 foreach ($slots as $slot) { 186 $output .= $attemptobj->render_question($slot, $reviewing, $this, 187 $attemptobj->review_url($slot, $page, $showall)); 188 } 189 return $output; 190 } 191 192 /** 193 * Renders the main bit of the review page. 194 * 195 * @param array $summarydata contain row data for table 196 * @param int $page current page number 197 * @param mod_quiz_display_options $displayoptions instance of mod_quiz_display_options 198 * @param $content contains each question 199 * @param quiz_attempt $attemptobj instance of quiz_attempt 200 * @param bool $showall if true display attempt on one page 201 */ 202 public function review_form($page, $showall, $displayoptions, $content, $attemptobj) { 203 if ($displayoptions->flags != question_display_options::EDITABLE) { 204 return $content; 205 } 206 207 $this->page->requires->js_init_call('M.mod_quiz.init_review_form', null, false, 208 quiz_get_js_module()); 209 210 $output = ''; 211 $output .= html_writer::start_tag('form', array('action' => $attemptobj->review_url(null, 212 $page, $showall), 'method' => 'post', 'class' => 'questionflagsaveform')); 213 $output .= html_writer::start_tag('div'); 214 $output .= $content; 215 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 216 'value' => sesskey())); 217 $output .= html_writer::start_tag('div', array('class' => 'submitbtns')); 218 $output .= html_writer::empty_tag('input', array('type' => 'submit', 219 'class' => 'questionflagsavebutton btn btn-secondary', 'name' => 'savingflags', 220 'value' => get_string('saveflags', 'question'))); 221 $output .= html_writer::end_tag('div'); 222 $output .= html_writer::end_tag('div'); 223 $output .= html_writer::end_tag('form'); 224 225 return $output; 226 } 227 228 /** 229 * Returns either a liink or button 230 * 231 * @param quiz_attempt $attemptobj instance of quiz_attempt 232 */ 233 public function finish_review_link(quiz_attempt $attemptobj) { 234 $url = $attemptobj->view_url(); 235 236 if ($attemptobj->get_access_manager(time())->attempt_must_be_in_popup()) { 237 $this->page->requires->js_init_call('M.mod_quiz.secure_window.init_close_button', 238 array($url), false, quiz_get_js_module()); 239 return html_writer::empty_tag('input', array('type' => 'button', 240 'value' => get_string('finishreview', 'quiz'), 241 'id' => 'secureclosebutton', 242 'class' => 'mod_quiz-next-nav btn btn-primary')); 243 244 } else { 245 return html_writer::link($url, get_string('finishreview', 'quiz'), 246 array('class' => 'mod_quiz-next-nav')); 247 } 248 } 249 250 /** 251 * Creates the navigation links/buttons at the bottom of the reivew attempt page. 252 * 253 * Note, the name of this function is no longer accurate, but when the design 254 * changed, it was decided to keep the old name for backwards compatibility. 255 * 256 * @param quiz_attempt $attemptobj instance of quiz_attempt 257 * @param int $page the current page 258 * @param bool $lastpage if true current page is the last page 259 * @param bool|null $showall if true, the URL will be to review the entire attempt on one page, 260 * and $page will be ignored. If null, a sensible default will be chosen. 261 * 262 * @return string HTML fragment. 263 */ 264 public function review_next_navigation(quiz_attempt $attemptobj, $page, $lastpage, $showall = null) { 265 $nav = ''; 266 if ($page > 0) { 267 $nav .= link_arrow_left(get_string('navigateprevious', 'quiz'), 268 $attemptobj->review_url(null, $page - 1, $showall), false, 'mod_quiz-prev-nav'); 269 } 270 if ($lastpage) { 271 $nav .= $this->finish_review_link($attemptobj); 272 } else { 273 $nav .= link_arrow_right(get_string('navigatenext', 'quiz'), 274 $attemptobj->review_url(null, $page + 1, $showall), false, 'mod_quiz-next-nav'); 275 } 276 return html_writer::tag('div', $nav, array('class' => 'submitbtns')); 277 } 278 279 /** 280 * Return the HTML of the quiz timer. 281 * @return string HTML content. 282 */ 283 public function countdown_timer(quiz_attempt $attemptobj, $timenow) { 284 285 $timeleft = $attemptobj->get_time_left_display($timenow); 286 if ($timeleft !== false) { 287 $ispreview = $attemptobj->is_preview(); 288 $timerstartvalue = $timeleft; 289 if (!$ispreview) { 290 // Make sure the timer starts just above zero. If $timeleft was <= 0, then 291 // this will just have the effect of causing the quiz to be submitted immediately. 292 $timerstartvalue = max($timerstartvalue, 1); 293 } 294 $this->initialise_timer($timerstartvalue, $ispreview); 295 } 296 297 return html_writer::tag('div', get_string('timeleft', 'quiz') . ' ' . 298 html_writer::tag('span', '', array('id' => 'quiz-time-left')), 299 array('id' => 'quiz-timer', 'role' => 'timer', 300 'aria-atomic' => 'true', 'aria-relevant' => 'text')); 301 } 302 303 /** 304 * Create a preview link 305 * 306 * @param moodle_url $url contains a url to the given page 307 */ 308 public function restart_preview_button($url) { 309 return $this->single_button($url, get_string('startnewpreview', 'quiz')); 310 } 311 312 /** 313 * Outputs the navigation block panel 314 * 315 * @param quiz_nav_panel_base $panel instance of quiz_nav_panel_base 316 */ 317 public function navigation_panel(quiz_nav_panel_base $panel) { 318 319 $output = ''; 320 $userpicture = $panel->user_picture(); 321 if ($userpicture) { 322 $fullname = fullname($userpicture->user); 323 if ($userpicture->size === true) { 324 $fullname = html_writer::div($fullname); 325 } 326 $output .= html_writer::tag('div', $this->render($userpicture) . $fullname, 327 array('id' => 'user-picture', 'class' => 'clearfix')); 328 } 329 $output .= $panel->render_before_button_bits($this); 330 331 $bcc = $panel->get_button_container_class(); 332 $output .= html_writer::start_tag('div', array('class' => "qn_buttons clearfix $bcc")); 333 foreach ($panel->get_question_buttons() as $button) { 334 $output .= $this->render($button); 335 } 336 $output .= html_writer::end_tag('div'); 337 338 $output .= html_writer::tag('div', $panel->render_end_bits($this), 339 array('class' => 'othernav')); 340 341 $this->page->requires->js_init_call('M.mod_quiz.nav.init', null, false, 342 quiz_get_js_module()); 343 344 return $output; 345 } 346 347 /** 348 * Display a quiz navigation button. 349 * 350 * @param quiz_nav_question_button $button 351 * @return string HTML fragment. 352 */ 353 protected function render_quiz_nav_question_button(quiz_nav_question_button $button) { 354 $classes = array('qnbutton', $button->stateclass, $button->navmethod, 'btn'); 355 $extrainfo = array(); 356 357 if ($button->currentpage) { 358 $classes[] = 'thispage'; 359 $extrainfo[] = get_string('onthispage', 'quiz'); 360 } 361 362 // Flagged? 363 if ($button->flagged) { 364 $classes[] = 'flagged'; 365 $flaglabel = get_string('flagged', 'question'); 366 } else { 367 $flaglabel = ''; 368 } 369 $extrainfo[] = html_writer::tag('span', $flaglabel, array('class' => 'flagstate')); 370 371 if (is_numeric($button->number)) { 372 $qnostring = 'questionnonav'; 373 } else { 374 $qnostring = 'questionnonavinfo'; 375 } 376 377 $a = new stdClass(); 378 $a->number = $button->number; 379 $a->attributes = implode(' ', $extrainfo); 380 $tagcontents = html_writer::tag('span', '', array('class' => 'thispageholder')) . 381 html_writer::tag('span', '', array('class' => 'trafficlight')) . 382 get_string($qnostring, 'quiz', $a); 383 $tagattributes = array('class' => implode(' ', $classes), 'id' => $button->id, 384 'title' => $button->statestring, 'data-quiz-page' => $button->page); 385 386 if ($button->url) { 387 return html_writer::link($button->url, $tagcontents, $tagattributes); 388 } else { 389 return html_writer::tag('span', $tagcontents, $tagattributes); 390 } 391 } 392 393 /** 394 * Display a quiz navigation heading. 395 * 396 * @param quiz_nav_section_heading $heading the heading. 397 * @return string HTML fragment. 398 */ 399 protected function render_quiz_nav_section_heading(quiz_nav_section_heading $heading) { 400 return $this->heading($heading->heading, 3, 'mod_quiz-section-heading'); 401 } 402 403 /** 404 * outputs the link the other attempts. 405 * 406 * @param mod_quiz_links_to_other_attempts $links 407 */ 408 protected function render_mod_quiz_links_to_other_attempts( 409 mod_quiz_links_to_other_attempts $links) { 410 $attemptlinks = array(); 411 foreach ($links->links as $attempt => $url) { 412 if (!$url) { 413 $attemptlinks[] = html_writer::tag('strong', $attempt); 414 } else if ($url instanceof renderable) { 415 $attemptlinks[] = $this->render($url); 416 } else { 417 $attemptlinks[] = html_writer::link($url, $attempt); 418 } 419 } 420 return implode(', ', $attemptlinks); 421 } 422 423 public function start_attempt_page(quiz $quizobj, mod_quiz_preflight_check_form $mform) { 424 $output = ''; 425 $output .= $this->header(); 426 $output .= $this->heading(format_string($quizobj->get_quiz_name(), true, 427 array("context" => $quizobj->get_context()))); 428 $output .= $this->quiz_intro($quizobj->get_quiz(), $quizobj->get_cm()); 429 $output .= $mform->render(); 430 $output .= $this->footer(); 431 return $output; 432 } 433 434 /** 435 * Attempt Page 436 * 437 * @param quiz_attempt $attemptobj Instance of quiz_attempt 438 * @param int $page Current page number 439 * @param quiz_access_manager $accessmanager Instance of quiz_access_manager 440 * @param array $messages An array of messages 441 * @param array $slots Contains an array of integers that relate to questions 442 * @param int $id The ID of an attempt 443 * @param int $nextpage The number of the next page 444 */ 445 public function attempt_page($attemptobj, $page, $accessmanager, $messages, $slots, $id, 446 $nextpage) { 447 $output = ''; 448 $output .= $this->header(); 449 $output .= $this->quiz_notices($messages); 450 $output .= $this->attempt_form($attemptobj, $page, $slots, $id, $nextpage); 451 $output .= $this->footer(); 452 return $output; 453 } 454 455 /** 456 * Returns any notices. 457 * 458 * @param array $messages 459 */ 460 public function quiz_notices($messages) { 461 if (!$messages) { 462 return ''; 463 } 464 return $this->box($this->heading(get_string('accessnoticesheader', 'quiz'), 3) . 465 $this->access_messages($messages), 'quizaccessnotices'); 466 } 467 468 /** 469 * Ouputs the form for making an attempt 470 * 471 * @param quiz_attempt $attemptobj 472 * @param int $page Current page number 473 * @param array $slots Array of integers relating to questions 474 * @param int $id ID of the attempt 475 * @param int $nextpage Next page number 476 */ 477 public function attempt_form($attemptobj, $page, $slots, $id, $nextpage) { 478 $output = ''; 479 480 // Start the form. 481 $output .= html_writer::start_tag('form', 482 array('action' => new moodle_url($attemptobj->processattempt_url(), 483 array('cmid' => $attemptobj->get_cmid())), 'method' => 'post', 484 'enctype' => 'multipart/form-data', 'accept-charset' => 'utf-8', 485 'id' => 'responseform')); 486 $output .= html_writer::start_tag('div'); 487 488 // Print all the questions. 489 foreach ($slots as $slot) { 490 $output .= $attemptobj->render_question($slot, false, $this, 491 $attemptobj->attempt_url($slot, $page), $this); 492 } 493 494 $navmethod = $attemptobj->get_quiz()->navmethod; 495 $output .= $this->attempt_navigation_buttons($page, $attemptobj->is_last_page($page), $navmethod); 496 497 // Some hidden fields to trach what is going on. 498 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'attempt', 499 'value' => $attemptobj->get_attemptid())); 500 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'thispage', 501 'value' => $page, 'id' => 'followingpage')); 502 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'nextpage', 503 'value' => $nextpage)); 504 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'timeup', 505 'value' => '0', 'id' => 'timeup')); 506 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 507 'value' => sesskey())); 508 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'scrollpos', 509 'value' => '', 'id' => 'scrollpos')); 510 511 // Add a hidden field with questionids. Do this at the end of the form, so 512 // if you navigate before the form has finished loading, it does not wipe all 513 // the student's answers. 514 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'slots', 515 'value' => implode(',', $attemptobj->get_active_slots($page)))); 516 517 // Finish the form. 518 $output .= html_writer::end_tag('div'); 519 $output .= html_writer::end_tag('form'); 520 521 $output .= $this->connection_warning(); 522 523 return $output; 524 } 525 526 /** 527 * Display the prev/next buttons that go at the bottom of each page of the attempt. 528 * 529 * @param int $page the page number. Starts at 0 for the first page. 530 * @param bool $lastpage is this the last page in the quiz? 531 * @param string $navmethod Optional quiz attribute, 'free' (default) or 'sequential' 532 * @return string HTML fragment. 533 */ 534 protected function attempt_navigation_buttons($page, $lastpage, $navmethod = 'free') { 535 $output = ''; 536 537 $output .= html_writer::start_tag('div', array('class' => 'submitbtns')); 538 if ($page > 0 && $navmethod == 'free') { 539 $output .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'previous', 540 'value' => get_string('navigateprevious', 'quiz'), 'class' => 'mod_quiz-prev-nav btn btn-secondary')); 541 } 542 if ($lastpage) { 543 $nextlabel = get_string('endtest', 'quiz'); 544 } else { 545 $nextlabel = get_string('navigatenext', 'quiz'); 546 } 547 $output .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'next', 548 'value' => $nextlabel, 'class' => 'mod_quiz-next-nav btn btn-primary')); 549 $output .= html_writer::end_tag('div'); 550 551 return $output; 552 } 553 554 /** 555 * Render a button which allows students to redo a question in the attempt. 556 * 557 * @param int $slot the number of the slot to generate the button for. 558 * @param bool $disabled if true, output the button disabled. 559 * @return string HTML fragment. 560 */ 561 public function redo_question_button($slot, $disabled) { 562 $attributes = array('type' => 'submit', 'name' => 'redoslot' . $slot, 563 'value' => get_string('redoquestion', 'quiz'), 564 'class' => 'mod_quiz-redo_question_button btn btn-secondary'); 565 if ($disabled) { 566 $attributes['disabled'] = 'disabled'; 567 } 568 return html_writer::div(html_writer::empty_tag('input', $attributes)); 569 } 570 571 /** 572 * Output the JavaScript required to initialise the countdown timer. 573 * @param int $timerstartvalue time remaining, in seconds. 574 */ 575 public function initialise_timer($timerstartvalue, $ispreview) { 576 $options = array($timerstartvalue, (bool)$ispreview); 577 $this->page->requires->js_init_call('M.mod_quiz.timer.init', $options, false, quiz_get_js_module()); 578 } 579 580 /** 581 * Output a page with an optional message, and JavaScript code to close the 582 * current window and redirect the parent window to a new URL. 583 * @param moodle_url $url the URL to redirect the parent window to. 584 * @param string $message message to display before closing the window. (optional) 585 * @return string HTML to output. 586 */ 587 public function close_attempt_popup($url, $message = '') { 588 $output = ''; 589 $output .= $this->header(); 590 $output .= $this->box_start(); 591 592 if ($message) { 593 $output .= html_writer::tag('p', $message); 594 $output .= html_writer::tag('p', get_string('windowclosing', 'quiz')); 595 $delay = 5; 596 } else { 597 $output .= html_writer::tag('p', get_string('pleaseclose', 'quiz')); 598 $delay = 0; 599 } 600 $this->page->requires->js_init_call('M.mod_quiz.secure_window.close', 601 array($url, $delay), false, quiz_get_js_module()); 602 603 $output .= $this->box_end(); 604 $output .= $this->footer(); 605 return $output; 606 } 607 608 /** 609 * Print each message in an array, surrounded by <p>, </p> tags. 610 * 611 * @param array $messages the array of message strings. 612 * @param bool $return if true, return a string, instead of outputting. 613 * 614 * @return string HTML to output. 615 */ 616 public function access_messages($messages) { 617 $output = ''; 618 foreach ($messages as $message) { 619 $output .= html_writer::tag('p', $message) . "\n"; 620 } 621 return $output; 622 } 623 624 /* 625 * Summary Page 626 */ 627 /** 628 * Create the summary page 629 * 630 * @param quiz_attempt $attemptobj 631 * @param mod_quiz_display_options $displayoptions 632 */ 633 public function summary_page($attemptobj, $displayoptions) { 634 $output = ''; 635 $output .= $this->header(); 636 $output .= $this->heading(format_string($attemptobj->get_quiz_name())); 637 $output .= $this->heading(get_string('summaryofattempt', 'quiz'), 3); 638 $output .= $this->summary_table($attemptobj, $displayoptions); 639 $output .= $this->summary_page_controls($attemptobj); 640 $output .= $this->footer(); 641 return $output; 642 } 643 644 /** 645 * Generates the table of summarydata 646 * 647 * @param quiz_attempt $attemptobj 648 * @param mod_quiz_display_options $displayoptions 649 */ 650 public function summary_table($attemptobj, $displayoptions) { 651 // Prepare the summary table header. 652 $table = new html_table(); 653 $table->attributes['class'] = 'generaltable quizsummaryofattempt boxaligncenter'; 654 $table->head = array(get_string('question', 'quiz'), get_string('status', 'quiz')); 655 $table->align = array('left', 'left'); 656 $table->size = array('', ''); 657 $markscolumn = $displayoptions->marks >= question_display_options::MARK_AND_MAX; 658 if ($markscolumn) { 659 $table->head[] = get_string('marks', 'quiz'); 660 $table->align[] = 'left'; 661 $table->size[] = ''; 662 } 663 $tablewidth = count($table->align); 664 $table->data = array(); 665 666 // Get the summary info for each question. 667 $slots = $attemptobj->get_slots(); 668 foreach ($slots as $slot) { 669 // Add a section headings if we need one here. 670 $heading = $attemptobj->get_heading_before_slot($slot); 671 if ($heading) { 672 $cell = new html_table_cell(format_string($heading)); 673 $cell->header = true; 674 $cell->colspan = $tablewidth; 675 $table->data[] = array($cell); 676 $table->rowclasses[] = 'quizsummaryheading'; 677 } 678 679 // Don't display information items. 680 if (!$attemptobj->is_real_question($slot)) { 681 continue; 682 } 683 684 // Real question, show it. 685 $flag = ''; 686 if ($attemptobj->is_question_flagged($slot)) { 687 // Quiz has custom JS manipulating these image tags - so we can't use the pix_icon method here. 688 $flag = html_writer::empty_tag('img', array('src' => $this->image_url('i/flagged'), 689 'alt' => get_string('flagged', 'question'), 'class' => 'questionflag icon-post')); 690 } 691 if ($attemptobj->can_navigate_to($slot)) { 692 $row = array(html_writer::link($attemptobj->attempt_url($slot), 693 $attemptobj->get_question_number($slot) . $flag), 694 $attemptobj->get_question_status($slot, $displayoptions->correctness)); 695 } else { 696 $row = array($attemptobj->get_question_number($slot) . $flag, 697 $attemptobj->get_question_status($slot, $displayoptions->correctness)); 698 } 699 if ($markscolumn) { 700 $row[] = $attemptobj->get_question_mark($slot); 701 } 702 $table->data[] = $row; 703 $table->rowclasses[] = 'quizsummary' . $slot . ' ' . $attemptobj->get_question_state_class( 704 $slot, $displayoptions->correctness); 705 } 706 707 // Print the summary table. 708 $output = html_writer::table($table); 709 710 return $output; 711 } 712 713 /** 714 * Creates any controls a the page should have. 715 * 716 * @param quiz_attempt $attemptobj 717 */ 718 public function summary_page_controls($attemptobj) { 719 $output = ''; 720 721 // Return to place button. 722 if ($attemptobj->get_state() == quiz_attempt::IN_PROGRESS) { 723 $button = new single_button( 724 new moodle_url($attemptobj->attempt_url(null, $attemptobj->get_currentpage())), 725 get_string('returnattempt', 'quiz')); 726 $output .= $this->container($this->container($this->render($button), 727 'controls'), 'submitbtns mdl-align'); 728 } 729 730 // Finish attempt button. 731 $options = array( 732 'attempt' => $attemptobj->get_attemptid(), 733 'finishattempt' => 1, 734 'timeup' => 0, 735 'slots' => '', 736 'cmid' => $attemptobj->get_cmid(), 737 'sesskey' => sesskey(), 738 ); 739 740 $button = new single_button( 741 new moodle_url($attemptobj->processattempt_url(), $options), 742 get_string('submitallandfinish', 'quiz')); 743 $button->id = 'responseform'; 744 if ($attemptobj->get_state() == quiz_attempt::IN_PROGRESS) { 745 $button->add_action(new confirm_action(get_string('confirmclose', 'quiz'), null, 746 get_string('submitallandfinish', 'quiz'))); 747 } 748 749 $duedate = $attemptobj->get_due_date(); 750 $message = ''; 751 if ($attemptobj->get_state() == quiz_attempt::OVERDUE) { 752 $message = get_string('overduemustbesubmittedby', 'quiz', userdate($duedate)); 753 754 } else if ($duedate) { 755 $message = get_string('mustbesubmittedby', 'quiz', userdate($duedate)); 756 } 757 758 $output .= $this->countdown_timer($attemptobj, time()); 759 $output .= $this->container($message . $this->container( 760 $this->render($button), 'controls'), 'submitbtns mdl-align'); 761 762 return $output; 763 } 764 765 /* 766 * View Page 767 */ 768 /** 769 * Generates the view page 770 * 771 * @param int $course The id of the course 772 * @param array $quiz Array conting quiz data 773 * @param int $cm Course Module ID 774 * @param int $context The page context ID 775 * @param array $infomessages information about this quiz 776 * @param mod_quiz_view_object $viewobj 777 * @param string $buttontext text for the start/continue attempt button, if 778 * it should be shown. 779 * @param array $infomessages further information about why the student cannot 780 * attempt this quiz now, if appicable this quiz 781 */ 782 public function view_page($course, $quiz, $cm, $context, $viewobj) { 783 $output = ''; 784 $output .= $this->view_information($quiz, $cm, $context, $viewobj->infomessages); 785 $output .= $this->view_table($quiz, $context, $viewobj); 786 $output .= $this->view_result_info($quiz, $context, $cm, $viewobj); 787 $output .= $this->box($this->view_page_buttons($viewobj), 'quizattempt'); 788 return $output; 789 } 790 791 /** 792 * Work out, and render, whatever buttons, and surrounding info, should appear 793 * at the end of the review page. 794 * @param mod_quiz_view_object $viewobj the information required to display 795 * the view page. 796 * @return string HTML to output. 797 */ 798 public function view_page_buttons(mod_quiz_view_object $viewobj) { 799 global $CFG; 800 $output = ''; 801 802 if (!$viewobj->quizhasquestions) { 803 $output .= $this->no_questions_message($viewobj->canedit, $viewobj->editurl); 804 } 805 806 $output .= $this->access_messages($viewobj->preventmessages); 807 808 if ($viewobj->buttontext) { 809 $output .= $this->start_attempt_button($viewobj->buttontext, 810 $viewobj->startattempturl, $viewobj->preflightcheckform, 811 $viewobj->popuprequired, $viewobj->popupoptions); 812 } 813 814 if ($viewobj->showbacktocourse) { 815 $output .= $this->single_button($viewobj->backtocourseurl, 816 get_string('backtocourse', 'quiz'), 'get', 817 array('class' => 'continuebutton')); 818 } 819 820 return $output; 821 } 822 823 /** 824 * Generates the view attempt button 825 * 826 * @param string $buttontext the label to display on the button. 827 * @param moodle_url $url The URL to POST to in order to start the attempt. 828 * @param mod_quiz_preflight_check_form $preflightcheckform deprecated. 829 * @param bool $popuprequired whether the attempt needs to be opened in a pop-up. 830 * @param array $popupoptions the options to use if we are opening a popup. 831 * @return string HTML fragment. 832 */ 833 public function start_attempt_button($buttontext, moodle_url $url, 834 mod_quiz_preflight_check_form $preflightcheckform = null, 835 $popuprequired = false, $popupoptions = null) { 836 837 if (is_string($preflightcheckform)) { 838 // Calling code was not updated since the API change. 839 debugging('The third argument to start_attempt_button should now be the ' . 840 'mod_quiz_preflight_check_form from ' . 841 'quiz_access_manager::get_preflight_check_form, not a warning message string.'); 842 } 843 844 $button = new single_button($url, $buttontext); 845 $button->class .= ' quizstartbuttondiv'; 846 if ($popuprequired) { 847 $button->class .= ' quizsecuremoderequired'; 848 } 849 850 $popupjsoptions = null; 851 if ($popuprequired && $popupoptions) { 852 $action = new popup_action('click', $url, 'popup', $popupoptions); 853 $popupjsoptions = $action->get_js_options(); 854 } 855 856 if ($preflightcheckform) { 857 $checkform = $preflightcheckform->render(); 858 } else { 859 $checkform = null; 860 } 861 862 $this->page->requires->js_call_amd('mod_quiz/preflightcheck', 'init', 863 array('.quizstartbuttondiv [type=submit]', get_string('startattempt', 'quiz'), 864 '#mod_quiz_preflight_form', $popupjsoptions)); 865 866 return $this->render($button) . $checkform; 867 } 868 869 /** 870 * Generate a message saying that this quiz has no questions, with a button to 871 * go to the edit page, if the user has the right capability. 872 * @param object $quiz the quiz settings. 873 * @param object $cm the course_module object. 874 * @param object $context the quiz context. 875 * @return string HTML to output. 876 */ 877 public function no_questions_message($canedit, $editurl) { 878 $output = ''; 879 $output .= $this->notification(get_string('noquestions', 'quiz')); 880 if ($canedit) { 881 $output .= $this->single_button($editurl, get_string('editquiz', 'quiz'), 'get'); 882 } 883 884 return $output; 885 } 886 887 /** 888 * Outputs an error message for any guests accessing the quiz 889 * 890 * @param int $course The course ID 891 * @param array $quiz Array contingin quiz data 892 * @param int $cm Course Module ID 893 * @param int $context The page contect ID 894 * @param array $messages Array containing any messages 895 */ 896 public function view_page_guest($course, $quiz, $cm, $context, $messages) { 897 $output = ''; 898 $output .= $this->view_information($quiz, $cm, $context, $messages); 899 $guestno = html_writer::tag('p', get_string('guestsno', 'quiz')); 900 $liketologin = html_writer::tag('p', get_string('liketologin')); 901 $referer = get_local_referer(false); 902 $output .= $this->confirm($guestno."\n\n".$liketologin."\n", get_login_url(), $referer); 903 return $output; 904 } 905 906 /** 907 * Outputs and error message for anyone who is not enrolle don the course 908 * 909 * @param int $course The course ID 910 * @param array $quiz Array contingin quiz data 911 * @param int $cm Course Module ID 912 * @param int $context The page contect ID 913 * @param array $messages Array containing any messages 914 */ 915 public function view_page_notenrolled($course, $quiz, $cm, $context, $messages) { 916 global $CFG; 917 $output = ''; 918 $output .= $this->view_information($quiz, $cm, $context, $messages); 919 $youneedtoenrol = html_writer::tag('p', get_string('youneedtoenrol', 'quiz')); 920 $button = html_writer::tag('p', 921 $this->continue_button($CFG->wwwroot . '/course/view.php?id=' . $course->id)); 922 $output .= $this->box($youneedtoenrol."\n\n".$button."\n", 'generalbox', 'notice'); 923 return $output; 924 } 925 926 /** 927 * Output the page information 928 * 929 * @param object $quiz the quiz settings. 930 * @param object $cm the course_module object. 931 * @param object $context the quiz context. 932 * @param array $messages any access messages that should be described. 933 * @return string HTML to output. 934 */ 935 public function view_information($quiz, $cm, $context, $messages) { 936 global $CFG; 937 938 $output = ''; 939 940 // Print quiz name and description. 941 $output .= $this->heading(format_string($quiz->name)); 942 $output .= $this->quiz_intro($quiz, $cm); 943 944 // Output any access messages. 945 if ($messages) { 946 $output .= $this->box($this->access_messages($messages), 'quizinfo'); 947 } 948 949 // Show number of attempts summary to those who can view reports. 950 if (has_capability('mod/quiz:viewreports', $context)) { 951 if ($strattemptnum = $this->quiz_attempt_summary_link_to_reports($quiz, $cm, 952 $context)) { 953 $output .= html_writer::tag('div', $strattemptnum, 954 array('class' => 'quizattemptcounts')); 955 } 956 } 957 return $output; 958 } 959 960 /** 961 * Output the quiz intro. 962 * @param object $quiz the quiz settings. 963 * @param object $cm the course_module object. 964 * @return string HTML to output. 965 */ 966 public function quiz_intro($quiz, $cm) { 967 if (html_is_blank($quiz->intro)) { 968 return ''; 969 } 970 971 return $this->box(format_module_intro('quiz', $quiz, $cm->id), 'generalbox', 'intro'); 972 } 973 974 /** 975 * Generates the table heading. 976 */ 977 public function view_table_heading() { 978 return $this->heading(get_string('summaryofattempts', 'quiz'), 3); 979 } 980 981 /** 982 * Generates the table of data 983 * 984 * @param array $quiz Array contining quiz data 985 * @param int $context The page context ID 986 * @param mod_quiz_view_object $viewobj 987 */ 988 public function view_table($quiz, $context, $viewobj) { 989 if (!$viewobj->attempts) { 990 return ''; 991 } 992 993 // Prepare table header. 994 $table = new html_table(); 995 $table->attributes['class'] = 'generaltable quizattemptsummary'; 996 $table->head = array(); 997 $table->align = array(); 998 $table->size = array(); 999 if ($viewobj->attemptcolumn) { 1000 $table->head[] = get_string('attemptnumber', 'quiz'); 1001 $table->align[] = 'center'; 1002 $table->size[] = ''; 1003 } 1004 $table->head[] = get_string('attemptstate', 'quiz'); 1005 $table->align[] = 'left'; 1006 $table->size[] = ''; 1007 if ($viewobj->markcolumn) { 1008 $table->head[] = get_string('marks', 'quiz') . ' / ' . 1009 quiz_format_grade($quiz, $quiz->sumgrades); 1010 $table->align[] = 'center'; 1011 $table->size[] = ''; 1012 } 1013 if ($viewobj->gradecolumn) { 1014 $table->head[] = get_string('grade') . ' / ' . 1015 quiz_format_grade($quiz, $quiz->grade); 1016 $table->align[] = 'center'; 1017 $table->size[] = ''; 1018 } 1019 if ($viewobj->canreviewmine) { 1020 $table->head[] = get_string('review', 'quiz'); 1021 $table->align[] = 'center'; 1022 $table->size[] = ''; 1023 } 1024 if ($viewobj->feedbackcolumn) { 1025 $table->head[] = get_string('feedback', 'quiz'); 1026 $table->align[] = 'left'; 1027 $table->size[] = ''; 1028 } 1029 1030 // One row for each attempt. 1031 foreach ($viewobj->attemptobjs as $attemptobj) { 1032 $attemptoptions = $attemptobj->get_display_options(true); 1033 $row = array(); 1034 1035 // Add the attempt number. 1036 if ($viewobj->attemptcolumn) { 1037 if ($attemptobj->is_preview()) { 1038 $row[] = get_string('preview', 'quiz'); 1039 } else { 1040 $row[] = $attemptobj->get_attempt_number(); 1041 } 1042 } 1043 1044 $row[] = $this->attempt_state($attemptobj); 1045 1046 if ($viewobj->markcolumn) { 1047 if ($attemptoptions->marks >= question_display_options::MARK_AND_MAX && 1048 $attemptobj->is_finished()) { 1049 $row[] = quiz_format_grade($quiz, $attemptobj->get_sum_marks()); 1050 } else { 1051 $row[] = ''; 1052 } 1053 } 1054 1055 // Ouside the if because we may be showing feedback but not grades. 1056 $attemptgrade = quiz_rescale_grade($attemptobj->get_sum_marks(), $quiz, false); 1057 1058 if ($viewobj->gradecolumn) { 1059 if ($attemptoptions->marks >= question_display_options::MARK_AND_MAX && 1060 $attemptobj->is_finished()) { 1061 1062 // Highlight the highest grade if appropriate. 1063 if ($viewobj->overallstats && !$attemptobj->is_preview() 1064 && $viewobj->numattempts > 1 && !is_null($viewobj->mygrade) 1065 && $attemptobj->get_state() == quiz_attempt::FINISHED 1066 && $attemptgrade == $viewobj->mygrade 1067 && $quiz->grademethod == QUIZ_GRADEHIGHEST) { 1068 $table->rowclasses[$attemptobj->get_attempt_number()] = 'bestrow'; 1069 } 1070 1071 $row[] = quiz_format_grade($quiz, $attemptgrade); 1072 } else { 1073 $row[] = ''; 1074 } 1075 } 1076 1077 if ($viewobj->canreviewmine) { 1078 $row[] = $viewobj->accessmanager->make_review_link($attemptobj->get_attempt(), 1079 $attemptoptions, $this); 1080 } 1081 1082 if ($viewobj->feedbackcolumn && $attemptobj->is_finished()) { 1083 if ($attemptoptions->overallfeedback) { 1084 $row[] = quiz_feedback_for_grade($attemptgrade, $quiz, $context); 1085 } else { 1086 $row[] = ''; 1087 } 1088 } 1089 1090 if ($attemptobj->is_preview()) { 1091 $table->data['preview'] = $row; 1092 } else { 1093 $table->data[$attemptobj->get_attempt_number()] = $row; 1094 } 1095 } // End of loop over attempts. 1096 1097 $output = ''; 1098 $output .= $this->view_table_heading(); 1099 $output .= html_writer::table($table); 1100 return $output; 1101 } 1102 1103 /** 1104 * Generate a brief textual desciption of the current state of an attempt. 1105 * @param quiz_attempt $attemptobj the attempt 1106 * @param int $timenow the time to use as 'now'. 1107 * @return string the appropriate lang string to describe the state. 1108 */ 1109 public function attempt_state($attemptobj) { 1110 switch ($attemptobj->get_state()) { 1111 case quiz_attempt::IN_PROGRESS: 1112 return get_string('stateinprogress', 'quiz'); 1113 1114 case quiz_attempt::OVERDUE: 1115 return get_string('stateoverdue', 'quiz') . html_writer::tag('span', 1116 get_string('stateoverduedetails', 'quiz', 1117 userdate($attemptobj->get_due_date())), 1118 array('class' => 'statedetails')); 1119 1120 case quiz_attempt::FINISHED: 1121 return get_string('statefinished', 'quiz') . html_writer::tag('span', 1122 get_string('statefinisheddetails', 'quiz', 1123 userdate($attemptobj->get_submitted_date())), 1124 array('class' => 'statedetails')); 1125 1126 case quiz_attempt::ABANDONED: 1127 return get_string('stateabandoned', 'quiz'); 1128 } 1129 } 1130 1131 /** 1132 * Generates data pertaining to quiz results 1133 * 1134 * @param array $quiz Array containing quiz data 1135 * @param int $context The page context ID 1136 * @param int $cm The Course Module Id 1137 * @param mod_quiz_view_object $viewobj 1138 */ 1139 public function view_result_info($quiz, $context, $cm, $viewobj) { 1140 $output = ''; 1141 if (!$viewobj->numattempts && !$viewobj->gradecolumn && is_null($viewobj->mygrade)) { 1142 return $output; 1143 } 1144 $resultinfo = ''; 1145 1146 if ($viewobj->overallstats) { 1147 if ($viewobj->moreattempts) { 1148 $a = new stdClass(); 1149 $a->method = quiz_get_grading_option_name($quiz->grademethod); 1150 $a->mygrade = quiz_format_grade($quiz, $viewobj->mygrade); 1151 $a->quizgrade = quiz_format_grade($quiz, $quiz->grade); 1152 $resultinfo .= $this->heading(get_string('gradesofar', 'quiz', $a), 3); 1153 } else { 1154 $a = new stdClass(); 1155 $a->grade = quiz_format_grade($quiz, $viewobj->mygrade); 1156 $a->maxgrade = quiz_format_grade($quiz, $quiz->grade); 1157 $a = get_string('outofshort', 'quiz', $a); 1158 $resultinfo .= $this->heading(get_string('yourfinalgradeis', 'quiz', $a), 3); 1159 } 1160 } 1161 1162 if ($viewobj->mygradeoverridden) { 1163 1164 $resultinfo .= html_writer::tag('p', get_string('overriddennotice', 'grades'), 1165 array('class' => 'overriddennotice'))."\n"; 1166 } 1167 if ($viewobj->gradebookfeedback) { 1168 $resultinfo .= $this->heading(get_string('comment', 'quiz'), 3); 1169 $resultinfo .= html_writer::div($viewobj->gradebookfeedback, 'quizteacherfeedback') . "\n"; 1170 } 1171 if ($viewobj->feedbackcolumn) { 1172 $resultinfo .= $this->heading(get_string('overallfeedback', 'quiz'), 3); 1173 $resultinfo .= html_writer::div( 1174 quiz_feedback_for_grade($viewobj->mygrade, $quiz, $context), 1175 'quizgradefeedback') . "\n"; 1176 } 1177 1178 if ($resultinfo) { 1179 $output .= $this->box($resultinfo, 'generalbox', 'feedback'); 1180 } 1181 return $output; 1182 } 1183 1184 /** 1185 * Output either a link to the review page for an attempt, or a button to 1186 * open the review in a popup window. 1187 * 1188 * @param moodle_url $url of the target page. 1189 * @param bool $reviewinpopup whether a pop-up is required. 1190 * @param array $popupoptions options to pass to the popup_action constructor. 1191 * @return string HTML to output. 1192 */ 1193 public function review_link($url, $reviewinpopup, $popupoptions) { 1194 if ($reviewinpopup) { 1195 $button = new single_button($url, get_string('review', 'quiz')); 1196 $button->add_action(new popup_action('click', $url, 'quizpopup', $popupoptions)); 1197 return $this->render($button); 1198 1199 } else { 1200 return html_writer::link($url, get_string('review', 'quiz'), 1201 array('title' => get_string('reviewthisattempt', 'quiz'))); 1202 } 1203 } 1204 1205 /** 1206 * Displayed where there might normally be a review link, to explain why the 1207 * review is not available at this time. 1208 * @param string $message optional message explaining why the review is not possible. 1209 * @return string HTML to output. 1210 */ 1211 public function no_review_message($message) { 1212 return html_writer::nonempty_tag('span', $message, 1213 array('class' => 'noreviewmessage')); 1214 } 1215 1216 /** 1217 * Returns the same as {@link quiz_num_attempt_summary()} but wrapped in a link 1218 * to the quiz reports. 1219 * 1220 * @param object $quiz the quiz object. Only $quiz->id is used at the moment. 1221 * @param object $cm the cm object. Only $cm->course, $cm->groupmode and $cm->groupingid 1222 * fields are used at the moment. 1223 * @param object $context the quiz context. 1224 * @param bool $returnzero if false (default), when no attempts have been made '' is returned 1225 * instead of 'Attempts: 0'. 1226 * @param int $currentgroup if there is a concept of current group where this method is being 1227 * called 1228 * (e.g. a report) pass it in here. Default 0 which means no current group. 1229 * @return string HTML fragment for the link. 1230 */ 1231 public function quiz_attempt_summary_link_to_reports($quiz, $cm, $context, 1232 $returnzero = false, $currentgroup = 0) { 1233 global $CFG; 1234 $summary = quiz_num_attempt_summary($quiz, $cm, $returnzero, $currentgroup); 1235 if (!$summary) { 1236 return ''; 1237 } 1238 1239 require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php'); 1240 $url = new moodle_url('/mod/quiz/report.php', array( 1241 'id' => $cm->id, 'mode' => quiz_report_default_report($context))); 1242 return html_writer::link($url, $summary); 1243 } 1244 1245 /** 1246 * Outputs a chart. 1247 * 1248 * @param \core\chart_base $chart The chart. 1249 * @param string $title The title to display above the graph. 1250 * @param array $attrs extra container html attributes. 1251 * @return string HTML fragment for the graph. 1252 */ 1253 public function chart(\core\chart_base $chart, $title, $attrs = []) { 1254 return $this->heading($title, 3) . html_writer::tag('div', 1255 $this->render($chart), array_merge(['class' => 'graph'], $attrs)); 1256 } 1257 1258 /** 1259 * Output a graph, or a message saying that GD is required. 1260 * @param moodle_url $url the URL of the graph. 1261 * @param string $title the title to display above the graph. 1262 * @return string HTML fragment for the graph. 1263 */ 1264 public function graph(moodle_url $url, $title) { 1265 global $CFG; 1266 1267 $graph = html_writer::empty_tag('img', array('src' => $url, 'alt' => $title)); 1268 1269 return $this->heading($title, 3) . html_writer::tag('div', $graph, array('class' => 'graph')); 1270 } 1271 1272 /** 1273 * Output the connection warning messages, which are initially hidden, and 1274 * only revealed by JavaScript if necessary. 1275 */ 1276 public function connection_warning() { 1277 $options = array('filter' => false, 'newlines' => false); 1278 $warning = format_text(get_string('connectionerror', 'quiz'), FORMAT_MARKDOWN, $options); 1279 $ok = format_text(get_string('connectionok', 'quiz'), FORMAT_MARKDOWN, $options); 1280 return html_writer::tag('div', $warning, 1281 array('id' => 'connection-error', 'style' => 'display: none;', 'role' => 'alert')) . 1282 html_writer::tag('div', $ok, array('id' => 'connection-ok', 'style' => 'display: none;', 'role' => 'alert')); 1283 } 1284 } 1285 1286 1287 class mod_quiz_links_to_other_attempts implements renderable { 1288 /** 1289 * @var array string attempt number => url, or null for the current attempt. 1290 * url may be either a moodle_url, or a renderable. 1291 */ 1292 public $links = array(); 1293 } 1294 1295 1296 class mod_quiz_view_object { 1297 /** @var array $infomessages of messages with information to display about the quiz. */ 1298 public $infomessages; 1299 /** @var array $attempts contains all the user's attempts at this quiz. */ 1300 public $attempts; 1301 /** @var array $attemptobjs quiz_attempt objects corresponding to $attempts. */ 1302 public $attemptobjs; 1303 /** @var quiz_access_manager $accessmanager contains various access rules. */ 1304 public $accessmanager; 1305 /** @var bool $canreviewmine whether the current user has the capability to 1306 * review their own attempts. */ 1307 public $canreviewmine; 1308 /** @var bool $canedit whether the current user has the capability to edit the quiz. */ 1309 public $canedit; 1310 /** @var moodle_url $editurl the URL for editing this quiz. */ 1311 public $editurl; 1312 /** @var int $attemptcolumn contains the number of attempts done. */ 1313 public $attemptcolumn; 1314 /** @var int $gradecolumn contains the grades of any attempts. */ 1315 public $gradecolumn; 1316 /** @var int $markcolumn contains the marks of any attempt. */ 1317 public $markcolumn; 1318 /** @var int $overallstats contains all marks for any attempt. */ 1319 public $overallstats; 1320 /** @var string $feedbackcolumn contains any feedback for and attempt. */ 1321 public $feedbackcolumn; 1322 /** @var string $timenow contains a timestamp in string format. */ 1323 public $timenow; 1324 /** @var int $numattempts contains the total number of attempts. */ 1325 public $numattempts; 1326 /** @var float $mygrade contains the user's final grade for a quiz. */ 1327 public $mygrade; 1328 /** @var bool $moreattempts whether this user is allowed more attempts. */ 1329 public $moreattempts; 1330 /** @var int $mygradeoverridden contains an overriden grade. */ 1331 public $mygradeoverridden; 1332 /** @var string $gradebookfeedback contains any feedback for a gradebook. */ 1333 public $gradebookfeedback; 1334 /** @var bool $unfinished contains 1 if an attempt is unfinished. */ 1335 public $unfinished; 1336 /** @var object $lastfinishedattempt the last attempt from the attempts array. */ 1337 public $lastfinishedattempt; 1338 /** @var array $preventmessages of messages telling the user why they can't 1339 * attempt the quiz now. */ 1340 public $preventmessages; 1341 /** @var string $buttontext caption for the start attempt button. If this is null, show no 1342 * button, or if it is '' show a back to the course button. */ 1343 public $buttontext; 1344 /** @var moodle_url $startattempturl URL to start an attempt. */ 1345 public $startattempturl; 1346 /** @var moodleform|null $preflightcheckform confirmation form that must be 1347 * submitted before an attempt is started, if required. */ 1348 public $preflightcheckform; 1349 /** @var moodle_url $startattempturl URL for any Back to the course button. */ 1350 public $backtocourseurl; 1351 /** @var bool $showbacktocourse should we show a back to the course button? */ 1352 public $showbacktocourse; 1353 /** @var bool whether the attempt must take place in a popup window. */ 1354 public $popuprequired; 1355 /** @var array options to use for the popup window, if required. */ 1356 public $popupoptions; 1357 /** @var bool $quizhasquestions whether the quiz has any questions. */ 1358 public $quizhasquestions; 1359 1360 public function __get($field) { 1361 switch ($field) { 1362 case 'startattemptwarning': 1363 debugging('startattemptwarning has been deprecated. It is now always blank.'); 1364 return ''; 1365 1366 default: 1367 debugging('Unknown property ' . $field); 1368 return null; 1369 } 1370 } 1371 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body