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
25namespace mod_quiz\output;
26defined('MOODLE_INTERNAL') || die();
27
28use \mod_quiz\structure;
29use \html_writer;
30use 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 */
39class 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',
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}
1260