Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

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