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.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Renderer outputting the quiz editing UI.
  19   *
  20   * @package mod_quiz
  21   * @copyright 2013 The Open University.
  22   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace mod_quiz\output;
  26  
  27  use core_question\local\bank\question_version_status;
  28  use mod_quiz\question\bank\qbank_helper;
  29  use \mod_quiz\structure;
  30  use \html_writer;
  31  use qbank_previewquestion\question_preview_options;
  32  use renderable;
  33  
  34  /**
  35   * Renderer outputting the quiz editing UI.
  36   *
  37   * @copyright 2013 The Open University.
  38   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   * @since Moodle 2.7
  40   */
  41  class edit_renderer extends \plugin_renderer_base {
  42  
  43      /** @var string The toggle group name of the checkboxes for the toggle-all functionality. */
  44      protected $togglegroup = 'quiz-questions';
  45  
  46      /**
  47       * Render the edit page
  48       *
  49       * @param \mod_quiz\quiz_settings $quizobj object containing all the quiz settings information.
  50       * @param structure $structure object containing the structure of the quiz.
  51       * @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
  52       * @param \moodle_url $pageurl the canonical URL of this page.
  53       * @param array $pagevars the variables from {@link question_edit_setup()}.
  54       * @return string HTML to output.
  55       */
  56      public function edit_page(\mod_quiz\quiz_settings $quizobj, structure $structure,
  57          \core_question\local\bank\question_edit_contexts $contexts, \moodle_url $pageurl, array $pagevars) {
  58          $output = '';
  59  
  60          // Page title.
  61          $output .= $this->heading(get_string('questions', 'quiz'));
  62  
  63          // Information at the top.
  64          $output .= $this->quiz_state_warnings($structure);
  65  
  66          $output .= html_writer::start_div('mod_quiz-edit-top-controls');
  67  
  68          $output .= html_writer::start_div('d-flex justify-content-between flex-wrap mb-1');
  69          $output .= html_writer::start_div('d-flex flex-column justify-content-around');
  70          $output .= $this->quiz_information($structure);
  71          $output .= html_writer::end_tag('div');
  72          $output .= $this->maximum_grade_input($structure, $pageurl);
  73          $output .= html_writer::end_tag('div');
  74  
  75          $output .= html_writer::start_div('d-flex justify-content-between flex-wrap mb-1');
  76          $output .= html_writer::start_div('mod_quiz-edit-action-buttons btn-group edit-toolbar', ['role' => 'group']);
  77          $output .= $this->repaginate_button($structure, $pageurl);
  78          $output .= $this->selectmultiple_button($structure);
  79          $output .= html_writer::end_tag('div');
  80  
  81          $output .= html_writer::start_div('d-flex flex-column justify-content-around');
  82          $output .= $this->total_marks($quizobj->get_quiz());
  83          $output .= html_writer::end_tag('div');
  84          $output .= html_writer::end_tag('div');
  85  
  86          $output .= $this->selectmultiple_controls($structure);
  87          $output .= html_writer::end_tag('div');
  88  
  89          // Show the questions organised into sections and pages.
  90          $output .= $this->start_section_list($structure);
  91  
  92          foreach ($structure->get_sections() as $section) {
  93              $output .= $this->start_section($structure, $section);
  94              $output .= $this->questions_in_section($structure, $section, $contexts, $pagevars, $pageurl);
  95  
  96              if ($structure->is_last_section($section)) {
  97                  $output .= \html_writer::start_div('last-add-menu');
  98                  $output .= html_writer::tag('span', $this->add_menu_actions($structure, 0,
  99                          $pageurl, $contexts, $pagevars), ['class' => 'add-menu-outer']);
 100                  $output .= \html_writer::end_div();
 101              }
 102  
 103              $output .= $this->end_section();
 104          }
 105  
 106          $output .= $this->end_section_list();
 107  
 108          // Initialise the JavaScript.
 109          $this->initialise_editing_javascript($structure, $contexts, $pagevars, $pageurl);
 110  
 111          // Include the contents of any other popups required.
 112          if ($structure->can_be_edited()) {
 113              $thiscontext = $contexts->lowest();
 114              $this->page->requires->js_call_amd('mod_quiz/quizquestionbank', 'init', [
 115                  $thiscontext->id
 116              ]);
 117  
 118              $this->page->requires->js_call_amd('mod_quiz/add_random_question', 'init', [
 119                  $thiscontext->id,
 120                  $pagevars['cat'],
 121                  $pageurl->out_as_local_url(true),
 122                  $pageurl->param('cmid'),
 123                  \core\plugininfo\qbank::is_plugin_enabled(\qbank_managecategories\helper::PLUGINNAME),
 124              ]);
 125  
 126              // Include the question chooser.
 127              $output .= $this->question_chooser();
 128          }
 129  
 130          return $output;
 131      }
 132  
 133      /**
 134       * Render any warnings that might be required about the state of the quiz,
 135       * e.g. if it has been attempted, or if the shuffle questions option is
 136       * turned on.
 137       *
 138       * @param structure $structure the quiz structure.
 139       * @return string HTML to output.
 140       */
 141      public function quiz_state_warnings(structure $structure) {
 142          $warnings = $structure->get_edit_page_warnings();
 143  
 144          if (empty($warnings)) {
 145              return '';
 146          }
 147  
 148          $output = [];
 149          foreach ($warnings as $warning) {
 150              $output[] = \html_writer::tag('p', $warning);
 151          }
 152          return $this->box(implode("\n", $output), 'statusdisplay');
 153      }
 154  
 155      /**
 156       * Render the status bar.
 157       *
 158       * @param structure $structure the quiz structure.
 159       * @return string HTML to output.
 160       */
 161      public function quiz_information(structure $structure) {
 162          list($currentstatus, $explanation) = $structure->get_dates_summary();
 163  
 164          $output = html_writer::span(
 165                      get_string('numquestionsx', 'quiz', $structure->get_question_count()),
 166                      'numberofquestions') . ' | ' .
 167                  html_writer::span($currentstatus, 'quizopeningstatus',
 168                      ['title' => $explanation]);
 169  
 170          return html_writer::div($output, 'statusbar');
 171      }
 172  
 173      /**
 174       * Render the form for setting a quiz' overall grade
 175       *
 176       * @param structure $structure the quiz structure.
 177       * @param \moodle_url $pageurl the canonical URL of this page.
 178       * @return string HTML to output.
 179       */
 180      public function maximum_grade_input($structure, \moodle_url $pageurl) {
 181          $output = '';
 182          $output .= html_writer::start_div('maxgrade');
 183          $output .= html_writer::start_tag('form', ['method' => 'post', 'action' => 'edit.php',
 184                  'class' => 'quizsavegradesform form-inline']);
 185          $output .= html_writer::start_tag('fieldset', ['class' => 'invisiblefieldset']);
 186          $output .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]);
 187          $output .= html_writer::input_hidden_params($pageurl);
 188          $output .= html_writer::tag('label', get_string('maximumgrade') . ' ',
 189                  ['for' => 'inputmaxgrade']);
 190          $output .= html_writer::empty_tag('input', ['type' => 'text', 'id' => 'inputmaxgrade',
 191                  'name' => 'maxgrade', 'size' => ($structure->get_decimal_places_for_grades() + 2),
 192                  'value' => $structure->formatted_quiz_grade(),
 193                  'class' => 'form-control']);
 194          $output .= html_writer::empty_tag('input', ['type' => 'submit', 'class' => 'btn btn-secondary ml-1',
 195                  'name' => 'savechanges', 'value' => get_string('save', 'quiz')]);
 196          $output .= html_writer::end_tag('fieldset');
 197          $output .= html_writer::end_tag('form');
 198          $output .= html_writer::end_tag('div');
 199          return $output;
 200      }
 201  
 202      /**
 203       * Return the repaginate button
 204       * @param structure $structure the structure of the quiz being edited.
 205       * @param \moodle_url $pageurl the canonical URL of this page.
 206       * @return string HTML to output.
 207       */
 208      protected function repaginate_button(structure $structure, \moodle_url $pageurl) {
 209          $header = html_writer::tag('span', get_string('repaginatecommand', 'quiz'), ['class' => 'repaginatecommand']);
 210          $form = $this->repaginate_form($structure, $pageurl);
 211  
 212          $buttonoptions = [
 213              'type'  => 'submit',
 214              'name'  => 'repaginate',
 215              'id'    => 'repaginatecommand',
 216              'value' => get_string('repaginatecommand', 'quiz'),
 217              'class' => 'btn btn-secondary mr-1',
 218              'data-header' => $header,
 219              'data-form'   => $form,
 220          ];
 221          if (!$structure->can_be_repaginated()) {
 222              $buttonoptions['disabled'] = 'disabled';
 223          } else {
 224              $this->page->requires->js_call_amd('mod_quiz/repaginate', 'init');
 225          }
 226  
 227          return html_writer::empty_tag('input', $buttonoptions);
 228      }
 229  
 230      /**
 231       * Generate the bulk action button.
 232       *
 233       * @param structure $structure the structure of the quiz being edited.
 234       * @return string HTML to output.
 235       */
 236      protected function selectmultiple_button(structure $structure) {
 237          $buttonoptions = [
 238              'type'  => 'button',
 239              'name'  => 'selectmultiple',
 240              'id'    => 'selectmultiplecommand',
 241              'value' => get_string('selectmultipleitems', 'quiz'),
 242              'class' => 'btn btn-secondary'
 243          ];
 244          if (!$structure->can_be_edited()) {
 245              $buttonoptions['disabled'] = 'disabled';
 246          }
 247  
 248          return html_writer::tag('button', get_string('selectmultipleitems', 'quiz'), $buttonoptions);
 249      }
 250  
 251      /**
 252       * Generate the controls that appear when the bulk action button is pressed.
 253       *
 254       * @param structure $structure the structure of the quiz being edited.
 255       * @return string HTML to output.
 256       */
 257      protected function selectmultiple_controls(structure $structure) {
 258          $output = '';
 259  
 260          // Bulk action button delete and bulk action button cancel.
 261          $buttondeleteoptions = [
 262              'type' => 'button',
 263              'id' => 'selectmultipledeletecommand',
 264              'value' => get_string('deleteselected', 'mod_quiz'),
 265              'class' => 'btn btn-secondary',
 266              'data-action' => 'toggle',
 267              'data-togglegroup' => $this->togglegroup,
 268              'data-toggle' => 'action',
 269              'disabled' => true
 270          ];
 271          $buttoncanceloptions = [
 272              'type' => 'button',
 273              'id' => 'selectmultiplecancelcommand',
 274              'value' => get_string('cancel', 'moodle'),
 275              'class' => 'btn btn-secondary'
 276          ];
 277  
 278          $groupoptions = [
 279              'class' => 'btn-group selectmultiplecommand actions m-1',
 280              'role' => 'group'
 281          ];
 282  
 283          $output .= html_writer::tag('div',
 284                          html_writer::tag('button', get_string('deleteselected', 'mod_quiz'), $buttondeleteoptions) .
 285                          " " .
 286                          html_writer::tag('button', get_string('cancel', 'moodle'),
 287                  $buttoncanceloptions), $groupoptions);
 288  
 289          $toolbaroptions = [
 290              'class' => 'btn-toolbar m-1',
 291              'role' => 'toolbar',
 292              'aria-label' => get_string('selectmultipletoolbar', 'quiz'),
 293          ];
 294  
 295          // Select all/deselect all questions.
 296          $selectallid = 'questionselectall';
 297          $selectalltext = get_string('selectall', 'moodle');
 298          $deselectalltext = get_string('deselectall', 'moodle');
 299          $mastercheckbox = new \core\output\checkbox_toggleall($this->togglegroup, true, [
 300              'id' => $selectallid,
 301              'name' => $selectallid,
 302              'value' => 1,
 303              'label' => $selectalltext,
 304              'selectall' => $selectalltext,
 305              'deselectall' => $deselectalltext,
 306          ], true);
 307  
 308          $selectdeselect = html_writer::div($this->render($mastercheckbox), 'selectmultiplecommandbuttons');
 309          $output .= html_writer::tag('div', $selectdeselect, $toolbaroptions);
 310          return $output;
 311      }
 312  
 313      /**
 314       * Return the repaginate form
 315       * @param structure $structure the structure of the quiz being edited.
 316       * @param \moodle_url $pageurl the canonical URL of this page.
 317       * @return string HTML to output.
 318       */
 319      protected function repaginate_form(structure $structure, \moodle_url $pageurl) {
 320          $perpage = [];
 321          $perpage[0] = get_string('allinone', 'quiz');
 322          for ($i = 1; $i <= 50; ++$i) {
 323              $perpage[$i] = $i;
 324          }
 325  
 326          $hiddenurl = clone($pageurl);
 327          $hiddenurl->param('sesskey', sesskey());
 328  
 329          $select = html_writer::select($perpage, 'questionsperpage',
 330                  $structure->get_questions_per_page(), false, ['class' => 'custom-select']);
 331  
 332          $buttonattributes = [
 333              'type' => 'submit',
 334              'name' => 'repaginate',
 335              'value' => get_string('go'),
 336              'class' => 'btn btn-secondary ml-1'
 337          ];
 338  
 339          $formcontent = html_writer::tag('form', html_writer::div(
 340                      html_writer::input_hidden_params($hiddenurl) .
 341                      get_string('repaginate', 'quiz', $select) .
 342                      html_writer::empty_tag('input', $buttonattributes)
 343                  ), ['action' => 'edit.php', 'method' => 'post']);
 344  
 345          return html_writer::div($formcontent, '', ['id' => 'repaginatedialog']);
 346      }
 347  
 348      /**
 349       * Render the total marks available for the quiz.
 350       *
 351       * @param \stdClass $quiz the quiz settings from the database.
 352       * @return string HTML to output.
 353       */
 354      public function total_marks($quiz) {
 355          $totalmark = html_writer::span(quiz_format_grade($quiz, $quiz->sumgrades), 'mod_quiz_summarks');
 356          return html_writer::tag('span',
 357                  get_string('totalmarksx', 'quiz', $totalmark),
 358                  ['class' => 'totalpoints']);
 359      }
 360  
 361      /**
 362       * Generate the starting container html for the start of a list of sections
 363       * @param structure $structure the structure of the quiz being edited.
 364       * @return string HTML to output.
 365       */
 366      protected function start_section_list(structure $structure) {
 367          $class = 'slots';
 368          if ($structure->get_section_count() == 1) {
 369              $class .= ' only-one-section';
 370          }
 371          return html_writer::start_tag('ul', ['class' => $class, 'role' => 'presentation']);
 372      }
 373  
 374      /**
 375       * Generate the closing container html for the end of a list of sections
 376       * @return string HTML to output.
 377       */
 378      protected function end_section_list() {
 379          return html_writer::end_tag('ul');
 380      }
 381  
 382      /**
 383       * Display the start of a section, before the questions.
 384       *
 385       * @param structure $structure the structure of the quiz being edited.
 386       * @param \stdClass $section The quiz_section entry from DB
 387       * @return string HTML to output.
 388       */
 389      protected function start_section($structure, $section) {
 390  
 391          $output = '';
 392  
 393          $sectionstyle = '';
 394          if ($structure->is_only_one_slot_in_section($section)) {
 395              $sectionstyle .= ' only-has-one-slot';
 396          }
 397          if ($section->shufflequestions) {
 398              $sectionstyle .= ' shuffled';
 399          }
 400  
 401          if ($section->heading) {
 402              $sectionheadingtext = format_string($section->heading);
 403              $sectionheading = html_writer::span($sectionheadingtext, 'instancesection');
 404          } else {
 405              // Use a sr-only default section heading, so we don't end up with an empty section heading.
 406              $sectionheadingtext = get_string('sectionnoname', 'quiz');
 407              $sectionheading = html_writer::span($sectionheadingtext, 'instancesection sr-only');
 408          }
 409  
 410          $output .= html_writer::start_tag('li', ['id' => 'section-'.$section->id,
 411              'class' => 'section main clearfix'.$sectionstyle, 'role' => 'presentation',
 412              'data-sectionname' => $sectionheadingtext]);
 413  
 414          $output .= html_writer::start_div('content');
 415  
 416          $output .= html_writer::start_div('section-heading');
 417  
 418          $headingtext = $this->heading(html_writer::span($sectionheading, 'sectioninstance'), 3);
 419  
 420          if (!$structure->can_be_edited()) {
 421              $editsectionheadingicon = '';
 422          } else {
 423              $editsectionheadingicon = html_writer::link(new \moodle_url('#'),
 424                  $this->pix_icon('t/editstring', get_string('sectionheadingedit', 'quiz', $sectionheadingtext),
 425                          'moodle', ['class' => 'editicon visibleifjs']),
 426                          ['class' => 'editing_section', 'data-action' => 'edit_section_title', 'role' => 'button']);
 427          }
 428          $output .= html_writer::div($headingtext . $editsectionheadingicon, 'instancesectioncontainer');
 429  
 430          if (!$structure->is_first_section($section) && $structure->can_be_edited()) {
 431              $output .= $this->section_remove_icon($section);
 432          }
 433          $output .= $this->section_shuffle_questions($structure, $section);
 434  
 435          $output .= html_writer::end_div($output, 'section-heading');
 436  
 437          return $output;
 438      }
 439  
 440      /**
 441       * Display a checkbox for shuffling question within a section.
 442       *
 443       * @param structure $structure object containing the structure of the quiz.
 444       * @param \stdClass $section data from the quiz_section table.
 445       * @return string HTML to output.
 446       */
 447      public function section_shuffle_questions(structure $structure, $section) {
 448          $checkboxattributes = [
 449              'type' => 'checkbox',
 450              'id' => 'shuffle-' . $section->id,
 451              'value' => 1,
 452              'data-action' => 'shuffle_questions',
 453              'class' => 'cm-edit-action',
 454          ];
 455  
 456          if (!$structure->can_be_edited()) {
 457              $checkboxattributes['disabled'] = 'disabled';
 458          }
 459          if ($section->shufflequestions) {
 460              $checkboxattributes['checked'] = 'checked';
 461          }
 462  
 463          if ($structure->is_first_section($section)) {
 464              $help = $this->help_icon('shufflequestions', 'quiz');
 465          } else {
 466              $help = '';
 467          }
 468  
 469          $helpspan = html_writer::span($help, 'shuffle-help-tip');
 470          $progressspan = html_writer::span('', 'shuffle-progress');
 471          $checkbox = html_writer::empty_tag('input', $checkboxattributes);
 472          $label = html_writer::label(get_string('shufflequestions', 'quiz'),
 473                  $checkboxattributes['id'], false);
 474          return html_writer::span($progressspan . $checkbox . $label. ' ' . $helpspan,
 475                  'instanceshufflequestions', ['data-action' => 'shuffle_questions']);
 476      }
 477  
 478      /**
 479       * Display the end of a section, after the questions.
 480       *
 481       * @return string HTML to output.
 482       */
 483      protected function end_section() {
 484          $output = html_writer::end_tag('div');
 485          $output .= html_writer::end_tag('li');
 486  
 487          return $output;
 488      }
 489  
 490      /**
 491       * Render an icon to remove a section from the quiz.
 492       *
 493       * @param stdClass $section the section to be removed.
 494       * @return string HTML to output.
 495       */
 496      public function section_remove_icon($section) {
 497          $title = get_string('sectionheadingremove', 'quiz', format_string($section->heading));
 498          $url = new \moodle_url('/mod/quiz/edit.php',
 499                  ['sesskey' => sesskey(), 'removesection' => '1', 'sectionid' => $section->id]);
 500          $image = $this->pix_icon('t/delete', $title);
 501          return $this->action_link($url, $image, null, [
 502                  'class' => 'cm-edit-action editing_delete', 'data-action' => 'deletesection']);
 503      }
 504  
 505      /**
 506       * Renders HTML to display the questions in a section of the quiz.
 507       *
 508       * This function calls {@link core_course_renderer::quiz_section_question()}
 509       *
 510       * @param structure $structure object containing the structure of the quiz.
 511       * @param \stdClass $section information about the section.
 512       * @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
 513       * @param array $pagevars the variables from {@link \question_edit_setup()}.
 514       * @param \moodle_url $pageurl the canonical URL of this page.
 515       * @return string HTML to output.
 516       */
 517      public function questions_in_section(structure $structure, $section,
 518              $contexts, $pagevars, $pageurl) {
 519  
 520          $output = '';
 521          foreach ($structure->get_slots_in_section($section->id) as $slot) {
 522              $output .= $this->question_row($structure, $slot, $contexts, $pagevars, $pageurl);
 523          }
 524          return html_writer::tag('ul', $output, ['class' => 'section img-text']);
 525      }
 526  
 527      /**
 528       * Displays one question with the surrounding controls.
 529       *
 530       * @param structure $structure object containing the structure of the quiz.
 531       * @param int $slot which slot we are outputting.
 532       * @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
 533       * @param array $pagevars the variables from {@link \question_edit_setup()}.
 534       * @param \moodle_url $pageurl the canonical URL of this page.
 535       * @return string HTML to output.
 536       */
 537      public function question_row(structure $structure, $slot, $contexts, $pagevars, $pageurl) {
 538          $output = '';
 539  
 540          $output .= $this->page_row($structure, $slot, $contexts, $pagevars, $pageurl);
 541  
 542          // Page split/join icon.
 543          $joinhtml = '';
 544          if ($structure->can_be_edited() && !$structure->is_last_slot_in_quiz($slot) &&
 545                                              !$structure->is_last_slot_in_section($slot)) {
 546              $joinhtml = $this->page_split_join_button($structure, $slot);
 547          }
 548          // Question HTML.
 549          $questionhtml = $this->question($structure, $slot, $pageurl);
 550          $qtype = $structure->get_question_type_for_slot($slot);
 551          $questionclasses = 'activity ' . $qtype . ' qtype_' . $qtype . ' slot';
 552  
 553          $output .= html_writer::tag('li', $questionhtml . $joinhtml,
 554                  ['class' => $questionclasses, 'id' => 'slot-' . $structure->get_slot_id_for_slot($slot),
 555                          'data-canfinish' => $structure->can_finish_during_the_attempt($slot)]);
 556  
 557          return $output;
 558      }
 559  
 560      /**
 561       * Displays one question with the surrounding controls.
 562       *
 563       * @param structure $structure object containing the structure of the quiz.
 564       * @param int $slot the first slot on the page we are outputting.
 565       * @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
 566       * @param array $pagevars the variables from {@link \question_edit_setup()}.
 567       * @param \moodle_url $pageurl the canonical URL of this page.
 568       * @return string HTML to output.
 569       */
 570      public function page_row(structure $structure, $slot, $contexts, $pagevars, $pageurl) {
 571          $output = '';
 572  
 573          $pagenumber = $structure->get_page_number_for_slot($slot);
 574  
 575          // Put page in a heading for accessibility and styling.
 576          $page = $this->heading(get_string('page') . ' ' . $pagenumber, 4);
 577  
 578          if ($structure->is_first_slot_on_page($slot)) {
 579              // Add the add-menu at the page level.
 580              $addmenu = html_writer::tag('span', $this->add_menu_actions($structure,
 581                      $pagenumber, $pageurl, $contexts, $pagevars),
 582                      ['class' => 'add-menu-outer']);
 583  
 584              $addquestionform = $this->add_question_form($structure,
 585                      $pagenumber, $pageurl, $pagevars);
 586  
 587              $output .= html_writer::tag('li', $page . $addmenu . $addquestionform,
 588                      ['class' => 'pagenumber activity yui3-dd-drop page', 'id' => 'page-' . $pagenumber]);
 589          }
 590  
 591          return $output;
 592      }
 593  
 594      /**
 595       * Returns the add menu that is output once per page.
 596       * @param structure $structure object containing the structure of the quiz.
 597       * @param int $page the page number that this menu will add to.
 598       * @param \moodle_url $pageurl the canonical URL of this page.
 599       * @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
 600       * @param array $pagevars the variables from {@link \question_edit_setup()}.
 601       * @return string HTML to output.
 602       */
 603      public function add_menu_actions(structure $structure, $page, \moodle_url $pageurl,
 604              \core_question\local\bank\question_edit_contexts $contexts, array $pagevars) {
 605  
 606          $actions = $this->edit_menu_actions($structure, $page, $pageurl, $pagevars);
 607          if (empty($actions)) {
 608              return '';
 609          }
 610          $menu = new \action_menu();
 611          $menu->set_constraint('.mod-quiz-edit-content');
 612          $trigger = html_writer::tag('span', get_string('add', 'quiz'), ['class' => 'add-menu']);
 613          $menu->set_menu_trigger($trigger);
 614          // The menu appears within an absolutely positioned element causing width problems.
 615          // Make sure no-wrap is set so that we don't get a squashed menu.
 616          $menu->set_nowrap_on_items(true);
 617  
 618          // Disable the link if quiz has attempts.
 619          if (!$structure->can_be_edited()) {
 620              return $this->render($menu);
 621          }
 622  
 623          foreach ($actions as $action) {
 624              if ($action instanceof \action_menu_link) {
 625                  $action->add_class('add-menu');
 626              }
 627              $menu->add($action);
 628          }
 629          $menu->attributes['class'] .= ' page-add-actions commands';
 630  
 631          // Prioritise the menu ahead of all other actions.
 632          $menu->prioritise = true;
 633  
 634          return $this->render($menu);
 635      }
 636  
 637      /**
 638       * Returns the list of actions to go in the add menu.
 639       * @param structure $structure object containing the structure of the quiz.
 640       * @param int $page the page number that this menu will add to.
 641       * @param \moodle_url $pageurl the canonical URL of this page.
 642       * @param array $pagevars the variables from {@link \question_edit_setup()}.
 643       * @return array the actions.
 644       */
 645      public function edit_menu_actions(structure $structure, $page,
 646              \moodle_url $pageurl, array $pagevars) {
 647          $questioncategoryid = question_get_category_id_from_pagevars($pagevars);
 648          static $str;
 649          if (!isset($str)) {
 650              $str = get_strings(['addasection', 'addaquestion', 'addarandomquestion',
 651                      'addarandomselectedquestion', 'questionbank'], 'quiz');
 652          }
 653  
 654          // Get section, page, slotnumber and maxmark.
 655          $actions = [];
 656  
 657          // Add a new question to the quiz.
 658          $returnurl = new \moodle_url($pageurl, ['addonpage' => $page]);
 659          $params = ['returnurl' => $returnurl->out_as_local_url(false),
 660                  'cmid' => $structure->get_cmid(), 'category' => $questioncategoryid,
 661                  'addonpage' => $page, 'appendqnumstring' => 'addquestion'];
 662  
 663          $actions['addaquestion'] = new \action_menu_link_secondary(
 664              new \moodle_url('/question/bank/editquestion/addquestion.php', $params),
 665              new \pix_icon('t/add', $str->addaquestion, 'moodle', ['class' => 'iconsmall', 'title' => '']),
 666              $str->addaquestion, ['class' => 'cm-edit-action addquestion', 'data-action' => 'addquestion']
 667          );
 668  
 669          // Call question bank.
 670          $icon = new \pix_icon('t/add', $str->questionbank, 'moodle', ['class' => 'iconsmall', 'title' => '']);
 671          if ($page) {
 672              $title = get_string('addquestionfrombanktopage', 'quiz', $page);
 673          } else {
 674              $title = get_string('addquestionfrombankatend', 'quiz');
 675          }
 676          $attributes = ['class' => 'cm-edit-action questionbank',
 677                  'data-header' => $title, 'data-action' => 'questionbank', 'data-addonpage' => $page];
 678          $actions['questionbank'] = new \action_menu_link_secondary($pageurl, $icon, $str->questionbank, $attributes);
 679  
 680          // Add a random question.
 681          if ($structure->can_add_random_questions()) {
 682              $returnurl = new \moodle_url('/mod/quiz/edit.php', ['cmid' => $structure->get_cmid(), 'data-addonpage' => $page]);
 683              $params = ['returnurl' => $returnurl, 'cmid' => $structure->get_cmid(), 'appendqnumstring' => 'addarandomquestion'];
 684              $url = new \moodle_url('/mod/quiz/addrandom.php', $params);
 685              $icon = new \pix_icon('t/add', $str->addarandomquestion, 'moodle', ['class' => 'iconsmall', 'title' => '']);
 686              $attributes = ['class' => 'cm-edit-action addarandomquestion', 'data-action' => 'addarandomquestion'];
 687              if ($page) {
 688                  $title = get_string('addrandomquestiontopage', 'quiz', $page);
 689              } else {
 690                  $title = get_string('addrandomquestionatend', 'quiz');
 691              }
 692              $attributes = array_merge(['data-header' => $title, 'data-addonpage' => $page], $attributes);
 693              $actions['addarandomquestion'] = new \action_menu_link_secondary($url, $icon, $str->addarandomquestion, $attributes);
 694          }
 695  
 696          // Add a new section to the add_menu if possible. This is always added to the HTML
 697          // then hidden with CSS when no needed, so that as things are re-ordered, etc. with
 698          // Ajax it can be relevaled again when necessary.
 699          $params = ['cmid' => $structure->get_cmid(), 'addsectionatpage' => $page];
 700  
 701          $actions['addasection'] = new \action_menu_link_secondary(
 702              new \moodle_url($pageurl, $params),
 703              new \pix_icon('t/add', $str->addasection, 'moodle', ['class' => 'iconsmall', 'title' => '']),
 704              $str->addasection, ['class' => 'cm-edit-action addasection', 'data-action' => 'addasection']
 705          );
 706  
 707          return $actions;
 708      }
 709  
 710      /**
 711       * Render the form that contains the data for adding a new question to the quiz.
 712       *
 713       * @param structure $structure object containing the structure of the quiz.
 714       * @param int $page the page number that this menu will add to.
 715       * @param \moodle_url $pageurl the canonical URL of this page.
 716       * @param array $pagevars the variables from {@link \question_edit_setup()}.
 717       * @return string HTML to output.
 718       */
 719      protected function add_question_form(structure $structure, $page, \moodle_url $pageurl, array $pagevars) {
 720  
 721          $questioncategoryid = question_get_category_id_from_pagevars($pagevars);
 722  
 723          $output = html_writer::tag('input', null,
 724                  ['type' => 'hidden', 'name' => 'returnurl',
 725                          'value' => $pageurl->out_as_local_url(false, ['addonpage' => $page])]);
 726          $output .= html_writer::tag('input', null,
 727                  ['type' => 'hidden', 'name' => 'cmid', 'value' => $structure->get_cmid()]);
 728          $output .= html_writer::tag('input', null,
 729                  ['type' => 'hidden', 'name' => 'appendqnumstring', 'value' => 'addquestion']);
 730          $output .= html_writer::tag('input', null,
 731                  ['type' => 'hidden', 'name' => 'category', 'value' => $questioncategoryid]);
 732  
 733          return html_writer::tag('form', html_writer::div($output),
 734                  ['class' => 'addnewquestion', 'method' => 'post',
 735                          'action' => new \moodle_url('/question/bank/editquestion/addquestion.php')]);
 736      }
 737  
 738      /**
 739       * Display a question.
 740       *
 741       * @param structure $structure object containing the structure of the quiz.
 742       * @param int $slot the first slot on the page we are outputting.
 743       * @param \moodle_url $pageurl the canonical URL of this page.
 744       * @return string HTML to output.
 745       */
 746      public function question(structure $structure, int $slot, \moodle_url $pageurl) {
 747          // Get the data required by the question_slot template.
 748          $slotid = $structure->get_slot_id_for_slot($slot);
 749  
 750          $output = '';
 751          $output .= html_writer::start_tag('div');
 752  
 753          if ($structure->can_be_edited()) {
 754              $output .= $this->question_move_icon($structure, $slot);
 755          }
 756  
 757          if ($structure->can_display_number_be_customised($slot)) {
 758              $questionnumber = $this->output->render($structure->make_slot_display_number_in_place_editable(
 759                      $slotid, $structure->get_context()));
 760          } else {
 761              $questionnumber = $structure->get_displayed_number_for_slot($slot);
 762          }
 763  
 764          $data = [
 765              'slotid' => $slotid,
 766              'canbeedited' => $structure->can_be_edited(),
 767              'checkbox' => $this->get_checkbox_render($structure, $slot),
 768              'questionnumber' => $this->question_number($questionnumber, $structure->get_slot_by_number($slot)->defaultnumber),
 769              'questionname' => $this->get_question_name_for_slot($structure, $slot, $pageurl),
 770              'questionicons' => $this->get_action_icon($structure, $slot, $pageurl),
 771              'questiondependencyicon' => ($structure->can_be_edited() ? $this->question_dependency_icon($structure, $slot) : ''),
 772              'versionselection' => false,
 773              'draftversion' => $structure->get_question_in_slot($slot)->status == question_version_status::QUESTION_STATUS_DRAFT,
 774          ];
 775  
 776          $data['versionoptions'] = [];
 777          if ($structure->get_slot_by_number($slot)->qtype !== 'random') {
 778              $data['versionselection'] = true;
 779              $data['versionoption'] = $structure->get_version_choices_for_slot($slot);
 780              $this->page->requires->js_call_amd('mod_quiz/question_slot', 'init', [$slotid]);
 781          }
 782  
 783          // Render the question slot template.
 784          $output .= $this->render_from_template('mod_quiz/question_slot', $data);
 785  
 786          $output .= html_writer::end_tag('div');
 787  
 788          return $output;
 789      }
 790  
 791      /**
 792       * Get the checkbox render.
 793       *
 794       * @param structure $structure object containing the structure of the quiz.
 795       * @param int $slot the slot on the page we are outputting.
 796       * @return string HTML to output.
 797       */
 798      public function get_checkbox_render(structure $structure, int $slot) : string {
 799          $questionslot = $structure->get_displayed_number_for_slot($slot);
 800          $checkbox = new \core\output\checkbox_toggleall($this->togglegroup, false,
 801              [
 802                  'id' => 'selectquestion-' . $slot,
 803                  'name' => 'selectquestion[]',
 804                  'classes' => 'select-multiple-checkbox',
 805                  'label' => get_string('selectquestionslot', 'quiz', $questionslot),
 806                  'labelclasses' => 'sr-only',
 807              ]);
 808  
 809          return $this->render($checkbox);
 810      }
 811  
 812      /**
 813       * Get the question name for the slot.
 814       *
 815       * @param structure $structure object containing the structure of the quiz.
 816       * @param int $slot the slot on the page we are outputting.
 817       * @param \moodle_url $pageurl the canonical URL of this page.
 818       * @return string HTML to output.
 819       */
 820      public function get_question_name_for_slot(structure $structure, int $slot, \moodle_url $pageurl) : string {
 821          // Display the link to the question (or do nothing if question has no url).
 822          if ($structure->get_question_type_for_slot($slot) === 'random') {
 823              $questionname = $this->random_question($structure, $slot, $pageurl);
 824          } else {
 825              $questionname = $this->question_name($structure, $slot, $pageurl);
 826          }
 827  
 828          return $questionname;
 829      }
 830  
 831      /**
 832       * Get the action icons render.
 833       *
 834       * @param structure $structure object containing the structure of the quiz.
 835       * @param int $slot the slot on the page we are outputting.
 836       * @param \moodle_url $pageurl the canonical URL of this page.
 837       * @return string HTML to output.
 838       */
 839      public function get_action_icon(structure $structure, int $slot, \moodle_url $pageurl) : string {
 840          // Action icons.
 841          $qtype = $structure->get_question_type_for_slot($slot);
 842          $slotinfo = $structure->get_slot_by_number($slot);
 843          $questionicons = '';
 844          if ($qtype !== 'random') {
 845              $questionicons .= $this->question_preview_icon($structure->get_quiz(),
 846                      $structure->get_question_in_slot($slot),
 847                      null, null, $slotinfo->requestedversion ?: question_preview_options::ALWAYS_LATEST);
 848          }
 849          if ($structure->can_be_edited() && $structure->has_use_capability($slot)) {
 850              $questionicons .= $this->question_remove_icon($structure, $slot, $pageurl);
 851          }
 852          $questionicons .= $this->marked_out_of_field($structure, $slot);
 853  
 854          return $questionicons;
 855      }
 856  
 857      /**
 858       * Render the move icon.
 859       *
 860       * @param structure $structure object containing the structure of the quiz.
 861       * @param int $slot the first slot on the page we are outputting.
 862       * @return string The markup for the move action.
 863       */
 864      public function question_move_icon(structure $structure, $slot) {
 865          return html_writer::link(new \moodle_url('#'),
 866              $this->pix_icon('i/dragdrop', get_string('move'), 'moodle', ['class' => 'iconsmall', 'title' => '']),
 867              ['class' => 'editing_move', 'data-action' => 'move']
 868          );
 869      }
 870  
 871      /**
 872       * Output the question number.
 873       *
 874       * @param string $editablenumber The, which may be an in-place editable.
 875       * @param string $uncustomisednumber The un-customised number number, or 'i'.
 876       * @return string HTML to output.
 877       */
 878      public function question_number(string $editablenumber, string $uncustomisednumber) {
 879          if ($editablenumber !== get_string('infoshort', 'quiz')) {
 880              $editablenumber = html_writer::span(get_string('question'), 'accesshide') . ' ' . $editablenumber;
 881              $uncustomisednumber = html_writer::span(get_string('question'), 'accesshide') . ' ' . $uncustomisednumber;
 882          }
 883          return html_writer::tag('span', $editablenumber, ['class' => 'slotnumber unshuffled']) .
 884                  html_writer::tag('span', $uncustomisednumber, ['class' => 'slotnumber shuffled']);
 885      }
 886  
 887      /**
 888       * Render the preview icon.
 889       *
 890       * @param \stdClass $quiz the quiz settings from the database.
 891       * @param \stdClass $questiondata which question to preview.
 892       *      If ->questionid is set, that is used instead of ->id.
 893       * @param bool $label if true, show the preview question label after the icon
 894       * @param int $variant which question variant to preview (optional).
 895       * @param int $restartversion version to use when restarting the preview
 896       * @return string HTML to output.
 897       */
 898      public function question_preview_icon($quiz, $questiondata, $label = null, $variant = null, $restartversion = null) {
 899          $question = clone($questiondata);
 900          if (isset($question->questionid)) {
 901  
 902              $question->id = $question->questionid;
 903          }
 904  
 905          $url = quiz_question_preview_url($quiz, $question, $variant, $restartversion);
 906  
 907          // Do we want a label?
 908          $strpreviewlabel = '';
 909          if ($label) {
 910              $strpreviewlabel = ' ' . get_string('preview', 'quiz');
 911          }
 912  
 913          // Build the icon.
 914          $strpreviewquestion = get_string('previewquestion', 'quiz');
 915          $image = $this->pix_icon('t/preview', $strpreviewquestion);
 916  
 917          $action = new \popup_action('click', $url, 'questionpreview',
 918                  \qbank_previewquestion\helper::question_preview_popup_params());
 919  
 920          return $this->action_link($url, $image . $strpreviewlabel, $action,
 921                  ['title' => $strpreviewquestion, 'class' => 'preview']);
 922      }
 923  
 924      /**
 925       * Render an icon to remove a question from the quiz.
 926       *
 927       * @param structure $structure object containing the structure of the quiz.
 928       * @param int $slot the first slot on the page we are outputting.
 929       * @param \moodle_url $pageurl the canonical URL of the edit page.
 930       * @return string HTML to output.
 931       */
 932      public function question_remove_icon(structure $structure, $slot, $pageurl) {
 933          $url = new \moodle_url($pageurl, ['sesskey' => sesskey(), 'remove' => $slot]);
 934          $strdelete = get_string('delete');
 935  
 936          $image = $this->pix_icon('t/delete', $strdelete);
 937  
 938          return $this->action_link($url, $image, null, ['title' => $strdelete,
 939                      'class' => 'cm-edit-action editing_delete', 'data-action' => 'delete']);
 940      }
 941  
 942      /**
 943       * Display an icon to split or join two pages of the quiz.
 944       *
 945       * @param structure $structure object containing the structure of the quiz.
 946       * @param int $slot the first slot on the page we are outputting.
 947       * @return string HTML to output.
 948       */
 949      public function page_split_join_button($structure, $slot) {
 950          $insertpagebreak = !$structure->is_last_slot_on_page($slot);
 951          $url = new \moodle_url('repaginate.php', ['quizid' => $structure->get_quizid(),
 952                  'slot' => $slot, 'repag' => $insertpagebreak ? 2 : 1, 'sesskey' => sesskey()]);
 953  
 954          if ($insertpagebreak) {
 955              $title = get_string('addpagebreak', 'quiz');
 956              $image = $this->image_icon('e/insert_page_break', $title);
 957              $action = 'addpagebreak';
 958          } else {
 959              $title = get_string('removepagebreak', 'quiz');
 960              $image = $this->image_icon('e/remove_page_break', $title);
 961              $action = 'removepagebreak';
 962          }
 963  
 964          // Disable the link if quiz has attempts.
 965          $disabled = null;
 966          if (!$structure->can_be_edited()) {
 967              $disabled = 'disabled';
 968          }
 969          return html_writer::span($this->action_link($url, $image, null, ['title' => $title,
 970                      'class' => 'page_split_join cm-edit-action', 'disabled' => $disabled, 'data-action' => $action]),
 971                  'page_split_join_wrapper');
 972      }
 973  
 974      /**
 975       * Display the icon for whether this question can only be seen if the previous
 976       * one has been answered.
 977       *
 978       * @param structure $structure object containing the structure of the quiz.
 979       * @param int $slot the first slot on the page we are outputting.
 980       * @return string HTML to output.
 981       */
 982      public function question_dependency_icon($structure, $slot) {
 983          $a = [
 984              'thisq' => $structure->get_displayed_number_for_slot($slot),
 985              'previousq' => $structure->get_displayed_number_for_slot(max($slot - 1, 1)),
 986          ];
 987          if ($structure->is_question_dependent_on_previous_slot($slot)) {
 988              $title = get_string('questiondependencyremove', 'quiz', $a);
 989              $image = $this->pix_icon('t/locked', get_string('questiondependsonprevious', 'quiz'),
 990                      'moodle', ['title' => '']);
 991              $action = 'removedependency';
 992          } else {
 993              $title = get_string('questiondependencyadd', 'quiz', $a);
 994              $image = $this->pix_icon('t/unlocked', get_string('questiondependencyfree', 'quiz'),
 995                      'moodle', ['title' => '']);
 996              $action = 'adddependency';
 997          }
 998  
 999          // Disable the link if quiz has attempts.
1000          $disabled = null;
1001          if (!$structure->can_be_edited()) {
1002              $disabled = 'disabled';
1003          }
1004          $extraclass = '';
1005          if (!$structure->can_question_depend_on_previous_slot($slot)) {
1006              $extraclass = ' question_dependency_cannot_depend';
1007          }
1008          return html_writer::span($this->action_link('#', $image, null, ['title' => $title,
1009                  'class' => 'cm-edit-action', 'disabled' => $disabled, 'data-action' => $action]),
1010                  'question_dependency_wrapper' . $extraclass);
1011      }
1012  
1013      /**
1014       * Renders html to display a name with the link to the question on a quiz edit page
1015       *
1016       * If the user does not have permission to edi the question, it is rendered
1017       * without a link
1018       *
1019       * @param structure $structure object containing the structure of the quiz.
1020       * @param int $slot which slot we are outputting.
1021       * @param \moodle_url $pageurl the canonical URL of this page.
1022       * @return string HTML to output.
1023       */
1024      public function question_name(structure $structure, $slot, $pageurl) {
1025          $output = '';
1026  
1027          $question = $structure->get_question_in_slot($slot);
1028          $editurl = new \moodle_url('/question/bank/editquestion/question.php', [
1029                  'returnurl' => $pageurl->out_as_local_url(),
1030                  'cmid' => $structure->get_cmid(), 'id' => $question->questionid]);
1031  
1032          $instancename = quiz_question_tostring($question);
1033  
1034          $qtype = \question_bank::get_qtype($question->qtype, false);
1035          $namestr = $qtype->local_name();
1036  
1037          $icon = $this->pix_icon('icon', $namestr, $qtype->plugin_name(), ['title' => $namestr,
1038                  'class' => 'activityicon', 'alt' => ' ', 'role' => 'presentation']);
1039  
1040          $editicon = $this->pix_icon('t/edit', '', 'moodle', ['title' => '']);
1041  
1042          // Need plain question name without html tags for link title.
1043          $title = shorten_text(format_string($question->name), 100);
1044  
1045          // Display the link itself.
1046          $activitylink = $icon . html_writer::tag('span', $editicon . $instancename, ['class' => 'instancename']);
1047          $output .= html_writer::link($editurl, $activitylink,
1048                  ['title' => get_string('editquestion', 'quiz').' '.$title]);
1049  
1050          return $output;
1051      }
1052  
1053      /**
1054       * Renders html to display a random question the link to edit the configuration
1055       * and also to see that category in the question bank.
1056       *
1057       * @param structure $structure object containing the structure of the quiz.
1058       * @param int $slotnumber which slot we are outputting.
1059       * @param \moodle_url $pageurl the canonical URL of this page.
1060       * @return string HTML to output.
1061       */
1062      public function random_question(structure $structure, $slotnumber, $pageurl) {
1063          $question = $structure->get_question_in_slot($slotnumber);
1064          $slot = $structure->get_slot_by_number($slotnumber);
1065          $editurl = new \moodle_url('/mod/quiz/editrandom.php',
1066                  ['returnurl' => $pageurl->out_as_local_url(), 'slotid' => $slot->id]);
1067  
1068          $temp = clone($question);
1069          $temp->questiontext = '';
1070          $temp->name = qbank_helper::describe_random_question($slot);
1071          $instancename = quiz_question_tostring($temp);
1072  
1073          $configuretitle = get_string('configurerandomquestion', 'quiz');
1074          $qtype = \question_bank::get_qtype($question->qtype, false);
1075          $namestr = $qtype->local_name();
1076          $icon = $this->pix_icon('icon', $namestr, $qtype->plugin_name(), ['title' => $namestr,
1077                  'class' => 'icon activityicon', 'alt' => ' ', 'role' => 'presentation']);
1078  
1079          $editicon = $this->pix_icon('t/edit', $configuretitle, 'moodle', ['title' => '']);
1080          $qbankurlparams = [
1081              'cmid' => $structure->get_cmid(),
1082              'cat' => $slot->category . ',' . $slot->contextid,
1083              'recurse' => $slot->randomrecurse,
1084          ];
1085  
1086          $slottags = [];
1087          if (isset($slot->randomtags)) {
1088              $slottags = $slot->randomtags;
1089          }
1090          foreach ($slottags as $index => $slottag) {
1091              $slottag = explode(',', $slottag);
1092              $qbankurlparams["qtagids[{$index}]"] = $slottag[0];
1093          }
1094  
1095          // If this is a random question, display a link to show the questions
1096          // selected from in the question bank.
1097          $qbankurl = new \moodle_url('/question/edit.php', $qbankurlparams);
1098          $qbanklink = ' ' . \html_writer::link($qbankurl,
1099                          get_string('seequestions', 'quiz'), ['class' => 'mod_quiz_random_qbank_link']);
1100  
1101          return html_writer::link($editurl, $icon . $editicon, ['title' => $configuretitle]) .
1102                  ' ' . $instancename . ' ' . $qbanklink;
1103      }
1104  
1105      /**
1106       * Display the 'marked out of' information for a question.
1107       * Along with the regrade action.
1108       * @param structure $structure object containing the structure of the quiz.
1109       * @param int $slot which slot we are outputting.
1110       * @return string HTML to output.
1111       */
1112      public function marked_out_of_field(structure $structure, $slot) {
1113          if (!$structure->is_real_question($slot)) {
1114              $output = html_writer::span('',
1115                      'instancemaxmark decimalplaces_' . $structure->get_decimal_places_for_question_marks());
1116  
1117              $output .= html_writer::span(
1118                      $this->pix_icon('spacer', '', 'moodle', ['class' => 'editicon visibleifjs', 'title' => '']),
1119                      'editing_maxmark');
1120              return html_writer::span($output, 'instancemaxmarkcontainer infoitem');
1121          }
1122  
1123          $output = html_writer::span($structure->formatted_question_grade($slot),
1124                  'instancemaxmark decimalplaces_' . $structure->get_decimal_places_for_question_marks(),
1125                  ['title' => get_string('maxmark', 'quiz')]);
1126  
1127          $output .= html_writer::span(
1128              html_writer::link(
1129                  new \moodle_url('#'),
1130                  $this->pix_icon('t/editstring', '', 'moodle', ['class' => 'editicon visibleifjs', 'title' => '']),
1131                  [
1132                      'class' => 'editing_maxmark',
1133                      'data-action' => 'editmaxmark',
1134                      'title' => get_string('editmaxmark', 'quiz'),
1135                  ]
1136              )
1137          );
1138          return html_writer::span($output, 'instancemaxmarkcontainer');
1139      }
1140  
1141      /**
1142       * Renders the question chooser.
1143       *
1144       * @param renderable
1145       * @return string
1146       */
1147      public function render_question_chooser(renderable $chooser) {
1148          return $this->render_from_template('mod_quiz/question_chooser', $chooser->export_for_template($this));
1149      }
1150  
1151      /**
1152       * Render the question type chooser dialogue.
1153       * @return string HTML to output.
1154       */
1155      public function question_chooser() {
1156          $chooser = \mod_quiz\output\question_chooser::get($this->page->course, [], null);
1157          $container = html_writer::div($this->render($chooser), '', ['id' => 'qtypechoicecontainer']);
1158          return html_writer::div($container, 'createnewquestion');
1159      }
1160  
1161      /**
1162       * Render the contents of the question bank pop-up in its initial state,
1163       * when it just contains a loading progress indicator.
1164       * @return string HTML to output.
1165       */
1166      public function question_bank_loading() {
1167          return html_writer::div($this->pix_icon('i/loading', get_string('loading')), 'questionbankloading');
1168      }
1169  
1170      /**
1171       * Initialise the JavaScript for the general editing. (JavaScript for popups
1172       * is handled with the specific code for those.)
1173       *
1174       * @param structure $structure object containing the structure of the quiz.
1175       * @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
1176       * @param array $pagevars the variables from {@link \question_edit_setup()}.
1177       * @param \moodle_url $pageurl the canonical URL of this page.
1178       * @return bool Always returns true
1179       */
1180      protected function initialise_editing_javascript(structure $structure,
1181              \core_question\local\bank\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
1182  
1183          $config = new \stdClass();
1184          $config->resourceurl = '/mod/quiz/edit_rest.php';
1185          $config->sectionurl = '/mod/quiz/edit_rest.php';
1186          $config->pageparams = [];
1187          $config->questiondecimalpoints = $structure->get_decimal_places_for_question_marks();
1188          $config->pagehtml = $this->new_page_template($structure, $contexts, $pagevars, $pageurl);
1189          $config->addpageiconhtml = $this->add_page_icon_template($structure);
1190  
1191          $this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
1192                  'M.mod_quiz.init_resource_toolbox',
1193                  [[
1194                          'courseid' => $structure->get_courseid(),
1195                          'quizid' => $structure->get_quizid(),
1196                          'ajaxurl' => $config->resourceurl,
1197                          'config' => $config,
1198                  ]]
1199          );
1200          unset($config->pagehtml);
1201          unset($config->addpageiconhtml);
1202  
1203          $this->page->requires->strings_for_js(['areyousureremoveselected'], 'quiz');
1204          $this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
1205                  'M.mod_quiz.init_section_toolbox',
1206                  [[
1207                          'courseid' => $structure,
1208                          'quizid' => $structure->get_quizid(),
1209                          'ajaxurl' => $config->sectionurl,
1210                          'config' => $config,
1211                  ]]
1212          );
1213  
1214          $this->page->requires->yui_module('moodle-mod_quiz-dragdrop', 'M.mod_quiz.init_section_dragdrop',
1215                  [[
1216                          'courseid' => $structure,
1217                          'quizid' => $structure->get_quizid(),
1218                          'ajaxurl' => $config->sectionurl,
1219                          'config' => $config,
1220                  ]], null, true);
1221  
1222          $this->page->requires->yui_module('moodle-mod_quiz-dragdrop', 'M.mod_quiz.init_resource_dragdrop',
1223                  [[
1224                          'courseid' => $structure,
1225                          'quizid' => $structure->get_quizid(),
1226                          'ajaxurl' => $config->resourceurl,
1227                          'config' => $config,
1228                  ]], null, true);
1229  
1230          // Require various strings for the command toolbox.
1231          $this->page->requires->strings_for_js([
1232                  'clicktohideshow',
1233                  'deletechecktype',
1234                  'deletechecktypename',
1235                  'edittitle',
1236                  'edittitleinstructions',
1237                  'emptydragdropregion',
1238                  'hide',
1239                  'markedthistopic',
1240                  'markthistopic',
1241                  'move',
1242                  'movecontent',
1243                  'moveleft',
1244                  'movesection',
1245                  'page',
1246                  'question',
1247                  'selectall',
1248                  'show',
1249                  'tocontent',
1250          ], 'moodle');
1251  
1252          $this->page->requires->strings_for_js([
1253                  'addpagebreak',
1254                  'cannotremoveallsectionslots',
1255                  'cannotremoveslots',
1256                  'confirmremovesectionheading',
1257                  'confirmremovequestion',
1258                  'dragtoafter',
1259                  'dragtostart',
1260                  'numquestionsx',
1261                  'sectionheadingedit',
1262                  'sectionheadingremove',
1263                  'sectionnoname',
1264                  'removepagebreak',
1265                  'questiondependencyadd',
1266                  'questiondependencyfree',
1267                  'questiondependencyremove',
1268                  'questiondependsonprevious',
1269          ], 'quiz');
1270  
1271          foreach (\question_bank::get_all_qtypes() as $qtype => $notused) {
1272              $this->page->requires->string_for_js('pluginname', 'qtype_' . $qtype);
1273          }
1274  
1275          return true;
1276      }
1277  
1278      /**
1279       * HTML for a page, with ids stripped, so it can be used as a javascript template.
1280       *
1281       * @param structure $structure object containing the structure of the quiz.
1282       * @param \core_question\local\bank\question_edit_contexts $contexts the relevant question bank contexts.
1283       * @param array $pagevars the variables from {@link \question_edit_setup()}.
1284       * @param \moodle_url $pageurl the canonical URL of this page.
1285       * @return string HTML for a new page.
1286       */
1287      protected function new_page_template(structure $structure,
1288              \core_question\local\bank\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
1289          if (!$structure->has_questions()) {
1290              return '';
1291          }
1292  
1293          $pagehtml = $this->page_row($structure, 1, $contexts, $pagevars, $pageurl);
1294  
1295          // Normalise the page number.
1296          $pagenumber = $structure->get_page_number_for_slot(1);
1297          $strcontexts = [];
1298          $strcontexts[] = 'page-';
1299          $strcontexts[] = get_string('page') . ' ';
1300          $strcontexts[] = 'addonpage%3D';
1301          $strcontexts[] = 'addonpage=';
1302          $strcontexts[] = 'addonpage="';
1303          $strcontexts[] = get_string('addquestionfrombanktopage', 'quiz', '');
1304          $strcontexts[] = 'data-addonpage%3D';
1305          $strcontexts[] = 'action-menu-';
1306  
1307          foreach ($strcontexts as $strcontext) {
1308              $pagehtml = str_replace($strcontext . $pagenumber, $strcontext . '%%PAGENUMBER%%', $pagehtml);
1309          }
1310  
1311          return $pagehtml;
1312      }
1313  
1314      /**
1315       * HTML for a page, with ids stripped, so it can be used as a javascript template.
1316       *
1317       * @param structure $structure object containing the structure of the quiz.
1318       * @return string HTML for a new icon
1319       */
1320      protected function add_page_icon_template(structure $structure) {
1321  
1322          if (!$structure->has_questions()) {
1323              return '';
1324          }
1325  
1326          $html = $this->page_split_join_button($structure, 1);
1327          return str_replace('&amp;slot=1&amp;', '&amp;slot=%%SLOT%%&amp;', $html);
1328      }
1329  
1330      /**
1331       * Return the contents of the question bank, to be displayed in the question-bank pop-up.
1332       *
1333       * @param \mod_quiz\question\bank\custom_view $questionbank the question bank view object.
1334       * @param array $pagevars the variables from {@link \question_edit_setup()}.
1335       * @return string HTML to output / send back in response to an AJAX request.
1336       */
1337      public function question_bank_contents(\mod_quiz\question\bank\custom_view $questionbank, array $pagevars) {
1338  
1339          $qbank = $questionbank->render($pagevars, 'editq');
1340          return html_writer::div(html_writer::div($qbank, 'bd'), 'questionbankformforpopup');
1341      }
1342  }