MDL-85246 mod_quiz: Fix quiz grade feedback

Using $CFG->recovergradesdefault and/or $gradeitem->refresh_grades()
are problematic when viewing a quiz. When reenrolling a user with
previous attempts, a teacher can choose not to recover the user's grades,
even when recovergradesdefault=true. This must be respected when
the user views the quiz. For site with recovergradesdefault=false,
the users always see a grade of 0, which is wrong.

This change shows the correct grade score and feedback to users and
avoids unintentionally recovering the grades.
This commit is contained in:
Rajneel Totaram
2025-07-09 20:30:48 +12:00
parent a6b17d8f6b
commit f99677e996
3 changed files with 112 additions and 18 deletions
@@ -75,6 +75,7 @@ Feature: Attempt a quiz
And I should see "Grade"
And I should see "25.00 out of 100.00"
And I follow "Finish review"
And I should see "Highest grade: 25.00 / 100.00"
And I press "Re-attempt quiz"
@javascript
@@ -0,0 +1,93 @@
@mod @mod_quiz
Feature: Testing view quiz grade feedback with recover grades setting
As a user
I want the quiz grade and completion status to reflect the recover grades setting used when re-enrolling a user
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher | Teacher | One | teacher@example.com |
| student | Student | One | student@example.com |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher | C1 | editingteacher |
| student | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber | completion | completionusegrade |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | 2 | 1 |
And the following "question categories" exist:
| contextlevel | reference | name |
| Activity module | quiz1 | Test questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
And quiz "Quiz 1" contains the following questions:
| question | page |
| TF1 | 1 |
And user "student" has attempted "Quiz 1" with responses:
| slot | response |
| 1 | True |
Scenario Outline: Teachers can view grade feedback and score regardless of recover grades setting
Given the following config values are set as admin:
| recovergradesdefault | <recovergradesetting> |
And user "teacher" has attempted "Quiz 1" with responses:
| slot | response |
| 1 | True |
When I am on the "Quiz 1" "mod_quiz > View" page logged in as "teacher"
# Grade feedback and grade score should be the same. Recover grades setting should not affect the score
Then I should see "Highest grade: 100.00 / 100.00"
And I should see "100.00 out of 100.00" in the "Grade" "table_row"
Examples:
| recovergradesetting |
| 0 |
| 1 |
Scenario Outline: View quiz grade feedback and score with recover grades settings
Given the following config values are set as admin:
| recovergradesdefault | <recovergradesetting> |
When I am on the "Quiz 1" "mod_quiz > View" page logged in as "student"
# Grade feedback and grade score should be the same. Recover grades setting should not affect users who are not unenrolled
Then I should see "Highest grade: 100.00 / 100.00"
And I should see "100.00 out of 100.00" in the "Grade" "table_row"
And I should see "Done: Receive a grade" in the "[data-region='completion-info']" "css_element"
Examples:
| recovergradesetting |
| 0 |
| 1 |
@javascript
Scenario Outline: View quiz after unenrolling and re-enrolling user
Given the following config values are set as admin:
| recovergradesdefault | <recovergradesetting> |
And I log in as "teacher"
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Unenrol" "icon" in the "Student One" "table_row"
And I click on "Unenrol" "button" in the "Unenrol" "dialogue"
And the following "course enrolments" exist:
| user | course | role |
| student | C1 | student |
When I am on the "Quiz 1" "mod_quiz > View" page logged in as "student"
Then I should see "<overallgradefeedback>" in the "[id='feedback']" "css_element"
And I should see "100.00 out of 100.00" in the "Grade" "table_row"
And I should see "<mycompletionstatus>" in the "[data-region='completion-info']" "css_element"
# Re-attempt the quiz
And I press "Re-attempt quiz"
And I should see "First question"
And I click on "True" "radio" in the "First question" "question"
And I click on "Finish attempt ..." "button" in the "region-main" "region"
And I press "Submit all and finish"
And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue"
And I follow "Finish review"
And I should see "Highest grade: 100.00 / 100.00" in the "[id='feedback']" "css_element"
Examples:
| recovergradesetting | overallgradefeedback | mycompletionstatus |
| 0 | Highest grade: Not yet graded / 100.00 | To do: Receive a grade |
| 1 | Highest grade: 100.00 / 100.00 | Done: Receive a grade |
+18 -18
View File
@@ -116,6 +116,7 @@ foreach (array_reverse($viewobj->attemptobjs) as $attemptobj) {
}
// Work out the final grade, checking whether it was overridden in the gradebook.
// First, get an initial grade to display.
if (!$canpreview) {
$mygrade = quiz_get_best_grade($quiz, $USER->id);
} else if ($lastfinishedattempt) {
@@ -126,6 +127,7 @@ if (!$canpreview) {
$mygrade = null;
}
// Now, check the grade in the gradebook, if there is one.
$mygradeoverridden = false;
$gradebookfeedback = '';
@@ -137,25 +139,23 @@ $gradeitem = grade_item::fetch([
'courseid' => $course->id,
]);
if ($gradeitem) {
if ($CFG->recovergradesdefault && $gradeitem->refresh_grades($USER->id)) {
$grade = $gradeitem->get_grade($USER->id, false);
if ($grade->overridden) {
if ($gradeitem->needsupdate) {
// It is Error, but let's be consistent with the old code.
$mygrade = 0;
} else {
$mygrade = $grade->finalgrade;
}
$mygradeoverridden = true;
}
// If there's a grade item grade, then get that grade for this user.
// Users who can preview the quiz (eg teachers) won't have a proper grade,
// so no point getting their grades here.
if (!$canpreview && $gradeitem) {
$grade = $gradeitem->get_grade($USER->id, false);
$mygrade = $grade->finalgrade; // Use this grade to display in the view page.
if (!empty($grade->feedback)) {
$gradebookfeedback = $grade->feedback;
if ($grade->overridden) {
if ($gradeitem->needsupdate) {
// It is Error, but let's be consistent with the old code.
$mygrade = 0;
}
} else {
// It is Error, but let's be consistent with the old code.
$mygrade = 0;
$mygradeoverridden = true;
}
if (!empty($grade->feedback)) {
$gradebookfeedback = $grade->feedback;
}
}
@@ -169,7 +169,7 @@ $PAGE->add_body_class('limitedwidth');
/** @var renderer $output */
$output = $PAGE->get_renderer('mod_quiz');
// Print table with existing attempts.
// Print overall stats and table with existing attempts.
if ($attempts) {
// Work out which columns we need, taking account what data is available in each attempt.
list($someoptions, $alloptions) = quiz_get_combined_reviewoptions($quiz, $attempts);