mirror of
https://github.com/moodle/moodle.git
synced 2025-01-19 06:18:28 +01:00
Merge branch 'MDL-80880' of https://github.com/timhunt/moodle
This commit is contained in:
commit
8e99589f77
250
mod/quiz/classes/output/attempt_summary_information.php
Normal file
250
mod/quiz/classes/output/attempt_summary_information.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?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/>.
|
||||
|
||||
namespace mod_quiz\output;
|
||||
|
||||
use action_link;
|
||||
use core\output\named_templatable;
|
||||
use html_writer;
|
||||
use mod_quiz\quiz_attempt;
|
||||
use moodle_url;
|
||||
use mod_quiz\question\display_options;
|
||||
use question_display_options;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use user_picture;
|
||||
|
||||
/**
|
||||
* A summary of a single quiz attempt for rendering.
|
||||
*
|
||||
* This is used in places like
|
||||
* - at the top of the review attempt page (review.php)
|
||||
* - at the top of the review single question page (reviewquestion.php)
|
||||
* - on the quiz entry page (view.php).
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2024 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class attempt_summary_information implements renderable, named_templatable {
|
||||
|
||||
/** @var array[] The rows of summary data. {@see add_item()} should make the structure clear. */
|
||||
protected array $summarydata = [];
|
||||
|
||||
/**
|
||||
* Add an item to the summary.
|
||||
*
|
||||
* @param string $shortname unique identifier of this item (not displayed).
|
||||
* @param string|renderable $title the title of this item.
|
||||
* @param string|renderable $content the content of this item.
|
||||
*/
|
||||
public function add_item(string $shortname, string|renderable $title, string|renderable $content): void {
|
||||
$this->summarydata[$shortname] = [
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the data held, to keep only the information with the given shortnames.
|
||||
*
|
||||
* @param array $shortnames items to keep.
|
||||
*/
|
||||
public function filter_keeping_only(array $shortnames): void {
|
||||
foreach ($this->summarydata as $shortname => $rowdata) {
|
||||
if (!in_array($shortname, $shortnames)) {
|
||||
unset($this->summarydata[$shortname]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To aid conversion of old code. This converts the old array format into an instance of this class.
|
||||
*
|
||||
* @param array $items array of $shortname => [$title, $content].
|
||||
* @return static
|
||||
*/
|
||||
public static function create_from_legacy_array(array $items): static {
|
||||
$summary = new static();
|
||||
foreach ($items as $shortname => $item) {
|
||||
$summary->add_item($shortname, $item['title'], $item['content']);
|
||||
}
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise an instance of this class for a particular quiz attempt.
|
||||
*
|
||||
* @param quiz_attempt $attemptobj the attempt to summarise.
|
||||
* @param display_options $options options for what can be seen.
|
||||
* @param int|null $pageforlinkingtootherattempts if null, no links to other attempsts will be created.
|
||||
* If specified, the URL of this particular page of the attempt, otherwise
|
||||
* the URL will go to the first page. If -1, deduce $page from $slot.
|
||||
* @param bool|null $showall if true, the URL will be to review the entire attempt on one page,
|
||||
* and $page will be ignored. If null, a sensible default will be chosen.
|
||||
* @return self summary information.
|
||||
*/
|
||||
public static function create_for_attempt(
|
||||
quiz_attempt $attemptobj,
|
||||
display_options $options,
|
||||
?int $pageforlinkingtootherattempts = null,
|
||||
?bool $showall = null,
|
||||
): static {
|
||||
global $DB, $USER;
|
||||
$summary = new static();
|
||||
|
||||
// Prepare summary information about the whole attempt.
|
||||
if (!$attemptobj->get_quiz()->showuserpicture && $attemptobj->get_userid() != $USER->id) {
|
||||
// If showuserpicture is true, the picture is shown elsewhere, so don't repeat it.
|
||||
$student = $DB->get_record('user', ['id' => $attemptobj->get_userid()]);
|
||||
$userpicture = new user_picture($student);
|
||||
$userpicture->courseid = $attemptobj->get_courseid();
|
||||
$summary->add_item('user', $userpicture,
|
||||
new action_link(
|
||||
new moodle_url('/user/view.php', ['id' => $student->id, 'course' => $attemptobj->get_courseid()]),
|
||||
fullname($student, true),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($pageforlinkingtootherattempts !== null && $attemptobj->has_capability('mod/quiz:viewreports')) {
|
||||
$attemptlist = $attemptobj->links_to_other_attempts(
|
||||
$attemptobj->review_url(null, $pageforlinkingtootherattempts, $showall));
|
||||
if ($attemptlist) {
|
||||
$summary->add_item('attemptlist', get_string('attempts', 'quiz'), $attemptlist);
|
||||
}
|
||||
}
|
||||
|
||||
// Timing information.
|
||||
$attempt = $attemptobj->get_attempt();
|
||||
$quiz = $attemptobj->get_quiz();
|
||||
$overtime = 0;
|
||||
|
||||
if ($attempt->state == quiz_attempt::FINISHED) {
|
||||
if ($timetaken = ($attempt->timefinish - $attempt->timestart)) {
|
||||
if ($quiz->timelimit && $timetaken > ($quiz->timelimit + 60)) {
|
||||
$overtime = $timetaken - $quiz->timelimit;
|
||||
$overtime = format_time($overtime);
|
||||
}
|
||||
$timetaken = format_time($timetaken);
|
||||
} else {
|
||||
$timetaken = "-";
|
||||
}
|
||||
} else {
|
||||
$timetaken = get_string('unfinished', 'quiz');
|
||||
}
|
||||
|
||||
$summary->add_item('startedon', get_string('startedon', 'quiz'), userdate($attempt->timestart));
|
||||
|
||||
$summary->add_item('state', get_string('attemptstate', 'quiz'),
|
||||
quiz_attempt::state_name($attemptobj->get_attempt()->state));
|
||||
|
||||
if ($attempt->state == quiz_attempt::FINISHED) {
|
||||
$summary->add_item('completedon', get_string('completedon', 'quiz'),
|
||||
userdate($attempt->timefinish));
|
||||
$summary->add_item('timetaken', get_string('timetaken', 'quiz'), $timetaken);
|
||||
}
|
||||
|
||||
if (!empty($overtime)) {
|
||||
$summary->add_item('overdue', get_string('overdue', 'quiz'), $overtime);
|
||||
}
|
||||
|
||||
// Show marks (if the user is allowed to see marks at the moment).
|
||||
$grade = quiz_rescale_grade($attempt->sumgrades, $quiz, false);
|
||||
if ($options->marks >= question_display_options::MARK_AND_MAX && quiz_has_grades($quiz)) {
|
||||
|
||||
if ($attempt->state != quiz_attempt::FINISHED) {
|
||||
// Cannot display grade.
|
||||
|
||||
} else if (is_null($grade)) {
|
||||
$summary->add_item('grade', get_string('gradenoun'),
|
||||
quiz_format_grade($quiz, $grade));
|
||||
|
||||
} else {
|
||||
// Show raw marks only if they are different from the grade (like on the view page).
|
||||
if ($quiz->grade != $quiz->sumgrades) {
|
||||
$a = new stdClass();
|
||||
$a->grade = quiz_format_grade($quiz, $attempt->sumgrades);
|
||||
$a->maxgrade = quiz_format_grade($quiz, $quiz->sumgrades);
|
||||
$summary->add_item('marks', get_string('marks', 'quiz'),
|
||||
get_string('outofshort', 'quiz', $a));
|
||||
}
|
||||
|
||||
// Now the scaled grade.
|
||||
$a = new stdClass();
|
||||
$a->grade = html_writer::tag('b', quiz_format_grade($quiz, $grade));
|
||||
$a->maxgrade = quiz_format_grade($quiz, $quiz->grade);
|
||||
if ($quiz->grade != 100) {
|
||||
// Show the percentage using the configured number of decimal places,
|
||||
// but without trailing zeroes.
|
||||
$a->percent = html_writer::tag('b', format_float(
|
||||
$attempt->sumgrades * 100 / $quiz->sumgrades,
|
||||
$quiz->decimalpoints, true, true));
|
||||
$formattedgrade = get_string('outofpercent', 'quiz', $a);
|
||||
} else {
|
||||
$formattedgrade = get_string('outof', 'quiz', $a);
|
||||
}
|
||||
$summary->add_item('grade', get_string('gradenoun'),
|
||||
$formattedgrade);
|
||||
}
|
||||
}
|
||||
|
||||
// Any additional summary data from the behaviour.
|
||||
foreach ($attemptobj->get_additional_summary_data($options) as $shortname => $data) {
|
||||
$summary->add_item($shortname, $data['title'], $data['content']);
|
||||
}
|
||||
|
||||
// Feedback if there is any, and the user is allowed to see it now.
|
||||
$feedback = $attemptobj->get_overall_feedback($grade);
|
||||
if ($options->overallfeedback && $feedback) {
|
||||
$summary->add_item('feedback', get_string('feedback', 'quiz'), $feedback);
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
public function export_for_template(renderer_base $output): array {
|
||||
|
||||
$templatecontext = [
|
||||
'hasitems' => !empty($this->summarydata),
|
||||
'items' => [],
|
||||
];
|
||||
foreach ($this->summarydata as $item) {
|
||||
if ($item['title'] instanceof renderable) {
|
||||
$title = $output->render($item['title']);
|
||||
} else {
|
||||
$title = $item['title'];
|
||||
}
|
||||
|
||||
if ($item['content'] instanceof renderable) {
|
||||
$content = $output->render($item['content']);
|
||||
} else {
|
||||
$content = $item['content'];
|
||||
}
|
||||
|
||||
$templatecontext['items'][] = (object) ['title' => $title, 'content' => $content];
|
||||
}
|
||||
|
||||
return $templatecontext;
|
||||
}
|
||||
|
||||
public function get_template_name(\renderer_base $renderer): string {
|
||||
// Only reason we are forced to implement this is that we want the quiz renderer
|
||||
// passed to export_for_template, not a core_renderer.
|
||||
return 'mod_quiz/attempt_summary_information';
|
||||
}
|
||||
}
|
85
mod/quiz/classes/output/list_of_attempts.php
Normal file
85
mod/quiz/classes/output/list_of_attempts.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?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/>.
|
||||
|
||||
namespace mod_quiz\output;
|
||||
|
||||
use core\output\named_templatable;
|
||||
use mod_quiz\quiz_attempt;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
|
||||
/**
|
||||
* Display summary information about a list of attempts.
|
||||
*
|
||||
* This is used on the front page of the quiz (view.php).
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2024 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class list_of_attempts implements renderable, named_templatable {
|
||||
|
||||
/** @var int time to consider as now. */
|
||||
protected int $timenow;
|
||||
|
||||
/** @var quiz_attempt[] The list of attempts to summarise. */
|
||||
protected array $attempts = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $timenow time that is now.
|
||||
*/
|
||||
public function __construct(int $timenow) {
|
||||
$this->timenow = $timenow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event to the list.
|
||||
*
|
||||
* @param quiz_attempt $attemptobj
|
||||
*/
|
||||
public function add_attempt(quiz_attempt $attemptobj): void {
|
||||
$this->attempts[] = $attemptobj;
|
||||
}
|
||||
|
||||
public function export_for_template(renderer_base $output): array {
|
||||
|
||||
$templatecontext = [
|
||||
'hasattempts' => !empty($this->attempts),
|
||||
'attempts' => [],
|
||||
];
|
||||
|
||||
foreach ($this->attempts as $attemptobj) {
|
||||
$displayoptions = $attemptobj->get_display_options(true);
|
||||
$templatecontext['attempts'][] = (object) [
|
||||
'name' => get_string('attempt', 'mod_quiz', $attemptobj->get_attempt_number()),
|
||||
'summarydata' => attempt_summary_information::create_for_attempt(
|
||||
$attemptobj, $displayoptions)->export_for_template($output),
|
||||
'reviewlink' => $attemptobj->get_access_manager($this->timenow)->make_review_link(
|
||||
$attemptobj->get_attempt(), $displayoptions, $output),
|
||||
];
|
||||
}
|
||||
|
||||
return $templatecontext;
|
||||
}
|
||||
|
||||
public function get_template_name(\renderer_base $renderer): string {
|
||||
// Only reason we are forced to implement this is that we want the quiz renderer
|
||||
// passed to export_for_template, not a core_renderer.
|
||||
return 'mod_quiz/list_of_attempts';
|
||||
}
|
||||
}
|
@ -54,15 +54,22 @@ class renderer extends plugin_renderer_base {
|
||||
* @param bool $showall whether to show entire attempt on one page.
|
||||
* @param bool $lastpage if true the current page is the last page.
|
||||
* @param display_options $displayoptions instance of display_options.
|
||||
* @param array $summarydata contains all table data
|
||||
* @param attempt_summary_information|array $summarydata summary information about the attempt.
|
||||
* Passing an array is deprecated.
|
||||
* @return string HTML to display.
|
||||
*/
|
||||
public function review_page(quiz_attempt $attemptobj, $slots, $page, $showall,
|
||||
$lastpage, display_options $displayoptions, $summarydata) {
|
||||
if (is_array($summarydata)) {
|
||||
debugging('Since Moodle 4.4, $summarydata passed to review_page should be a attempt_summary_information.',
|
||||
DEBUG_DEVELOPER);
|
||||
$summarydata = $this->filter_review_summary_table($summarydata, $page);
|
||||
$summarydata = attempt_summary_information::create_from_legacy_array($summarydata);
|
||||
}
|
||||
|
||||
$output = '';
|
||||
$output .= $this->header();
|
||||
$output .= $this->review_summary_table($summarydata, $page);
|
||||
$output .= $this->review_attempt_summary($summarydata, $page);
|
||||
$output .= $this->review_form($page, $showall, $displayoptions,
|
||||
$this->questions($attemptobj, true, $slots, $page, $showall, $displayoptions),
|
||||
$attemptobj);
|
||||
@ -79,15 +86,21 @@ class renderer extends plugin_renderer_base {
|
||||
* @param int $slot which question to display.
|
||||
* @param int $seq which step of the question attempt to show. null = latest.
|
||||
* @param display_options $displayoptions instance of display_options.
|
||||
* @param array $summarydata contains all table data
|
||||
* @param attempt_summary_information|array $summarydata summary information about the attempt.
|
||||
* Passing an array is deprecated.
|
||||
* @return string HTML to display.
|
||||
*/
|
||||
public function review_question_page(quiz_attempt $attemptobj, $slot, $seq,
|
||||
display_options $displayoptions, $summarydata) {
|
||||
if (is_array($summarydata)) {
|
||||
debugging('Since Moodle 4.4, $summarydata passed to review_question_page should be a attempt_summary_information.',
|
||||
DEBUG_DEVELOPER);
|
||||
$summarydata = attempt_summary_information::create_from_legacy_array($summarydata);
|
||||
}
|
||||
|
||||
$output = '';
|
||||
$output .= $this->header();
|
||||
$output .= $this->review_summary_table($summarydata, 0);
|
||||
$output .= html_writer::div($this->render($summarydata), 'mb-3');
|
||||
|
||||
if (!is_null($seq)) {
|
||||
$output .= $attemptobj->render_question_at_step($slot, $seq, true, $this);
|
||||
@ -118,14 +131,49 @@ class renderer extends plugin_renderer_base {
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* A chance to filter the information before display.
|
||||
*
|
||||
* Moodle core uses this to display less infomrmation on pages after the first.
|
||||
* This is a separate method, becaus it is a useful hook where themes can overrid things.
|
||||
*
|
||||
* @param attempt_summary_information $summarydata the data that will be displayed. Modify as desired.
|
||||
* @param int $page contains the current page number
|
||||
*/
|
||||
public function filter_review_attempt_summary(
|
||||
attempt_summary_information $summarydata,
|
||||
int $page
|
||||
): void {
|
||||
if ($page > 0) {
|
||||
$summarydata->filter_keeping_only(['user', 'attemptlist']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the overall summary of the attempt at the top of the review page.
|
||||
*
|
||||
* @param attempt_summary_information $summarydata contains row data for table.
|
||||
* @param int $page contains the current page number
|
||||
* @return string HTML to display.
|
||||
*/
|
||||
public function review_attempt_summary(
|
||||
attempt_summary_information $summarydata,
|
||||
int $page
|
||||
): string {
|
||||
$this->filter_review_attempt_summary($summarydata, $page);
|
||||
return html_writer::div($this->render($summarydata), 'mb-3');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the summarydata array.
|
||||
*
|
||||
* @param array $summarydata contains row data for table
|
||||
* @param int $page the current page number
|
||||
* @return array updated version of the $summarydata array.
|
||||
* @deprecated since Moodle 4.4. Replaced by filter_review_attempt_summary.
|
||||
*/
|
||||
protected function filter_review_summary_table($summarydata, $page) {
|
||||
debugging('filter_review_summary_table() is deprecated. Replaced by filter_review_attempt_summary().', DEBUG_DEVELOPER);
|
||||
if ($page == 0) {
|
||||
return $summarydata;
|
||||
}
|
||||
@ -146,39 +194,12 @@ class renderer extends plugin_renderer_base {
|
||||
* @param array $summarydata contains row data for table
|
||||
* @param int $page contains the current page number
|
||||
* @return string HTML to display.
|
||||
* @deprecated since Moodle 4.4. Replaced by review_attempt_summary.
|
||||
*/
|
||||
public function review_summary_table($summarydata, $page) {
|
||||
debugging('review_summary_table() is deprecated. Please use review_attempt_summary() instead.', DEBUG_DEVELOPER);
|
||||
$summarydata = $this->filter_review_summary_table($summarydata, $page);
|
||||
if (empty($summarydata)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$output = '';
|
||||
$output .= html_writer::start_tag('table', [
|
||||
'class' => 'generaltable generalbox quizreviewsummary']);
|
||||
$output .= html_writer::start_tag('tbody');
|
||||
foreach ($summarydata as $rowdata) {
|
||||
if ($rowdata['title'] instanceof renderable) {
|
||||
$title = $this->render($rowdata['title']);
|
||||
} else {
|
||||
$title = $rowdata['title'];
|
||||
}
|
||||
|
||||
if ($rowdata['content'] instanceof renderable) {
|
||||
$content = $this->render($rowdata['content']);
|
||||
} else {
|
||||
$content = $rowdata['content'];
|
||||
}
|
||||
|
||||
$output .= html_writer::tag('tr',
|
||||
html_writer::tag('th', $title, ['class' => 'cell', 'scope' => 'row']) .
|
||||
html_writer::tag('td', $content, ['class' => 'cell'])
|
||||
);
|
||||
}
|
||||
|
||||
$output .= html_writer::end_tag('tbody');
|
||||
$output .= html_writer::end_tag('table');
|
||||
return $output;
|
||||
$this->render(attempt_summary_information::create_from_legacy_array($summarydata));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -868,8 +889,8 @@ class renderer extends plugin_renderer_base {
|
||||
|
||||
$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->render($viewobj->attemptslist);
|
||||
$output .= $this->box($this->view_page_buttons($viewobj), 'quizattempt');
|
||||
return $output;
|
||||
}
|
||||
@ -1094,8 +1115,10 @@ class renderer extends plugin_renderer_base {
|
||||
* @param stdClass $quiz the quiz settings.
|
||||
* @param context_module $context the quiz context.
|
||||
* @param view_page $viewobj
|
||||
* @deprecated Since 4.4 please use the {@see list_of_attempts} renderable instead.
|
||||
*/
|
||||
public function view_table($quiz, $context, $viewobj) {
|
||||
debugging('view_table has been deprecated since 4.4 please use the list_of_attempts renderable instead.');
|
||||
if (!$viewobj->attempts) {
|
||||
return '';
|
||||
}
|
||||
|
@ -39,6 +39,8 @@ class view_page {
|
||||
public $attempts;
|
||||
/** @var quiz_attempt[] $attemptobjs objects corresponding to $attempts. */
|
||||
public $attemptobjs;
|
||||
/** @var list_of_attempts list of past attempts for rendering. */
|
||||
public $attemptslist;
|
||||
/** @var access_manager $accessmanager contains various access rules. */
|
||||
public $accessmanager;
|
||||
/** @var bool $canreviewmine whether the current user has the capability to
|
||||
|
@ -593,10 +593,15 @@ class quiz_attempt {
|
||||
* The values are arrays with two items, title and content. Each of these
|
||||
* will be either a string, or a renderable.
|
||||
*
|
||||
* If this method is called before load_questions() is called, then an empty array is returned.
|
||||
*
|
||||
* @param question_display_options $options the display options for this quiz attempt at this time.
|
||||
* @return array as described above.
|
||||
*/
|
||||
public function get_additional_summary_data(question_display_options $options) {
|
||||
if (!isset($this->quba)) {
|
||||
return [];
|
||||
}
|
||||
return $this->quba->get_summary_information($options);
|
||||
}
|
||||
|
||||
@ -947,8 +952,7 @@ class quiz_attempt {
|
||||
* state data about this question.
|
||||
*
|
||||
* @param int $slot the number used to identify this question within this attempt.
|
||||
* @return string the formatted grade, to the number of decimal places specified
|
||||
* by the quiz.
|
||||
* @return string the name of the question. Must be output through format_string.
|
||||
*/
|
||||
public function get_question_name($slot) {
|
||||
return $this->quba->get_question($slot, false)->name;
|
||||
|
@ -23,6 +23,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use mod_quiz\output\attempt_summary_information;
|
||||
|
||||
require_once('../../config.php');
|
||||
require_once('locallib.php');
|
||||
|
||||
@ -55,29 +57,20 @@ $output = $PAGE->get_renderer('mod_quiz');
|
||||
echo $output->header();
|
||||
|
||||
// Prepare summary information about this question attempt.
|
||||
$summarydata = [];
|
||||
$summary = new attempt_summary_information();
|
||||
|
||||
// Student name.
|
||||
$userpicture = new user_picture($student);
|
||||
$userpicture->courseid = $attemptobj->get_courseid();
|
||||
$summarydata['user'] = [
|
||||
'title' => $userpicture,
|
||||
'content' => new action_link(new moodle_url('/user/view.php', [
|
||||
'id' => $student->id, 'course' => $attemptobj->get_courseid()]),
|
||||
fullname($student, true)),
|
||||
];
|
||||
$summary->add_item('user', $userpicture, new action_link(
|
||||
new moodle_url('/user/view.php', [ 'id' => $student->id, 'course' => $attemptobj->get_courseid()]),
|
||||
fullname($student, true)));
|
||||
|
||||
// Quiz name.
|
||||
$summarydata['quizname'] = [
|
||||
'title' => get_string('modulename', 'quiz'),
|
||||
'content' => format_string($attemptobj->get_quiz_name()),
|
||||
];
|
||||
$summary->add_item('quizname', get_string('modulename', 'quiz'), format_string($attemptobj->get_quiz_name()));
|
||||
|
||||
// Question name.
|
||||
$summarydata['questionname'] = [
|
||||
'title' => get_string('question', 'quiz'),
|
||||
'content' => $attemptobj->get_question_name($slot),
|
||||
];
|
||||
$summary->add_item('questionname', get_string('question', 'quiz'), $attemptobj->get_question_name($slot));
|
||||
|
||||
// Process any data that was submitted.
|
||||
if (data_submitted() && confirm_sesskey()) {
|
||||
@ -107,7 +100,7 @@ if (data_submitted() && confirm_sesskey()) {
|
||||
}
|
||||
|
||||
// Print quiz information.
|
||||
echo $output->review_summary_table($summarydata, 0);
|
||||
echo html_writer::div($output->render($summary), 'mb-3');
|
||||
|
||||
// Print the comment form.
|
||||
echo '<form method="post" class="mform" id="manualgradingform" action="' .
|
||||
|
@ -1016,7 +1016,7 @@ $string['subplugintype_quizaccess'] = 'Access rule';
|
||||
$string['subplugintype_quizaccess_plural'] = 'Access rules';
|
||||
$string['substitutedby'] = 'will be substituted by';
|
||||
$string['summaryofattempt'] = 'Summary of attempt';
|
||||
$string['summaryofattempts'] = 'Summary of your previous attempts';
|
||||
$string['summaryofattempts'] = 'Your attempts';
|
||||
$string['temporaryblocked'] = 'You are temporarily not allowed to re-attempt the quiz.<br /> You will be able to take another attempt on:';
|
||||
$string['theattempt'] = 'The attempt';
|
||||
$string['theattempt_help'] = 'Whether the student can review the attempt at all.';
|
||||
|
@ -664,7 +664,7 @@ function quiz_get_user_grades($quiz, $userid = 0) {
|
||||
* Round a grade to the correct number of decimal places, and format it for display.
|
||||
*
|
||||
* @param stdClass $quiz The quiz table row, only $quiz->decimalpoints is used.
|
||||
* @param float $grade The grade to round.
|
||||
* @param float|null $grade The grade to round and display (or null meaning no grade).
|
||||
* @return string
|
||||
*/
|
||||
function quiz_format_grade($quiz, $grade) {
|
||||
|
@ -25,6 +25,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use mod_quiz\output\attempt_summary_information;
|
||||
use mod_quiz\output\navigation_panel_review;
|
||||
use mod_quiz\output\renderer;
|
||||
use mod_quiz\quiz_attempt;
|
||||
@ -113,141 +114,7 @@ $PAGE->set_title($attemptobj->review_page_title($page, $showall));
|
||||
$PAGE->set_heading($attemptobj->get_course()->fullname);
|
||||
$PAGE->activityheader->disable();
|
||||
|
||||
// Summary table start. ============================================================================
|
||||
|
||||
// Work out some time-related things.
|
||||
$attempt = $attemptobj->get_attempt();
|
||||
$quiz = $attemptobj->get_quiz();
|
||||
$overtime = 0;
|
||||
|
||||
if ($attempt->state == quiz_attempt::FINISHED) {
|
||||
if ($timetaken = ($attempt->timefinish - $attempt->timestart)) {
|
||||
if ($quiz->timelimit && $timetaken > ($quiz->timelimit + 60)) {
|
||||
$overtime = $timetaken - $quiz->timelimit;
|
||||
$overtime = format_time($overtime);
|
||||
}
|
||||
$timetaken = format_time($timetaken);
|
||||
} else {
|
||||
$timetaken = "-";
|
||||
}
|
||||
} else {
|
||||
$timetaken = get_string('unfinished', 'quiz');
|
||||
}
|
||||
|
||||
// Prepare summary informat about the whole attempt.
|
||||
$summarydata = [];
|
||||
if (!$attemptobj->get_quiz()->showuserpicture && $attemptobj->get_userid() != $USER->id) {
|
||||
// If showuserpicture is true, the picture is shown elsewhere, so don't repeat it.
|
||||
$student = $DB->get_record('user', ['id' => $attemptobj->get_userid()]);
|
||||
$userpicture = new user_picture($student);
|
||||
$userpicture->courseid = $attemptobj->get_courseid();
|
||||
$summarydata['user'] = [
|
||||
'title' => $userpicture,
|
||||
'content' => new action_link(new moodle_url('/user/view.php', [
|
||||
'id' => $student->id, 'course' => $attemptobj->get_courseid()]),
|
||||
fullname($student, true)),
|
||||
];
|
||||
}
|
||||
|
||||
if ($attemptobj->has_capability('mod/quiz:viewreports')) {
|
||||
$attemptlist = $attemptobj->links_to_other_attempts($attemptobj->review_url(null, $page,
|
||||
$showall));
|
||||
if ($attemptlist) {
|
||||
$summarydata['attemptlist'] = [
|
||||
'title' => get_string('attempts', 'quiz'),
|
||||
'content' => $attemptlist,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Timing information.
|
||||
$summarydata['startedon'] = [
|
||||
'title' => get_string('startedon', 'quiz'),
|
||||
'content' => userdate($attempt->timestart),
|
||||
];
|
||||
|
||||
$summarydata['state'] = [
|
||||
'title' => get_string('attemptstate', 'quiz'),
|
||||
'content' => quiz_attempt::state_name($attempt->state),
|
||||
];
|
||||
|
||||
if ($attempt->state == quiz_attempt::FINISHED) {
|
||||
$summarydata['completedon'] = [
|
||||
'title' => get_string('completedon', 'quiz'),
|
||||
'content' => userdate($attempt->timefinish),
|
||||
];
|
||||
$summarydata['timetaken'] = [
|
||||
'title' => get_string('timetaken', 'quiz'),
|
||||
'content' => $timetaken,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($overtime)) {
|
||||
$summarydata['overdue'] = [
|
||||
'title' => get_string('overdue', 'quiz'),
|
||||
'content' => $overtime,
|
||||
];
|
||||
}
|
||||
|
||||
// Show marks (if the user is allowed to see marks at the moment).
|
||||
$grade = quiz_rescale_grade($attempt->sumgrades, $quiz, false);
|
||||
if ($options->marks >= question_display_options::MARK_AND_MAX && quiz_has_grades($quiz)) {
|
||||
|
||||
if ($attempt->state != quiz_attempt::FINISHED) {
|
||||
// Cannot display grade.
|
||||
|
||||
} else if (is_null($grade)) {
|
||||
$summarydata['grade'] = [
|
||||
'title' => get_string('gradenoun'),
|
||||
'content' => quiz_format_grade($quiz, $grade),
|
||||
];
|
||||
|
||||
} else {
|
||||
// Show raw marks only if they are different from the grade (like on the view page).
|
||||
if ($quiz->grade != $quiz->sumgrades) {
|
||||
$a = new stdClass();
|
||||
$a->grade = quiz_format_grade($quiz, $attempt->sumgrades);
|
||||
$a->maxgrade = quiz_format_grade($quiz, $quiz->sumgrades);
|
||||
$summarydata['marks'] = [
|
||||
'title' => get_string('marks', 'quiz'),
|
||||
'content' => get_string('outofshort', 'quiz', $a),
|
||||
];
|
||||
}
|
||||
|
||||
// Now the scaled grade.
|
||||
$a = new stdClass();
|
||||
$a->grade = html_writer::tag('b', quiz_format_grade($quiz, $grade));
|
||||
$a->maxgrade = quiz_format_grade($quiz, $quiz->grade);
|
||||
if ($quiz->grade != 100) {
|
||||
// Show the percentage using the configured number of decimal places,
|
||||
// but without trailing zeroes.
|
||||
$a->percent = html_writer::tag('b', format_float(
|
||||
$attempt->sumgrades * 100 / $quiz->sumgrades,
|
||||
$quiz->decimalpoints, true, true));
|
||||
$formattedgrade = get_string('outofpercent', 'quiz', $a);
|
||||
} else {
|
||||
$formattedgrade = get_string('outof', 'quiz', $a);
|
||||
}
|
||||
$summarydata['grade'] = [
|
||||
'title' => get_string('gradenoun'),
|
||||
'content' => $formattedgrade,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Any additional summary data from the behaviour.
|
||||
$summarydata = array_merge($summarydata, $attemptobj->get_additional_summary_data($options));
|
||||
|
||||
// Feedback if there is any, and the user is allowed to see it now.
|
||||
$feedback = $attemptobj->get_overall_feedback($grade);
|
||||
if ($options->overallfeedback && $feedback) {
|
||||
$summarydata['feedback'] = [
|
||||
'title' => get_string('feedback', 'quiz'),
|
||||
'content' => $feedback,
|
||||
];
|
||||
}
|
||||
|
||||
// Summary table end. ==============================================================================
|
||||
$summarydata = attempt_summary_information::create_for_attempt($attemptobj, $options, $page, $showall);
|
||||
|
||||
if ($showall) {
|
||||
$slots = $attemptobj->get_slots();
|
||||
|
@ -23,6 +23,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use mod_quiz\output\attempt_summary_information;
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once('locallib.php');
|
||||
@ -75,29 +76,22 @@ if ($attemptobj->is_own_attempt()) {
|
||||
}
|
||||
|
||||
// Prepare summary informat about this question attempt.
|
||||
$summarydata = [];
|
||||
$summary = new attempt_summary_information();
|
||||
|
||||
// Student name.
|
||||
$userpicture = new user_picture($student);
|
||||
$userpicture->courseid = $attemptobj->get_courseid();
|
||||
$summarydata['user'] = [
|
||||
'title' => $userpicture,
|
||||
'content' => new action_link(new moodle_url('/user/view.php', [
|
||||
'id' => $student->id, 'course' => $attemptobj->get_courseid()]),
|
||||
fullname($student, true)),
|
||||
];
|
||||
$summary->add_item('user', $userpicture,
|
||||
new action_link(new moodle_url('/user/view.php', ['id' => $student->id, 'course' => $attemptobj->get_courseid()]),
|
||||
fullname($student, true)));
|
||||
|
||||
// Quiz name.
|
||||
$summarydata['quizname'] = [
|
||||
'title' => get_string('modulename', 'quiz'),
|
||||
'content' => format_string($attemptobj->get_quiz_name()),
|
||||
];
|
||||
$summary->add_item('quizname', get_string('modulename', 'quiz'),
|
||||
format_string($attemptobj->get_quiz_name()));
|
||||
|
||||
// Question name.
|
||||
$summarydata['questionname'] = [
|
||||
'title' => get_string('question', 'quiz'),
|
||||
'content' => $attemptobj->get_question_name($slot),
|
||||
];
|
||||
$summary->add_item('questionname', get_string('question', 'quiz'),
|
||||
format_string($attemptobj->get_question_name($slot)));
|
||||
|
||||
// Other attempts at the quiz.
|
||||
if ($attemptobj->has_capability('mod/quiz:viewreports')) {
|
||||
@ -105,21 +99,15 @@ if ($attemptobj->has_capability('mod/quiz:viewreports')) {
|
||||
$otherattemptsurl->param('slot', $attemptobj->get_original_slot($slot));
|
||||
$attemptlist = $attemptobj->links_to_other_attempts($otherattemptsurl);
|
||||
if ($attemptlist) {
|
||||
$summarydata['attemptlist'] = [
|
||||
'title' => get_string('attempts', 'quiz'),
|
||||
'content' => $attemptlist,
|
||||
];
|
||||
$summary->add_item('attemptlist', get_string('attempts', 'quiz'), $attemptlist);
|
||||
}
|
||||
}
|
||||
|
||||
// Timestamp of this action.
|
||||
$timestamp = $attemptobj->get_question_action_time($slot);
|
||||
if ($timestamp) {
|
||||
$summarydata['timestamp'] = [
|
||||
'title' => get_string('completedon', 'quiz'),
|
||||
'content' => userdate($timestamp),
|
||||
];
|
||||
$summary->add_item('timestamp', get_string('completedon', 'quiz'), userdate($timestamp));
|
||||
}
|
||||
|
||||
echo $output->review_question_page($attemptobj, $slot, $seq,
|
||||
$attemptobj->get_display_options(true), $summarydata);
|
||||
$attemptobj->get_display_options(true), $summary);
|
||||
|
@ -251,24 +251,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#page-mod-quiz-view #page .quizattemptsummary td p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#page-mod-quiz-view table.quizattemptsummary tr.bestrow td {
|
||||
border-color: #bce8f1;
|
||||
background-color: #d9edf7;
|
||||
}
|
||||
|
||||
table.quizattemptsummary .noreviewmessage {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
#page-mod-quiz-view .generaltable.quizattemptsummary {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#page-mod-quiz-view .generalbox#feedback .overriddennotice {
|
||||
text-align: center;
|
||||
font-size: 0.7em;
|
||||
|
45
mod/quiz/templates/attempt_summary_information.mustache
Normal file
45
mod/quiz/templates/attempt_summary_information.mustache
Normal file
@ -0,0 +1,45 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template mod_quiz/attempt_summary_information
|
||||
|
||||
This template renders summary information about a single quiz attempt.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"hasitems": true,
|
||||
"items": [
|
||||
{
|
||||
"title": "<img src='https://qa.moodledemo.net/pluginfile.php/27/user/icon/boost/f2?rev=5522' class='userpicture' width='35' height='35' alt='It is me!'>",
|
||||
"content": "<a href='https://qa.moodledemo.net/user/view.php?id=4&course=2'>Sam Student</a>"
|
||||
},
|
||||
{"title": "State", "content": "Finished"},
|
||||
{"title": "Started on", "content": "Thursday, 23 November 2023, 9:29 AM"},
|
||||
{"title": "Completed on", "content": "Thursday, 23 November 2023, 9:32 AM"},
|
||||
{"title": "Grade", "content": "Not yet graded"}
|
||||
]
|
||||
}
|
||||
}}
|
||||
{{#hasitems}}
|
||||
<table class="generaltable generalbox quizreviewsummary mb-0">
|
||||
<tbody>
|
||||
{{#items}}
|
||||
<tr>
|
||||
<th class="cell" scope="row">{{{title}}}</th>
|
||||
<td class="cell">{{{content}}}</td>
|
||||
</tr>
|
||||
{{/items}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/hasitems}}
|
68
mod/quiz/templates/list_of_attempts.mustache
Normal file
68
mod/quiz/templates/list_of_attempts.mustache
Normal file
@ -0,0 +1,68 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template mod_quiz/list_of_attempts
|
||||
|
||||
This template renders summary information about a list of quiz attempts.
|
||||
|
||||
The structure for each attempt should be what is required by mod_quiz/attempt_summary_information.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"hasattempts": true,
|
||||
"attempts": [
|
||||
{
|
||||
"name": "Attempt 1",
|
||||
"reviewlink": "<a href='https://qa.moodledemo.net/mod/quiz/review.php?attempt=13&cmid=30'>Review</a>",
|
||||
"summarydata": {
|
||||
"hasitems": true,
|
||||
"items": [
|
||||
{
|
||||
"title": "<img src='https://qa.moodledemo.net/pluginfile.php/27/user/icon/boost/f2?rev=5522' class='userpicture' width='35' height='35' alt='It is me!'>",
|
||||
"content": "<a href='https://qa.moodledemo.net/user/view.php?id=4&course=2'>Sam Student</a>"
|
||||
},
|
||||
{"title": "State", "content": "Finished"},
|
||||
{"title": "Started on", "content": "Thursday, 23 November 2023, 9:29 AM"},
|
||||
{"title": "Completed on", "content": "Thursday, 23 November 2023, 9:32 AM"},
|
||||
{"title": "Grade", "content": "Not yet graded"}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
{{#hasattempts}}
|
||||
<h3>{{# str}}summaryofattempts, quiz{{/str}}</h3>
|
||||
|
||||
<ul class="list-unstyled row row-cols-1 row-cols-md-2 no-gutters">
|
||||
|
||||
{{#attempts}}
|
||||
<li class="col pl-0 pr-2 mb-2">
|
||||
<div class="card h-100">
|
||||
<div class="card-header py-2 border-bottom-0">
|
||||
<h4 class="card-title my-0">{{name}}</h4>
|
||||
</div>
|
||||
{{#summarydata}}
|
||||
{{> mod_quiz/attempt_summary_information}}
|
||||
{{/summarydata}}
|
||||
<div class="card-body py-2">
|
||||
<div>{{{reviewlink}}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/attempts}}
|
||||
|
||||
</ul>
|
||||
|
||||
{{/hasattempts}}
|
@ -507,56 +507,4 @@ class attempt_test extends \advanced_testcase {
|
||||
$this->expectExceptionObject(new \moodle_exception('questiondraftonly', 'mod_quiz', '', $question->name));
|
||||
quiz_start_attempt_built_on_last($quba, $newattempt, $attempt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting a new attempt and check the summary previous attempts table.
|
||||
*
|
||||
* @covers ::view_table()
|
||||
*/
|
||||
public function test_view_table(): void {
|
||||
global $PAGE;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$timenow = time();
|
||||
// Create attempt object.
|
||||
$attempt = $this->create_quiz_and_attempt_with_layout('1,1,0');
|
||||
// Finish attempt.
|
||||
$attempt->process_finish($timenow, false);
|
||||
|
||||
$quiz = $attempt->get_quiz();
|
||||
$context = $attempt->get_context();
|
||||
|
||||
// Prepare view object.
|
||||
$viewobj = new view_page();
|
||||
$viewobj->attemptcolumn = true;
|
||||
$viewobj->markcolumn = true;
|
||||
$viewobj->gradecolumn = true;
|
||||
$viewobj->canreviewmine = true;
|
||||
$viewobj->mygrade = 0.00;
|
||||
$viewobj->feedbackcolumn = false;
|
||||
$viewobj->attempts = $attempt;
|
||||
$viewobj->attemptobjs[] = new quiz_attempt($attempt->get_attempt(),
|
||||
$quiz, $attempt->get_cm(), $attempt->get_course(), false);
|
||||
$viewobj->accessmanager = new access_manager($attempt->get_quizobj(), $timenow,
|
||||
has_capability('mod/quiz:ignoretimelimits', $context, null, false));
|
||||
|
||||
// Render summary previous attempts table.
|
||||
$renderer = $PAGE->get_renderer('mod_quiz');
|
||||
$table = $renderer->view_table($quiz, $context, $viewobj);
|
||||
$captionpattern = '/<caption\b[^>]*>' . get_string('summaryofattempts', 'quiz') . '<\/caption>/';
|
||||
|
||||
// Check caption existed.
|
||||
$this->assertMatchesRegularExpression($captionpattern, $table);
|
||||
// Check column attempt.
|
||||
$this->assertMatchesRegularExpression('/<td\b[^>]*>' . $attempt->get_attempt_number() . '<\/td>/', $table);
|
||||
// Check column state.
|
||||
$this->assertMatchesRegularExpression('/<td\b[^>]*>' . ucfirst($attempt->get_state()) . '.+?<\/td>/', $table);
|
||||
// Check column marks.
|
||||
$this->assertMatchesRegularExpression('/<td\b[^>]* c2.+?' .
|
||||
quiz_format_grade($quiz, $attempt->get_sum_marks()) .'<\/td>/', $table);
|
||||
// Check column grades.
|
||||
$this->assertMatchesRegularExpression('/<td\b[^>]* c2.+?0\.00<\/td>/', $table);
|
||||
// Check column review.
|
||||
$this->assertMatchesRegularExpression('/<td\b[^>]*>.+?Review<\/a><\/td>/', $table);
|
||||
}
|
||||
}
|
||||
|
@ -144,15 +144,19 @@ Feature: Attempt a quiz
|
||||
And I should see "Once you submit your answers, you won’t be able to change them." in the "Submit all your answers and finish?" "dialogue"
|
||||
And I should see "Questions without a response: 2" in the "Submit all your answers and finish?" "dialogue"
|
||||
And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue"
|
||||
And I should see "0.00 out of 100.00" in the "Grade" "table_row"
|
||||
And I should see "First question"
|
||||
And I should see "Second question"
|
||||
And I follow "Show one page at a time"
|
||||
And I should see "0.00 out of 100.00" in the "Grade" "table_row"
|
||||
And I should see "First question"
|
||||
And I should not see "Second question"
|
||||
And I follow "Next page"
|
||||
And "Grade" "table_row" should not exist
|
||||
And I should see "Second question"
|
||||
And I should not see "First question"
|
||||
And I follow "Previous page"
|
||||
And I should see "0.00 out of 100.00" in the "Grade" "table_row"
|
||||
And I should see "First question"
|
||||
And I should not see "Second question"
|
||||
|
||||
|
@ -40,7 +40,7 @@ Feature: Preview a quiz as a teacher
|
||||
Then I should see "25.00 out of 100.00"
|
||||
And I should see "v1 (latest)" in the "Question 1" "question"
|
||||
And I follow "Finish review"
|
||||
And "Review" "link" in the "Preview" "table_row" should be visible
|
||||
And "Review" "link" in the "Attempt 1" "list_item" should be visible
|
||||
|
||||
@javascript
|
||||
Scenario: Review the quiz attempt with custom decimal separator
|
||||
@ -53,7 +53,7 @@ Feature: Preview a quiz as a teacher
|
||||
And I should see "25#00 out of 100#00"
|
||||
And I should see "Mark 1#00 out of 1#00"
|
||||
And I follow "Finish review"
|
||||
And "Review" "link" in the "Preview" "table_row" should be visible
|
||||
And "Review" "link" in the "Attempt 1" "list_item" should be visible
|
||||
|
||||
Scenario: Preview the quiz
|
||||
Given I am on the "Quiz 1" "mod_quiz > View" page logged in as "teacher"
|
||||
|
@ -51,5 +51,7 @@ Feature: Several attempts in a quiz
|
||||
Scenario: The redo question buttons are visible after 2 attempts are preset for student1.
|
||||
Given I am on the "Quiz 1" "mod_quiz > View" page logged in as "student1"
|
||||
Then "Re-attempt quiz" "button" should exist
|
||||
And "1" row "Marks / 2.00" column of "quizattemptsummary" table should contain "1.00"
|
||||
And "2" row "Marks / 2.00" column of "quizattemptsummary" table should contain "0.00"
|
||||
And I should see "Finished" in the "Attempt 1" "list_item"
|
||||
And I should see "1.00/2.00" in the "Attempt 1" "list_item"
|
||||
And I should see "Finished" in the "Attempt 2" "list_item"
|
||||
And I should see "0.00/2.00" in the "Attempt 2" "list_item"
|
||||
|
@ -11,6 +11,11 @@ This file describes API changes in the quiz code.
|
||||
unanswered questions.
|
||||
* quiz_settings::no_review_message now takes a new argument $attemptsubmittime for the time when the quiz attempt was
|
||||
submitted. It is strongly recommended that you always pass that.
|
||||
* In the renderer, related to rendering the review page, review_summary_table and filter_review_summary_table have been
|
||||
replaced by review_attempt_summary and filter_review_attempt_summary. This is to support changing the $summarydata argument
|
||||
the review_page() method to an instance of a new templateable class attempt_summary_information for displaying this.
|
||||
The $summarydata argument of review_question_page has also been changed to an attempt_summary_information.
|
||||
* In the renderer, the view_table has been deprecated. Please use the list_of_attempts renderable instead.
|
||||
|
||||
=== 4.3 ===
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
use mod_quiz\access_manager;
|
||||
use mod_quiz\output\list_of_attempts;
|
||||
use mod_quiz\output\renderer;
|
||||
use mod_quiz\output\view_page;
|
||||
use mod_quiz\quiz_attempt;
|
||||
@ -100,6 +101,10 @@ $viewobj->attemptobjs = [];
|
||||
foreach ($attempts as $attempt) {
|
||||
$viewobj->attemptobjs[] = new quiz_attempt($attempt, $quiz, $cm, $course, false);
|
||||
}
|
||||
$viewobj->attemptslist = new list_of_attempts($timenow);
|
||||
foreach (array_reverse($viewobj->attemptobjs) as $attemptobj) {
|
||||
$viewobj->attemptslist->add_attempt($attemptobj);
|
||||
}
|
||||
|
||||
// Work out the final grade, checking whether it was overridden in the gradebook.
|
||||
if (!$canpreview) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user