Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   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 mod_quiz\quiz_attempt;
  20  use moodle_url;
  21  use question_attempt;
  22  use question_display_options;
  23  use question_state;
  24  use renderable;
  25  use user_picture;
  26  
  27  /**
  28   * Represents the navigation panel, and builds a {@see block_contents} to allow it to be output.
  29   *
  30   * This class is not currently renderable or templatable, but it probably should be in the future,
  31   * which is why it is already in the output namespace.
  32   *
  33   * @package   mod_quiz
  34   * @category  output
  35   * @copyright 2008 Tim Hunt
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  abstract class navigation_panel_base {
  39      /** @var quiz_attempt */
  40      protected $attemptobj;
  41      /** @var question_display_options */
  42      protected $options;
  43      /** @var integer */
  44      protected $page;
  45      /** @var boolean */
  46      protected $showall;
  47  
  48      /**
  49       * Constructor.
  50       *
  51       * @param quiz_attempt $attemptobj construct the panel for this attempt.
  52       * @param question_display_options $options display options in force.
  53       * @param int $page which page of the quiz attempt is being shown, -1 if all.
  54       * @param bool $showall whether all pages are being shown at once.
  55       */
  56      public function __construct(quiz_attempt $attemptobj,
  57              question_display_options $options, $page, $showall) {
  58          $this->attemptobj = $attemptobj;
  59          $this->options = $options;
  60          $this->page = $page;
  61          $this->showall = $showall;
  62      }
  63  
  64      /**
  65       * Get the buttons and section headings to go in the quiz navigation block.
  66       *
  67       * @return renderable[] the buttons, possibly interleaved with section headings.
  68       */
  69      public function get_question_buttons() {
  70          $buttons = [];
  71          foreach ($this->attemptobj->get_slots() as $slot) {
  72              $heading = $this->attemptobj->get_heading_before_slot($slot);
  73              if (!is_null($heading)) {
  74                  $sections = $this->attemptobj->get_quizobj()->get_sections();
  75                  if (!(empty($heading) && count($sections) == 1)) {
  76                      $buttons[] = new navigation_section_heading(format_string($heading));
  77                  }
  78              }
  79  
  80              $qa = $this->attemptobj->get_question_attempt($slot);
  81              $showcorrectness = $this->options->correctness && $qa->has_marks();
  82  
  83              $button = new navigation_question_button();
  84              $button->id          = 'quiznavbutton' . $slot;
  85              $button->isrealquestion = $this->attemptobj->is_real_question($slot);
  86              $button->number      = $this->attemptobj->get_question_number($slot);
  87              $button->stateclass  = $qa->get_state_class($showcorrectness);
  88              $button->navmethod   = $this->attemptobj->get_navigation_method();
  89              if (!$showcorrectness && $button->stateclass === 'notanswered') {
  90                  $button->stateclass = 'complete';
  91              }
  92              $button->statestring = $this->get_state_string($qa, $showcorrectness);
  93              $button->page        = $this->attemptobj->get_question_page($slot);
  94              $button->currentpage = $this->showall || $button->page == $this->page;
  95              $button->flagged     = $qa->is_flagged();
  96              $button->url         = $this->get_question_url($slot);
  97              if ($this->attemptobj->is_blocked_by_previous_question($slot)) {
  98                  $button->url = null;
  99                  $button->stateclass = 'blocked';
 100                  $button->statestring = get_string('questiondependsonprevious', 'quiz');
 101              }
 102              $buttons[] = $button;
 103          }
 104  
 105          return $buttons;
 106      }
 107  
 108      /**
 109       * Get the human-readable description of the current state of a particular question.
 110       *
 111       * @param question_attempt $qa the attempt at the question of interest.
 112       * @param bool $showcorrectness whether the current use is allowed to see if they have got the question right.
 113       * @return string Human-readable description of the state.
 114       */
 115      protected function get_state_string(question_attempt $qa, $showcorrectness) {
 116          if ($qa->get_question(false)->length > 0) {
 117              return $qa->get_state_string($showcorrectness);
 118          }
 119  
 120          // Special case handling for 'information' items.
 121          if ($qa->get_state() == question_state::$todo) {
 122              return get_string('notyetviewed', 'quiz');
 123          } else {
 124              return get_string('viewed', 'quiz');
 125          }
 126      }
 127  
 128      /**
 129       * Hook for subclasses to override to do output above the question buttons.
 130       *
 131       * @param renderer $output the quiz renderer to use.
 132       * @return string HTML to output.
 133       */
 134      public function render_before_button_bits(renderer $output) {
 135          return '';
 136      }
 137  
 138      /**
 139       * Hook that subclasses must override to do output after the question buttons.
 140       *
 141       * @param renderer $output the quiz renderer to use.
 142       * @return string HTML to output.
 143       */
 144      abstract public function render_end_bits(renderer $output);
 145  
 146      /**
 147       * Render the restart preview button.
 148       *
 149       * @param renderer $output the quiz renderer to use.
 150       * @return string HTML to output.
 151       */
 152      protected function render_restart_preview_link($output) {
 153          if (!$this->attemptobj->is_own_preview()) {
 154              return '';
 155          }
 156          return $output->restart_preview_button(new moodle_url(
 157                  $this->attemptobj->start_attempt_url(), ['forcenew' => true]));
 158      }
 159  
 160      /**
 161       * Get the URL to navigate to a particular question.
 162       *
 163       * @param int $slot slot number, to identify the question.
 164       * @return moodle_url|null URL if the user can navigate there, or null if they cannot.
 165       */
 166      abstract protected function get_question_url($slot);
 167  
 168      /**
 169       * Get the user picture which should be displayed, if required.
 170       *
 171       * @return user_picture|null
 172       */
 173      public function user_picture() {
 174          global $DB;
 175          if ($this->attemptobj->get_quiz()->showuserpicture == QUIZ_SHOWIMAGE_NONE) {
 176              return null;
 177          }
 178          $user = $DB->get_record('user', ['id' => $this->attemptobj->get_userid()]);
 179          $userpicture = new user_picture($user);
 180          $userpicture->courseid = $this->attemptobj->get_courseid();
 181          if ($this->attemptobj->get_quiz()->showuserpicture == QUIZ_SHOWIMAGE_LARGE) {
 182              $userpicture->size = true;
 183          }
 184          return $userpicture;
 185      }
 186  
 187      /**
 188       * Return 'allquestionsononepage' as CSS class name when $showall is set,
 189       * otherwise, return 'multipages' as CSS class name.
 190       *
 191       * @return string, CSS class name
 192       */
 193      public function get_button_container_class() {
 194          // Quiz navigation is set on 'Show all questions on one page'.
 195          if ($this->showall) {
 196              return 'allquestionsononepage';
 197          }
 198          // Quiz navigation is set on 'Show one page at a time'.
 199          return 'multipages';
 200      }
 201  }