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