Rev 711 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
// Quiz.
require_once($CFG->dirroot . "/mod/quiz/renderer.php");
class theme_universe_mod_quiz_renderer extends mod_quiz\output\renderer
{
/**
* Outputs a box.
*
* @param string $contents The contents of the box
* @param string $classes A space-separated list of CSS classes
* @param string $id An optional ID
* @param array $attributes An array of other attributes to give the box.
* @return string the HTML to output.
*/
public function space_box($contents, $classes = 'generalbox', $id = null, $attributes = array())
{
return $this->space_box_start($classes, $id, $attributes) . $contents . $this->space_box_end();
}
/**
* Outputs the opening section of a box.
*
* @param string $classes A space-separated list of CSS classes
* @param string $id An optional ID
* @param array $attributes An array of other attributes to give the box.
* @return string the HTML to output.
*/
public function space_box_start($classes = 'generalbox', $id = null, $attributes = array())
{
$this->opencontainers->push('box', html_writer::end_tag('div'));
$attributes['id'] = $id;
$attributes['class'] = 'box ' . renderer_base::prepare_classes($classes);
return html_writer::start_tag('div', $attributes);
}
/**
* Outputs the closing section of a box.
*
* @return string the HTML to output.
*/
public function space_box_end()
{
return $this->opencontainers->pop('box');
}
/**
* Output the page information
*
* @param object $quiz the quiz settings.
* @param object $cm the course_module object.
* @param context $context the quiz context.
* @param array $messages any access messages that should be described.
* @param bool $quizhasquestions does quiz has questions added.
* @return string HTML to output.
*/
public function view_information($quiz, $cm, $context, $messages, bool $quizhasquestions = false)
{
$output = '';
// Output any access messages.
if ($messages) {
$output .= $this->box($this->access_messages($messages), 'rui-quizinfo quizinfo mt-3');
}
// Show number of attempts summary to those who can view reports.
if (has_capability('mod/quiz:viewreports', $context)) {
if (
$strattemptnum = $this->quiz_attempt_summary_link_to_reports(
$quiz,
$cm,
$context
)
) {
$output .= html_writer::tag(
'div',
$strattemptnum,
array('class' => 'rui-quizattemptcounts quizattemptcounts my-4')
);
}
}
if (has_any_capability(['mod/quiz:manageoverrides', 'mod/quiz:viewoverrides'], $context)) {
if ($overrideinfo = $this->quiz_override_summary_links($quiz, $cm)) {
$output .= html_writer::tag('div', $overrideinfo, ['class' => 'rui-quizattemptcounts quizattemptcounts my-4']);
}
}
return $output;
}
/**
* Generates the table of data
*
* @param array $quiz Array contining quiz data
* @param int $context The page context ID
* @param mod_quiz_view_object $viewobj
*/
public function view_table($quiz, $context, $viewobj)
{
if (!$viewobj->attempts) {
return '';
}
// Prepare table header.
$table = new html_table();
$table->attributes['class'] = 'generaltable rui-quizattemptsummary mt-2 mb-0';
$table->head = array();
$table->align = array();
$table->size = array();
if ($viewobj->attemptcolumn) {
$table->head[] = get_string('attemptnumber', 'quiz');
$table->size[] = '';
}
$table->head[] = get_string('attemptstate', 'quiz');
$table->align[] = 'left';
$table->size[] = '';
if ($viewobj->markcolumn) {
$table->head[] = get_string('marks', 'quiz') . ' / ' .
quiz_format_grade($quiz, $quiz->sumgrades);
$table->size[] = '';
}
if ($viewobj->gradecolumn) {
$table->head[] = get_string('grade', 'quiz') . ' / ' .
quiz_format_grade($quiz, $quiz->grade);
$table->size[] = '';
}
if ($viewobj->canreviewmine) {
$table->head[] = get_string('review', 'quiz');
$table->size[] = '';
}
if ($viewobj->feedbackcolumn) {
$table->head[] = get_string('feedback', 'quiz');
$table->align[] = 'left';
$table->size[] = '';
}
// One row for each attempt.
foreach ($viewobj->attemptobjs as $attemptobj) {
$attemptoptions = $attemptobj->get_display_options(true);
$row = array();
// Add the attempt number.
if ($viewobj->attemptcolumn) {
if ($attemptobj->is_preview()) {
$row[] = get_string('preview', 'quiz');
} else {
$row[] = $attemptobj->get_attempt_number();
}
}
$row[] = $this->attempt_state($attemptobj);
if ($viewobj->markcolumn) {
if (
$attemptoptions->marks >= question_display_options::MARK_AND_MAX &&
$attemptobj->is_finished()
) {
$row[] = quiz_format_grade($quiz, $attemptobj->get_sum_marks());
} else {
$row[] = '';
}
}
// Ouside the if because we may be showing feedback but not grades.
$attemptgrade = quiz_rescale_grade($attemptobj->get_sum_marks(), $quiz, false);
if ($viewobj->gradecolumn) {
if (
$attemptoptions->marks >= question_display_options::MARK_AND_MAX &&
$attemptobj->is_finished()
) {
// Highlight the highest grade if appropriate.
if (
$viewobj->overallstats && !$attemptobj->is_preview()
&& $viewobj->numattempts > 1 && !is_null($viewobj->mygrade)
&& $attemptobj->get_state() == quiz_attempt::FINISHED
&& $attemptgrade == $viewobj->mygrade
&& $quiz->grademethod == QUIZ_GRADEHIGHEST
) {
$table->rowclasses[$attemptobj->get_attempt_number()] = 'bestrow';
}
$row[] = quiz_format_grade($quiz, $attemptgrade);
} else {
$row[] = '';
}
}
if ($viewobj->canreviewmine) {
$row[] = $viewobj->accessmanager->make_review_link(
$attemptobj->get_attempt(),
$attemptoptions,
$this
);
}
if ($viewobj->feedbackcolumn && $attemptobj->is_finished()) {
if ($attemptoptions->overallfeedback) {
$row[] = quiz_feedback_for_grade($attemptgrade, $quiz, $context);
} else {
$row[] = '';
}
}
if ($attemptobj->is_preview()) {
$table->data['preview'] = $row;
} else {
$table->data[$attemptobj->get_attempt_number()] = $row;
}
} // End. of loop over attempts.
$output = '';
$output .= $this->view_table_heading();
$output .= html_writer::start_tag('div', array('class' => 'table-overflow mb-4'));
$output .= html_writer::table($table);
$output .= html_writer::end_tag('div');
return $output;
}
/*
* View Page
*/
/**
* Generates the view page
*
* @param stdClass $course the course settings row from the database.
* @param stdClass $quiz the quiz settings row from the database.
* @param stdClass $cm the course_module settings row from the database.
* @param context_module $context the quiz context.
* @param mod_quiz_view_object $viewobj
* @return string HTML to display
*/
public function view_page($course, $quiz, $cm, $context, $viewobj)
{
$output = '';
$output .= $this->view_page_tertiary_nav($viewobj);
$output .= $this->view_information($quiz, $cm, $context, $viewobj->infomessages);
$output .= $this->view_table($quiz, $context, $viewobj);
$output .= $this->view_result_info($quiz, $context, $cm, $viewobj);
$output .= $this->box($this->view_page_buttons($viewobj), 'rui-quizattempt');
return $output;
}
/**
* Outputs the table containing data from summary data array
*
* @param array $summarydata contains row data for table
* @param int $page contains the current page number
*/
public function review_summary_table($summarydata, $page)
{
$summarydata = $this->filter_review_summary_table($summarydata, $page);
if (empty($summarydata)) {
return '';
}
$output = '';
$output .= html_writer::start_tag('div', array('class' => 'rui-summary-table'));
$output .= html_writer::start_tag('div', array('class' => 'rui-info-container rui-quizreviewsummary'));
foreach ($summarydata as $rowdata => $val) {
$csstitle = $rowdata;
if ($val['title'] instanceof renderable) {
$title = $this->render($val['title']);
} else {
$title = $val['title'];
}
if ($val['content'] instanceof renderable) {
$content = $this->render($val['content']);
} else {
$content = $val['content'];
}
if ($val['title'] instanceof renderable) {
$output .= html_writer::tag(
'div',
html_writer::tag('h5', $title, array('class' => 'rui-infobox-title')) .
html_writer::tag('div', $content, array('class' => 'rui-infobox-content--small')),
array('class' => 'rui-infobox rui-infobox--avatar')
);
} else {
$output .= html_writer::tag(
'div',
html_writer::tag('h5', $title, array('class' => 'rui-infobox-title')) .
html_writer::tag('div', $content, array('class' => 'rui-infobox-content--small')),
array('class' => 'rui-infobox rui-infobox--' . strtolower(str_replace(' ', '', $csstitle)))
);
}
}
$output .= html_writer::end_tag('div');
$output .= html_writer::end_tag('div');
return $output;
}
/**
* Generates the table of summarydata
*
* @param quiz_attempt $attemptobj
* @param mod_quiz_display_options $displayoptions
*/
public function summary_table($attemptobj, $displayoptions)
{
// Prepare the summary table header.
$table = new html_table();
$table->attributes['class'] = 'generaltable quizsummaryofattempt';
$table->head = array(get_string('question', 'quiz'), get_string('status', 'quiz'));
$table->align = array('left', 'left');
$table->size = array('', '');
$markscolumn = $displayoptions->marks >= question_display_options::MARK_AND_MAX;
if ($markscolumn) {
$table->head[] = get_string('marks', 'quiz');
$table->align[] = 'left';
$table->size[] = '';
}
$tablewidth = count($table->align);
$table->data = array();
// Get the summary info for each question.
$slots = $attemptobj->get_slots();
foreach ($slots as $slot) {
// Add a section headings if we need one here.
$heading = $attemptobj->get_heading_before_slot($slot);
if ($heading !== null) {
// There is a heading here.
$rowclasses = 'quizsummaryheading';
if ($heading) {
$heading = format_string($heading);
} else if (count($attemptobj->get_quizobj()->get_sections()) > 1) {
// If this is the start of an unnamed section, and the quiz has more
// than one section, then add a default heading.
$heading = get_string('sectionnoname', 'quiz');
$rowclasses .= ' dimmed_text';
}
$cell = new html_table_cell(format_string($heading));
$cell->header = true;
$cell->colspan = $tablewidth;
$table->data[] = array($cell);
$table->rowclasses[] = $rowclasses;
}
// Don't display information items.
if (!$attemptobj->is_real_question($slot)) {
continue;
}
$flag = '';
// Real question, show it.
if ($attemptobj->is_question_flagged($slot)) {
// Quiz has custom JS manipulating these image tags - so we can't use the pix_icon method here.
$flag = '<svg class="ml-2"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M4.75 5.75V19.25"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
</path>
<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
6.25 19.25 5.75 19.25 5.75L15.75 10.5L19.25 15.25C19.25 15.25 18.5 16.25 16
16.25C13.5 16.25 11.5 14.25 9 14.25C6.5 14.25 4.75 15.25 4.75 15.25Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"></path>
</svg>';
}
if ($attemptobj->can_navigate_to($slot)) {
$row = array(
html_writer::link(
$attemptobj->attempt_url($slot),
$attemptobj->get_question_number($slot) . $flag
),
$attemptobj->get_question_status($slot, $displayoptions->correctness)
);
} else {
$row = array(
$attemptobj->get_question_number($slot) . $flag,
$attemptobj->get_question_status($slot, $displayoptions->correctness)
);
}
if ($markscolumn) {
$row[] = $attemptobj->get_question_mark($slot);
}
$table->data[] = $row;
$table->rowclasses[] = 'quizsummary' . $slot . ' ' . $attemptobj->get_question_state_class(
$slot,
$displayoptions->correctness
);
}
// Print the summary table.
$output = html_writer::table($table);
return $output;
}
}
require_once($CFG->dirroot . "/question/engine/renderer.php");
class theme_universe_core_question_renderer extends core_question_renderer
{
/**
* Generate the information bit of the question display that contains the
* metadata like the question number, current state, and mark.
* @param question_attempt $qa the question attempt to display.
* @param qbehaviour_renderer $behaviouroutput the renderer to output the behaviour
* specific parts.
* @param qtype_renderer $qtoutput the renderer to output the question type
* specific parts.
* @param question_display_options $options controls what should and should not be displayed.
* @param string|null $number The question number to display. 'i' is a special
* value that gets displayed as Information. Null means no number is displayed.
* @return HTML fragment.
*/
protected function info(
question_attempt $qa,
qbehaviour_renderer $behaviouroutput,
qtype_renderer $qtoutput,
question_display_options $options,
$number
) {
$output = '';
$output .= '<div class="d-flex align-items-center flex-wrap mb-sm-2 mb-md-0">' .
$this->number($number) .
'<div class="d-inline-flex align-items-center flex-wrap">' .
$this->status($qa, $behaviouroutput, $options) .
$this->mark_summary($qa, $behaviouroutput, $options) .
'</div></div>';
$output .= '<div>' .
$this->question_flag($qa, $options->flags) .
$this->edit_question_link($qa, $options) .
'</div>';
return $output;
}
/**
* Generate the display of the question number.
* @param string|null $number The question number to display. 'i' is a special
* value that gets displayed as Information. Null means no number is displayed.
* @return HTML fragment.
*/
protected function number($number)
{
if (trim($number) === '') {
return '';
}
$numbertext = '';
if (trim($number) === 'i') {
$numbertext = get_string('information', 'question');
} else {
$numbertext = get_string(
'questionx',
'question',
html_writer::tag('span', $number, array('class' => 'rui-qno'))
);
}
return html_writer::tag('h4', $numbertext, array('class' => 'h3 w-100 mb-2'));
}
/**
* Generate the display of the status line that gives the current state of
* the question.
* @param question_attempt $qa the question attempt to display.
* @param qbehaviour_renderer $behaviouroutput the renderer to output the behaviour
* specific parts.
* @param question_display_options $options controls what should and should not be displayed.
* @return HTML fragment.
*/
protected function status(
question_attempt $qa,
qbehaviour_renderer $behaviouroutput,
question_display_options $options
) {
return html_writer::tag(
'div',
$qa->get_state_string($options->correctness),
array('class' => 'state mr-2')
);
}
/**
* Render the question flag, assuming $flagsoption allows it.
*
* @param question_attempt $qa the question attempt to display.
* @param int $flagsoption the option that says whether flags should be displayed.
*/
protected function question_flag(question_attempt $qa, $flagsoption)
{
global $CFG;
$divattributes = array('class' => 'questionflag mx-1 d-none');
switch ($flagsoption) {
case question_display_options::VISIBLE:
$flagcontent = $this->get_flag_html($qa->is_flagged());
break;
case question_display_options::EDITABLE:
$id = $qa->get_flag_field_name();
$checkboxattributes = array(
'type' => 'checkbox',
'id' => $id . 'checkbox',
'name' => $id,
'value' => 1,
);
if ($qa->is_flagged()) {
$checkboxattributes['checked'] = 'checked';
}
$postdata = question_flags::get_postdata($qa);
$flagcontent = html_writer::empty_tag(
'input',
array('type' => 'hidden', 'name' => $id, 'value' => 0)
) .
html_writer::empty_tag('input', $checkboxattributes) .
html_writer::empty_tag(
'input',
array('type' => 'hidden', 'value' => $postdata, 'class' => 'questionflagpostdata')
) .
html_writer::tag(
'label',
$this->get_flag_html($qa->is_flagged(), $id . 'img'),
array('id' => $id . 'label', 'for' => $id . 'checkbox')
) . "\n";
$divattributes = array(
'class' => 'questionflag mb-sm-2 mb-md-0 mx-md-2 editable d-inline-flex',
'aria-atomic' => 'true',
'aria-relevant' => 'text',
'aria-live' => 'assertive',
);
break;
default:
$flagcontent = '';
}
return html_writer::nonempty_tag('div', $flagcontent, $divattributes);
}
protected function edit_question_link(question_attempt $qa, question_display_options $options)
{
global $CFG;
if (empty($options->editquestionparams)) {
return '';
}
$params = $options->editquestionparams;
if ($params['returnurl'] instanceof moodle_url) {
$params['returnurl'] = $params['returnurl']->out_as_local_url(false);
}
$params['id'] = $qa->get_question_id();
$editurl = new moodle_url('/question/bank/editquestion/question.php', $params);
$icon = '<svg width="19" height="19" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4.75 19.25L9 18.25L18.2929 8.95711C18.6834 8.56658 18.6834
7.93342 18.2929 7.54289L16.4571 5.70711C16.0666 5.31658 15.4334
5.31658 15.0429 5.70711L5.75 15L4.75 19.25Z">
</path>
<path stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19.25 19.25H13.75">
</path>
</svg>';
return html_writer::link(
$editurl,
$icon,
array('class' => 'btn btn-icon btn-secondary editquestion line-height-1 ml-sm-2')
);
}
}