diff --git a/mod/quiz/classes/output/attempt_summary_information.php b/mod/quiz/classes/output/attempt_summary_information.php index 9da72232bf8..5d807ad9039 100644 --- a/mod/quiz/classes/output/attempt_summary_information.php +++ b/mod/quiz/classes/output/attempt_summary_information.php @@ -19,6 +19,7 @@ namespace mod_quiz\output; use action_link; use core\output\named_templatable; use html_writer; +use mod_quiz\output\grades\grade_out_of; use mod_quiz\quiz_attempt; use moodle_url; use mod_quiz\question\display_options; @@ -178,29 +179,14 @@ class attempt_summary_information implements renderable, named_templatable { } 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)); + new grade_out_of($quiz, $attempt->sumgrades, $quiz->sumgrades, grade_out_of::SHORT)); } // 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); + new grade_out_of($quiz, $grade, $quiz->grade, + $quiz->grade == 100 ? grade_out_of::NORMAL : grade_out_of::WITH_PERCENT)); } } @@ -243,7 +229,7 @@ class attempt_summary_information implements renderable, named_templatable { return $templatecontext; } - public function get_template_name(\renderer_base $renderer): string { + 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/grades/grade_out_of.php b/mod/quiz/classes/output/grades/grade_out_of.php new file mode 100644 index 00000000000..332554440c0 --- /dev/null +++ b/mod/quiz/classes/output/grades/grade_out_of.php @@ -0,0 +1,108 @@ +. + +namespace mod_quiz\output\grades; + +use html_writer; +use renderable; +use stdClass; + +/** + * Represents a grade out of a give total, that wants to be output in a particular way. + * + * @package mod_quiz + * @category output + * @copyright 2024 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class grade_out_of implements renderable { + /** @var string Indicates we want a short rendering. Also the lang string used. */ + const SHORT = 'outofshort'; + + /** @var string Indicates we want the default rendering. Also the lang string used. */ + const NORMAL = 'outof'; + + /** @var string like normal, but with the percent equivalent in brackets. Also the lang string used */ + const WITH_PERCENT = 'outofpercent'; + + /** + * Constructor. + * + * @param stdClass $quiz Quiz settings. + * @param float $grade the mark to show. + * @param float $maxgrade the total to show it out of. + * @param string $style which format to use, grade_out_of::NORMAL, ::SHORT or ::WITH_PERCENT. + */ + public function __construct( + + /** @var stdClass Quiz settings (so we can access the settings like decimal places). */ + public readonly stdClass $quiz, + + /** @var float the grade to show. */ + public float $grade, + + /** @var float the total the grade is out of. */ + public float $maxgrade, + + /** @var string The display style, one of the consts above. */ + public readonly string $style = self::NORMAL, + + ) { + } + + /** + * Get the lang string to use to display the grade in the requested style. + * + * @return string lang string key from the mod_quiz lang pack. + */ + public function get_string_key(): string { + return $this->style; + } + + /** + * Get the formatted values to be inserted into the {@see get_string_key()} string placeholders. + * + * Values are not styled. To apply the recommended styling, call {@see style_formatted_values()} + * + * @return stdClass to be passed as the third argument to get_string(). + */ + public function get_formatted_values(): stdClass { + $a = new stdClass(); + $a->grade = quiz_format_grade($this->quiz, $this->grade); + $a->maxgrade = quiz_format_grade($this->quiz, $this->maxgrade); + if ($this->style === self::WITH_PERCENT) { + $a->percent = format_float($this->grade * 100 / $this->maxgrade, + $this->quiz->decimalpoints, true, true); + } + return $a; + } + + /** + * Apply the normal styling to the values returned by {@see get_formatted_values()}. + * + * @param stdClass $a formatted values, as returned by get_formatted_values. + * @return stdClass same structure, with some values wrapped in <b> tags. + */ + public function style_formatted_values(stdClass $a): stdClass { + if ($this->style !== self::SHORT) { + $a->grade = html_writer::tag('b', $a->grade); + } + if ($this->style === self::WITH_PERCENT) { + $a->percent = html_writer::tag('b', $a->percent); + } + return $a; + } +} diff --git a/mod/quiz/classes/output/renderer.php b/mod/quiz/classes/output/renderer.php index 070b8e330ee..347a19f8530 100644 --- a/mod/quiz/classes/output/renderer.php +++ b/mod/quiz/classes/output/renderer.php @@ -25,6 +25,7 @@ use html_table_cell; use html_writer; use mod_quiz\access_manager; use mod_quiz\form\preflight_check_form; +use mod_quiz\output\grades\grade_out_of; use mod_quiz\question\display_options; use mod_quiz\quiz_attempt; use moodle_url; @@ -465,6 +466,20 @@ class renderer extends plugin_renderer_base { return implode(', ', $attemptlinks); } + /** + * Render a {@see grade_out_of}. + * + * Most of the logic is in methods of the grade_out_of class. However, + * having this renderer method allows themes to override the default rendering. + * + * @param grade_out_of $grade + * @return string HTML to output. + */ + protected function render_grade_out_of(grade_out_of $grade): string { + return get_string($grade->get_string_key(), 'quiz', + $grade->style_formatted_values($grade->get_formatted_values())); + } + /** * Render the 'start attempt' page. * diff --git a/mod/quiz/index.php b/mod/quiz/index.php index 45372c8463d..640bddaf3a9 100644 --- a/mod/quiz/index.php +++ b/mod/quiz/index.php @@ -22,6 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use mod_quiz\output\grades\grade_out_of; require_once("../../config.php"); require_once("locallib.php"); @@ -161,10 +162,8 @@ foreach ($quizzes as $quiz) { $feedback = ''; if ($quiz->grade && array_key_exists($quiz->id, $grades)) { if ($alloptions->marks >= question_display_options::MARK_AND_MAX) { - $a = new stdClass(); - $a->grade = quiz_format_grade($quiz, $grades[$quiz->id]); - $a->maxgrade = quiz_format_grade($quiz, $quiz->grade); - $grade = get_string('outofshort', 'quiz', $a); + $grade = $OUTPUT->render(new grade_out_of( + $grades[$quiz->id], $quiz->grade, $quiz->sumgrades, style: grade_out_of::SHORT)); } if ($alloptions->overallfeedback) { $feedback = quiz_feedback_for_grade($grades[$quiz->id], $quiz, $context); diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt index f5ce83ccf23..4ea8641abf6 100644 --- a/mod/quiz/upgrade.txt +++ b/mod/quiz/upgrade.txt @@ -28,6 +28,7 @@ This file describes API changes in the quiz code. - override_manager::delete_override_by_id - override_manager::delete_overrides - override_manager::delete_all_overrides +* There is a new renderable grade_out_of to help with display a nicely formatted "42.00 out of 100.00" with a few variants. === 4.3 ===