Proyectos de Subversion Moodle

Rev

Rev 710 | Ir a la última revisión | | 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
defined('MOODLE_INTERNAL') || die();
18
 
19
 
20
// Quiz.
21
require_once($CFG->dirroot . "/mod/quiz/renderer.php");
22
class theme_universe_mod_quiz_renderer extends mod_quiz\output\renderer {
23
    /**
24
     * Outputs a box.
25
     *
26
     * @param string $contents The contents of the box
27
     * @param string $classes A space-separated list of CSS classes
28
     * @param string $id An optional ID
29
     * @param array $attributes An array of other attributes to give the box.
30
     * @return string the HTML to output.
31
     */
32
    public function space_box($contents, $classes = 'generalbox', $id = null, $attributes = array()) {
33
        return $this->space_box_start($classes, $id, $attributes) . $contents . $this->space_box_end();
34
    }
35
 
36
    /**
37
     * Outputs the opening section of a box.
38
     *
39
     * @param string $classes A space-separated list of CSS classes
40
     * @param string $id An optional ID
41
     * @param array $attributes An array of other attributes to give the box.
42
     * @return string the HTML to output.
43
     */
44
    public function space_box_start($classes = 'generalbox', $id = null, $attributes = array()) {
45
        $this->opencontainers->push('box', html_writer::end_tag('div'));
46
        $attributes['id'] = $id;
47
        $attributes['class'] = 'box ' . renderer_base::prepare_classes($classes);
48
        return html_writer::start_tag('div', $attributes);
49
    }
50
 
51
    /**
52
     * Outputs the closing section of a box.
53
     *
54
     * @return string the HTML to output.
55
     */
56
    public function space_box_end() {
57
        return $this->opencontainers->pop('box');
58
    }
59
 
60
    /**
61
     * Output the page information
62
     *
63
     * @param object $quiz the quiz settings.
64
     * @param object $cm the course_module object.
65
     * @param context $context the quiz context.
66
     * @param array $messages any access messages that should be described.
67
     * @param bool $quizhasquestions does quiz has questions added.
68
     * @return string HTML to output.
69
     */
70
    public function view_information($quiz, $cm, $context, $messages, bool $quizhasquestions = false) {
71
        $output = '';
72
 
73
        // Output any access messages.
74
        if ($messages) {
75
            $output .= $this->box($this->access_messages($messages), 'rui-quizinfo quizinfo mt-3');
76
        }
77
 
78
        // Show number of attempts summary to those who can view reports.
79
        if (has_capability('mod/quiz:viewreports', $context)) {
80
            if (
81
                $strattemptnum = $this->quiz_attempt_summary_link_to_reports(
82
                    $quiz,
83
                    $cm,
84
                    $context
85
                )
86
            ) {
87
                $output .= html_writer::tag(
88
                    'div',
89
                    $strattemptnum,
90
                    array('class' => 'rui-quizattemptcounts quizattemptcounts my-4')
91
                );
92
            }
93
        }
94
 
95
        if (has_any_capability(['mod/quiz:manageoverrides', 'mod/quiz:viewoverrides'], $context)) {
96
            if ($overrideinfo = $this->quiz_override_summary_links($quiz, $cm)) {
97
                $output .= html_writer::tag('div', $overrideinfo, ['class' => 'rui-quizattemptcounts quizattemptcounts my-4']);
98
            }
99
        }
100
 
101
        return $output;
102
    }
103
 
104
    /**
105
     * Generates the table of data
106
     *
107
     * @param array $quiz Array contining quiz data
108
     * @param int $context The page context ID
109
     * @param mod_quiz_view_object $viewobj
110
     */
111
    public function view_table($quiz, $context, $viewobj) {
112
        if (!$viewobj->attempts) {
113
            return '';
114
        }
115
 
116
        // Prepare table header.
117
        $table = new html_table();
118
        $table->attributes['class'] = 'generaltable rui-quizattemptsummary mt-2 mb-0';
119
        $table->head = array();
120
        $table->align = array();
121
        $table->size = array();
122
        if ($viewobj->attemptcolumn) {
123
            $table->head[] = get_string('attemptnumber', 'quiz');
124
            $table->size[] = '';
125
        }
126
        $table->head[] = get_string('attemptstate', 'quiz');
127
        $table->align[] = 'left';
128
        $table->size[] = '';
129
        if ($viewobj->markcolumn) {
130
            $table->head[] = get_string('marks', 'quiz') . ' / ' .
131
                quiz_format_grade($quiz, $quiz->sumgrades);
132
            $table->size[] = '';
133
        }
134
        if ($viewobj->gradecolumn) {
135
            $table->head[] = get_string('grade', 'quiz') . ' / ' .
136
                quiz_format_grade($quiz, $quiz->grade);
137
            $table->size[] = '';
138
        }
139
        if ($viewobj->canreviewmine) {
140
            $table->head[] = get_string('review', 'quiz');
141
            $table->size[] = '';
142
        }
143
        if ($viewobj->feedbackcolumn) {
144
            $table->head[] = get_string('feedback', 'quiz');
145
            $table->align[] = 'left';
146
            $table->size[] = '';
147
        }
148
 
149
        // One row for each attempt.
150
        foreach ($viewobj->attemptobjs as $attemptobj) {
151
            $attemptoptions = $attemptobj->get_display_options(true);
152
            $row = array();
153
 
154
            // Add the attempt number.
155
            if ($viewobj->attemptcolumn) {
156
                if ($attemptobj->is_preview()) {
157
                    $row[] = get_string('preview', 'quiz');
158
                } else {
159
                    $row[] = $attemptobj->get_attempt_number();
160
                }
161
            }
162
 
163
            $row[] = $this->attempt_state($attemptobj);
164
 
165
            if ($viewobj->markcolumn) {
166
                if (
167
                    $attemptoptions->marks >= question_display_options::MARK_AND_MAX &&
168
                    $attemptobj->is_finished()
169
                ) {
170
                    $row[] = quiz_format_grade($quiz, $attemptobj->get_sum_marks());
171
                } else {
172
                    $row[] = '';
173
                }
174
            }
175
 
176
            // Ouside the if because we may be showing feedback but not grades.
177
            $attemptgrade = quiz_rescale_grade($attemptobj->get_sum_marks(), $quiz, false);
178
 
179
            if ($viewobj->gradecolumn) {
180
                if (
181
                    $attemptoptions->marks >= question_display_options::MARK_AND_MAX &&
182
                    $attemptobj->is_finished()
183
                ) {
184
 
185
                    // Highlight the highest grade if appropriate.
186
                    if (
187
                        $viewobj->overallstats && !$attemptobj->is_preview()
188
                        && $viewobj->numattempts > 1 && !is_null($viewobj->mygrade)
189
                        && $attemptobj->get_state() == quiz_attempt::FINISHED
190
                        && $attemptgrade == $viewobj->mygrade
191
                        && $quiz->grademethod == QUIZ_GRADEHIGHEST
192
                    ) {
193
                        $table->rowclasses[$attemptobj->get_attempt_number()] = 'bestrow';
194
                    }
195
 
196
                    $row[] = quiz_format_grade($quiz, $attemptgrade);
197
                } else {
198
                    $row[] = '';
199
                }
200
            }
201
 
202
            if ($viewobj->canreviewmine) {
203
                $row[] = $viewobj->accessmanager->make_review_link(
204
                    $attemptobj->get_attempt(),
205
                    $attemptoptions,
206
                    $this
207
                );
208
            }
209
 
210
            if ($viewobj->feedbackcolumn && $attemptobj->is_finished()) {
211
                if ($attemptoptions->overallfeedback) {
212
                    $row[] = quiz_feedback_for_grade($attemptgrade, $quiz, $context);
213
                } else {
214
                    $row[] = '';
215
                }
216
            }
217
 
218
            if ($attemptobj->is_preview()) {
219
                $table->data['preview'] = $row;
220
            } else {
221
                $table->data[$attemptobj->get_attempt_number()] = $row;
222
            }
223
        } // End. of loop over attempts.
224
 
225
        $output = '';
226
        $output .= $this->view_table_heading();
227
        $output .= html_writer::start_tag('div', array('class' => 'table-overflow mb-4'));
228
        $output .= html_writer::table($table);
229
        $output .= html_writer::end_tag('div');
230
        return $output;
231
    }
232
 
233
    /*
234
     * View Page
235
     */
236
    /**
237
     * Generates the view page
238
     *
239
     * @param stdClass $course the course settings row from the database.
240
     * @param stdClass $quiz the quiz settings row from the database.
241
     * @param stdClass $cm the course_module settings row from the database.
242
     * @param context_module $context the quiz context.
243
     * @param mod_quiz_view_object $viewobj
244
     * @return string HTML to display
245
     */
246
    public function view_page($course, $quiz, $cm, $context, $viewobj) {
247
        $output = '';
248
 
249
        $output .= $this->view_page_tertiary_nav($viewobj);
250
        $output .= $this->view_information($quiz, $cm, $context, $viewobj->infomessages);
251
        $output .= $this->view_table($quiz, $context, $viewobj);
252
        $output .= $this->view_result_info($quiz, $context, $cm, $viewobj);
253
        $output .= $this->box($this->view_page_buttons($viewobj), 'rui-quizattempt');
254
        return $output;
255
    }
256
 
257
    /**
258
     * Outputs the table containing data from summary data array
259
     *
260
     * @param array $summarydata contains row data for table
261
     * @param int $page contains the current page number
262
     */
263
    public function review_summary_table($summarydata, $page) {
264
        $summarydata = $this->filter_review_summary_table($summarydata, $page);
265
        if (empty($summarydata)) {
266
            return '';
267
        }
268
 
269
        $output = '';
270
 
271
        $output .= html_writer::start_tag('div', array('class' => 'rui-summary-table'));
272
 
273
        $output .= html_writer::start_tag('div', array('class' => 'rui-info-container rui-quizreviewsummary'));
274
 
275
        foreach ($summarydata as $rowdata => $val) {
276
 
277
            $csstitle = $rowdata;
278
 
279
            if ($val['title'] instanceof renderable) {
280
                $title = $this->render($val['title']);
281
            } else {
282
                $title = $val['title'];
283
            }
284
 
285
            if ($val['content'] instanceof renderable) {
286
                $content = $this->render($val['content']);
287
            } else {
288
                $content = $val['content'];
289
            }
290
 
291
            if ($val['title'] instanceof renderable) {
292
                $output .= html_writer::tag(
293
                    'div',
294
                    html_writer::tag('h5', $title, array('class' => 'rui-infobox-title')) .
295
                        html_writer::tag('div', $content, array('class' => 'rui-infobox-content--small')),
296
                    array('class' => 'rui-infobox rui-infobox--avatar')
297
                );
298
            } else {
299
                $output .= html_writer::tag(
300
                    'div',
301
                    html_writer::tag('h5', $title, array('class' => 'rui-infobox-title')) .
302
                        html_writer::tag('div', $content, array('class' => 'rui-infobox-content--small')),
303
                    array('class' => 'rui-infobox rui-infobox--' . strtolower(str_replace(' ', '', $csstitle)))
304
                );
305
            }
306
        }
307
 
308
        $output .= html_writer::end_tag('div');
309
        $output .= html_writer::end_tag('div');
310
 
311
        return $output;
312
    }
313
 
314
    /**
315
     * Generates the table of summarydata
316
     *
317
     * @param quiz_attempt $attemptobj
318
     * @param mod_quiz_display_options $displayoptions
319
     */
320
    public function summary_table($attemptobj, $displayoptions) {
321
        // Prepare the summary table header.
322
        $table = new html_table();
323
        $table->attributes['class'] = 'generaltable quizsummaryofattempt';
324
        $table->head = array(get_string('question', 'quiz'), get_string('status', 'quiz'));
325
        $table->align = array('left', 'left');
326
        $table->size = array('', '');
327
        $markscolumn = $displayoptions->marks >= question_display_options::MARK_AND_MAX;
328
        if ($markscolumn) {
329
            $table->head[] = get_string('marks', 'quiz');
330
            $table->align[] = 'left';
331
            $table->size[] = '';
332
        }
333
        $tablewidth = count($table->align);
334
        $table->data = array();
335
 
336
        // Get the summary info for each question.
337
        $slots = $attemptobj->get_slots();
338
        foreach ($slots as $slot) {
339
            // Add a section headings if we need one here.
340
            $heading = $attemptobj->get_heading_before_slot($slot);
341
 
342
            if ($heading !== null) {
343
                // There is a heading here.
344
                $rowclasses = 'quizsummaryheading';
345
                if ($heading) {
346
                    $heading = format_string($heading);
347
                } else if (count($attemptobj->get_quizobj()->get_sections()) > 1) {
348
                    // If this is the start of an unnamed section, and the quiz has more
349
                    // than one section, then add a default heading.
350
                    $heading = get_string('sectionnoname', 'quiz');
351
                    $rowclasses .= ' dimmed_text';
352
                }
353
                $cell = new html_table_cell(format_string($heading));
354
                $cell->header = true;
355
                $cell->colspan = $tablewidth;
356
                $table->data[] = array($cell);
357
                $table->rowclasses[] = $rowclasses;
358
            }
359
 
360
            // Don't display information items.
361
            if (!$attemptobj->is_real_question($slot)) {
362
                continue;
363
            }
364
 
365
            $flag = '';
366
 
367
            // Real question, show it.
368
            if ($attemptobj->is_question_flagged($slot)) {
369
                // Quiz has custom JS manipulating these image tags - so we can't use the pix_icon method here.
370
                $flag = '<svg class="ml-2"
371
                    width="20"
372
                    height="20"
373
                    viewBox="0 0 24 24"
374
                    fill="none"
375
                    xmlns="http://www.w3.org/2000/svg"
376
                >
377
                <path d="M4.75 5.75V19.25"
378
                    stroke="currentColor"
379
                    stroke-width="2"
380
                    stroke-linecap="round"
381
                    stroke-linejoin="round">
382
                </path>
383
                <path d="M4.75 15.25V5.75C4.75 5.75 6 4.75 9 4.75C12 4.75 13.5 6.25 16 6.25C18.5
384
                6.25 19.25 5.75 19.25 5.75L15.75 10.5L19.25 15.25C19.25 15.25 18.5 16.25 16
385
                16.25C13.5 16.25 11.5 14.25 9 14.25C6.5 14.25 4.75 15.25 4.75 15.25Z"
386
                    stroke="currentColor"
387
                    stroke-width="2"
388
                    stroke-linecap="round"
389
                    stroke-linejoin="round"></path>
390
                </svg>';
391
            }
392
            if ($attemptobj->can_navigate_to($slot)) {
393
                $row = array(
394
                    html_writer::link(
395
                        $attemptobj->attempt_url($slot),
396
                        $attemptobj->get_question_number($slot) . $flag
397
                    ),
398
                    $attemptobj->get_question_status($slot, $displayoptions->correctness)
399
                );
400
            } else {
401
                $row = array(
402
                    $attemptobj->get_question_number($slot) . $flag,
403
                    $attemptobj->get_question_status($slot, $displayoptions->correctness)
404
                );
405
            }
406
            if ($markscolumn) {
407
                $row[] = $attemptobj->get_question_mark($slot);
408
            }
409
            $table->data[] = $row;
410
            $table->rowclasses[] = 'quizsummary' . $slot . ' ' . $attemptobj->get_question_state_class(
411
                $slot,
412
                $displayoptions->correctness
413
            );
414
        }
415
 
416
        // Print the summary table.
417
        $output = html_writer::table($table);
418
 
419
        return $output;
420
    }
421
}
422
 
423
require_once($CFG->dirroot . "/question/engine/renderer.php");
424
class theme_universe_core_question_renderer extends core_question_renderer {
425
    /**
426
     * Generate the information bit of the question display that contains the
427
     * metadata like the question number, current state, and mark.
428
     * @param question_attempt $qa the question attempt to display.
429
     * @param qbehaviour_renderer $behaviouroutput the renderer to output the behaviour
430
     *      specific parts.
431
     * @param qtype_renderer $qtoutput the renderer to output the question type
432
     *      specific parts.
433
     * @param question_display_options $options controls what should and should not be displayed.
434
     * @param string|null $number The question number to display. 'i' is a special
435
     *      value that gets displayed as Information. Null means no number is displayed.
436
     * @return HTML fragment.
437
     */
438
    protected function info(
439
        question_attempt $qa,
440
        qbehaviour_renderer $behaviouroutput,
441
        qtype_renderer $qtoutput,
442
        question_display_options $options,
443
        $number
444
    ) {
445
        $output = '';
446
        $output .= '<div class="d-flex align-items-center flex-wrap mb-sm-2 mb-md-0">' .
447
            $this->number($number) .
448
            '<div class="d-inline-flex align-items-center flex-wrap">' .
449
            $this->status($qa, $behaviouroutput, $options) .
450
            $this->mark_summary($qa, $behaviouroutput, $options) .
451
            '</div></div>';
452
        $output .= '<div>' .
453
            $this->question_flag($qa, $options->flags) .
454
            $this->edit_question_link($qa, $options) .
455
            '</div>';
456
        return $output;
457
    }
458
 
459
    /**
460
     * Generate the display of the question number.
461
     * @param string|null $number The question number to display. 'i' is a special
462
     *      value that gets displayed as Information. Null means no number is displayed.
463
     * @return HTML fragment.
464
     */
465
    protected function number($number) {
466
        if (trim($number) === '') {
467
            return '';
468
        }
469
        $numbertext = '';
470
        if (trim($number) === 'i') {
471
            $numbertext = get_string('information', 'question');
472
        } else {
473
            $numbertext = get_string(
474
                'questionx',
475
                'question',
476
                html_writer::tag('span', $number, array('class' => 'rui-qno'))
477
            );
478
        }
479
        return html_writer::tag('h4', $numbertext, array('class' => 'h3 w-100 mb-2'));
480
    }
481
 
482
 
483
    /**
484
     * Generate the display of the status line that gives the current state of
485
     * the question.
486
     * @param question_attempt $qa the question attempt to display.
487
     * @param qbehaviour_renderer $behaviouroutput the renderer to output the behaviour
488
     *      specific parts.
489
     * @param question_display_options $options controls what should and should not be displayed.
490
     * @return HTML fragment.
491
     */
492
    protected function status(
493
        question_attempt $qa,
494
        qbehaviour_renderer $behaviouroutput,
495
        question_display_options $options
496
    ) {
497
        return html_writer::tag(
498
            'div',
499
            $qa->get_state_string($options->correctness),
500
            array('class' => 'state mr-2 my-2')
501
        );
502
    }
503
 
504
    /**
505
     * Render the question flag, assuming $flagsoption allows it.
506
     *
507
     * @param question_attempt $qa the question attempt to display.
508
     * @param int $flagsoption the option that says whether flags should be displayed.
509
     */
510
    protected function question_flag(question_attempt $qa, $flagsoption) {
511
        global $CFG;
512
 
513
        $divattributes = array('class' => 'questionflag mx-1 d-none');
514
 
515
        switch ($flagsoption) {
516
            case question_display_options::VISIBLE:
517
                $flagcontent = $this->get_flag_html($qa->is_flagged());
518
                break;
519
 
520
            case question_display_options::EDITABLE:
521
                $id = $qa->get_flag_field_name();
522
                $checkboxattributes = array(
523
                    'type' => 'checkbox',
524
                    'id' => $id . 'checkbox',
525
                    'name' => $id,
526
                    'value' => 1,
527
                );
528
                if ($qa->is_flagged()) {
529
                    $checkboxattributes['checked'] = 'checked';
530
                }
531
                $postdata = question_flags::get_postdata($qa);
532
 
533
                $flagcontent = html_writer::empty_tag(
534
                    'input',
535
                    array('type' => 'hidden', 'name' => $id, 'value' => 0)
536
                ) .
537
                    html_writer::empty_tag('input', $checkboxattributes) .
538
                    html_writer::empty_tag(
539
                        'input',
540
                        array('type' => 'hidden', 'value' => $postdata, 'class' => 'questionflagpostdata')
541
                    ) .
542
                    html_writer::tag(
543
                        'label',
544
                        $this->get_flag_html($qa->is_flagged(), $id . 'img'),
545
                        array('id' => $id . 'label', 'for' => $id . 'checkbox')
546
                    ) . "\n";
547
 
548
                $divattributes = array(
549
                    'class' => 'questionflag mb-sm-2 mb-md-0 mx-md-2 editable d-inline-flex',
550
                    'aria-atomic' => 'true',
551
                    'aria-relevant' => 'text',
552
                    'aria-live' => 'assertive',
553
                );
554
 
555
                break;
556
 
557
            default:
558
                $flagcontent = '';
559
        }
560
 
561
        return html_writer::nonempty_tag('div', $flagcontent, $divattributes);
562
    }
563
 
564
 
565
    protected function edit_question_link(question_attempt $qa, question_display_options $options) {
566
        global $CFG;
567
 
568
        if (empty($options->editquestionparams)) {
569
            return '';
570
        }
571
 
572
        $params = $options->editquestionparams;
573
        if ($params['returnurl'] instanceof moodle_url) {
574
            $params['returnurl'] = $params['returnurl']->out_as_local_url(false);
575
        }
576
        $params['id'] = $qa->get_question_id();
577
        $editurl = new moodle_url('/question/bank/editquestion/question.php', $params);
578
 
579
        $icon = '<svg width="19" height="19" fill="none" viewBox="0 0 24 24">
580
        <path stroke="currentColor"
581
            stroke-linecap="round"
582
            stroke-linejoin="round"
583
            stroke-width="2"
584
            d="M4.75 19.25L9 18.25L18.2929 8.95711C18.6834 8.56658 18.6834
585
            7.93342 18.2929 7.54289L16.4571 5.70711C16.0666 5.31658 15.4334
586
            5.31658 15.0429 5.70711L5.75 15L4.75 19.25Z">
587
        </path>
588
        <path stroke="currentColor"
589
            stroke-linecap="round"
590
            stroke-linejoin="round"
591
            stroke-width="2"
592
            d="M19.25 19.25H13.75">
593
        </path>
594
        </svg>';
595
 
596
        return html_writer::link($editurl, $icon,
597
        array('class' => 'btn btn-icon btn-secondary editquestion line-height-1 ml-sm-2'));
598
    }
599
}
600