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