Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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