diff --git a/mod/quiz/classes/output/attempt_summary_information.php b/mod/quiz/classes/output/attempt_summary_information.php
index 20ddd630e95..6ca0a36d94e 100644
--- a/mod/quiz/classes/output/attempt_summary_information.php
+++ b/mod/quiz/classes/output/attempt_summary_information.php
@@ -17,6 +17,7 @@
namespace mod_quiz\output;
use action_link;
+use core\output\named_templatable;
use html_writer;
use mod_quiz\quiz_attempt;
use moodle_url;
@@ -25,7 +26,6 @@ use question_display_options;
use renderable;
use renderer_base;
use stdClass;
-use templatable;
use user_picture;
/**
@@ -34,12 +34,13 @@ use user_picture;
* 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, templatable {
+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 = [];
@@ -90,7 +91,8 @@ class attempt_summary_information implements renderable, templatable {
*
* @param quiz_attempt $attemptobj the attempt to summarise.
* @param display_options $options options for what can be seen.
- * @param int $page if specified, the URL of this particular page of the attempt, otherwise
+ * @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.
@@ -99,7 +101,7 @@ class attempt_summary_information implements renderable, templatable {
public static function create_for_attempt(
quiz_attempt $attemptobj,
display_options $options,
- int $page = -1,
+ ?int $pageforlinkingtootherattempts = null,
?bool $showall = null,
): static {
global $DB, $USER;
@@ -119,9 +121,9 @@ class attempt_summary_information implements renderable, templatable {
);
}
- if ($attemptobj->has_capability('mod/quiz:viewreports')) {
+ if ($pageforlinkingtootherattempts !== null && $attemptobj->has_capability('mod/quiz:viewreports')) {
$attemptlist = $attemptobj->links_to_other_attempts(
- $attemptobj->review_url(null, $page, $showall));
+ $attemptobj->review_url(null, $pageforlinkingtootherattempts, $showall));
if ($attemptlist) {
$summary->add_item('attemptlist', get_string('attempts', 'quiz'), $attemptlist);
}
@@ -239,4 +241,10 @@ class attempt_summary_information implements renderable, templatable {
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';
+ }
}
diff --git a/mod/quiz/classes/output/list_of_attempts.php b/mod/quiz/classes/output/list_of_attempts.php
new file mode 100644
index 00000000000..c1ae66e14e5
--- /dev/null
+++ b/mod/quiz/classes/output/list_of_attempts.php
@@ -0,0 +1,85 @@
+.
+
+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';
+ }
+}
diff --git a/mod/quiz/classes/output/renderer.php b/mod/quiz/classes/output/renderer.php
index 9e9b5d4be2e..070b8e330ee 100644
--- a/mod/quiz/classes/output/renderer.php
+++ b/mod/quiz/classes/output/renderer.php
@@ -889,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;
}
@@ -1115,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 '';
}
diff --git a/mod/quiz/classes/output/view_page.php b/mod/quiz/classes/output/view_page.php
index bc967837efa..a1e9ed55b39 100644
--- a/mod/quiz/classes/output/view_page.php
+++ b/mod/quiz/classes/output/view_page.php
@@ -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
diff --git a/mod/quiz/classes/quiz_attempt.php b/mod/quiz/classes/quiz_attempt.php
index 1ec18b5522a..d6b5479437a 100644
--- a/mod/quiz/classes/quiz_attempt.php
+++ b/mod/quiz/classes/quiz_attempt.php
@@ -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);
}
diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php
index b18d7349bed..df4f3ef522e 100644
--- a/mod/quiz/lang/en/quiz.php
+++ b/mod/quiz/lang/en/quiz.php
@@ -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.
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.';
diff --git a/mod/quiz/styles.css b/mod/quiz/styles.css
index 18da3255922..12853b40a4b 100644
--- a/mod/quiz/styles.css
+++ b/mod/quiz/styles.css
@@ -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;
diff --git a/mod/quiz/templates/list_of_attempts.mustache b/mod/quiz/templates/list_of_attempts.mustache
new file mode 100644
index 00000000000..17ccfab225b
--- /dev/null
+++ b/mod/quiz/templates/list_of_attempts.mustache
@@ -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 ",
+ "content": "Sam Student"
+ },
+ {"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}}
+