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