Skip to content

Commit

Permalink
MDL-76897 quiz: quiz_set_grade -> update_quiz_maximum_grade
Browse files Browse the repository at this point in the history
  • Loading branch information
timhunt committed Feb 27, 2023
1 parent c212565 commit ff3f4eb
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 85 deletions.
4 changes: 2 additions & 2 deletions grade/tests/report_graderlib_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

use grade_plugin_return;
use grade_report_grader;
use mod_quiz\quiz_settings;

defined('MOODLE_INTERNAL') || die();

Expand Down Expand Up @@ -478,8 +479,7 @@ public function test_get_right_rows() {

// Set the grade for the second one to 0 (note, you have to do this after creating it,
// otherwise it doesn't create an ungraded grade item).
$ungradedquiz->instance = $ungradedquiz->id;
quiz_set_grade(0, $ungradedquiz);
quiz_settings::create($ungradedquiz->id)->get_grade_calculator()->update_quiz_maximum_grade(0);

// Set current user.
$this->setUser($manager);
Expand Down
81 changes: 78 additions & 3 deletions mod/quiz/classes/grade_calculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

namespace mod_quiz;

use mod_quiz\event\quiz_grade_updated;
use question_engine_data_mapper;
use stdClass;

/**
* This class contains all the logic for computing the grade of a quiz.
Expand Down Expand Up @@ -64,7 +66,7 @@ public static function create(quiz_settings $quizobj): grade_calculator {
*
* You should call {@see quiz_delete_previews()} before you call this function.
*/
public function recompute_quiz_sumgrades() {
public function recompute_quiz_sumgrades(): void {
global $DB;
$quiz = $this->quizobj->get_quiz();

Expand All @@ -86,14 +88,14 @@ public function recompute_quiz_sumgrades() {
// If the quiz has been attempted, and the sumgrades has been
// set to 0, then we must also set the maximum possible grade to 0, or
// we will get a divide by zero error.
quiz_set_grade(0, $quiz);
self::update_quiz_maximum_grade(0);
}
}

/**
* Update the sumgrades field of attempts at this quiz.
*/
public function recompute_all_attempt_sumgrades() {
public function recompute_all_attempt_sumgrades(): void {
global $DB;
$dm = new question_engine_data_mapper();
$timenow = time();
Expand Down Expand Up @@ -260,4 +262,77 @@ public function recompute_all_final_grades(): void {
array_merge([$quiz->id], $params));
}
}

/**
* Update the quiz setting for the grade the quiz is out of.
*
* This function will update the data in quiz_grades and quiz_feedback, and
* pass the new grades on to the gradebook.
*
* @param float $newgrade the new maximum grade for the quiz.
*/
public function update_quiz_maximum_grade(float $newgrade): void {
global $DB;
$quiz = $this->quizobj->get_quiz();

// This is potentially expensive, so only do it if necessary.
if (abs($quiz->grade - $newgrade) < self::ALMOST_ZERO) {
// Nothing to do.
return;
}

// Use a transaction.
$transaction = $DB->start_delegated_transaction();

// Update the quiz table.
$oldgrade = $quiz->grade;
$quiz->grade = $newgrade;
$timemodified = time();
$DB->update_record('quiz', (object) [
'id' => $quiz->id,
'grade' => $newgrade,
'timemodified' => $timemodified,
]);

// Rescale the grade of all quiz attempts.
if ($oldgrade < $newgrade) {
// The new total is bigger, so we need to recompute fully to avoid underflow problems.
$this->recompute_all_final_grades();

} else {
// New total smaller, so we can rescale the grades efficiently.
$DB->execute("
UPDATE {quiz_grades}
SET grade = ? * grade, timemodified = ?
WHERE quiz = ?
", [$newgrade / $oldgrade, $timemodified, $quiz->id]);
}

// Rescale the overall feedback boundaries.
if ($oldgrade > self::ALMOST_ZERO) {
// Update the quiz_feedback table.
$factor = $newgrade / $oldgrade;
$DB->execute("
UPDATE {quiz_feedback}
SET mingrade = ? * mingrade, maxgrade = ? * maxgrade
WHERE quizid = ?
", [$factor, $factor, $quiz->id]);
}

// Update grade item and send all grades to gradebook.
quiz_grade_item_update($quiz);
quiz_update_grades($quiz);

// Log quiz grade updated event.
quiz_grade_updated::create([
'context' => $this->quizobj->get_context(),
'objectid' => $quiz->id,
'other' => [
'oldgrade' => $oldgrade + 0, // Remove trailing 0s.
'newgrade' => $newgrade,
]
])->trigger();

$transaction->allow_commit();
}
}
22 changes: 22 additions & 0 deletions mod/quiz/deprecatedlib.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,25 @@ function quiz_update_all_final_grades($quiz) {
'Please use a standard grade_calculator::recompute_all_final_grades instead.', DEBUG_DEVELOPER);
quiz_settings::create($quiz->id)->get_grade_calculator()->recompute_all_final_grades();
}

/**
* The quiz grade is the maximum that student's results are marked out of. When it
* changes, the corresponding data in quiz_grades and quiz_feedback needs to be
* rescaled. After calling this function, you probably need to call
* quiz_update_all_attempt_sumgrades, grade_calculator::recompute_all_final_grades();
* quiz_update_grades. (At least, that is what this comment has said for years, but
* it seems to call recompute_all_final_grades itself.)
*
* @param float $newgrade the new maximum grade for the quiz.
* @param stdClass $quiz the quiz we are updating. Passed by reference so its
* grade field can be updated too.
* @return bool indicating success or failure.
* @deprecated since Moodle 4.2. Please use grade_calculator::update_quiz_maximum_grade.
* @todo MDL-76612 Final deprecation in Moodle 4.6
*/
function quiz_set_grade($newgrade, $quiz) {
debugging('quiz_set_grade is deprecated. ' .
'Please use a standard grade_calculator::update_quiz_maximum_grade instead.', DEBUG_DEVELOPER);
quiz_settings::create($quiz->id)->get_grade_calculator()->update_quiz_maximum_grade($newgrade);
return true;
}
4 changes: 1 addition & 3 deletions mod/quiz/edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,7 @@
// If rescaling is required save the new maximum.
$maxgrade = unformat_float(optional_param('maxgrade', '', PARAM_RAW_TRIMMED), true);
if (is_float($maxgrade) && $maxgrade >= 0) {
quiz_set_grade($maxgrade, $quiz);
$gradecalculator->recompute_all_final_grades();
quiz_update_grades($quiz, 0, true);
$gradecalculator->update_quiz_maximum_grade($maxgrade);
}

redirect($afteractionurl);
Expand Down
77 changes: 0 additions & 77 deletions mod/quiz/locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -629,83 +629,6 @@ function quiz_has_feedback($quiz) {
return $cache[$quiz->id];
}

/**
* The quiz grade is the maximum that student's results are marked out of. When it
* changes, the corresponding data in quiz_grades and quiz_feedback needs to be
* rescaled. After calling this function, you probably need to call
* quiz_update_all_attempt_sumgrades, grade_calculator::recompute_all_final_grades();
* quiz_update_grades. (At least, that is what this comment has said for years, but
* it seems to call recompute_all_final_grades itself.)
*
* @param float $newgrade the new maximum grade for the quiz.
* @param stdClass $quiz the quiz we are updating. Passed by reference so its
* grade field can be updated too.
* @return bool indicating success or failure.
*/
function quiz_set_grade($newgrade, $quiz) {
global $DB;
// This is potentially expensive, so only do it if necessary.
if (abs($quiz->grade - $newgrade) < 1e-7) {
// Nothing to do.
return true;
}

$oldgrade = $quiz->grade;
$quiz->grade = $newgrade;

// Use a transaction, so that on those databases that support it, this is safer.
$transaction = $DB->start_delegated_transaction();

// Update the quiz table.
$DB->set_field('quiz', 'grade', $newgrade, ['id' => $quiz->instance]);

if ($oldgrade < 1) {
// If the old grade was zero, we cannot rescale, we have to recompute.
// We also recompute if the old grade was too small to avoid underflow problems.
$gradecalculator = quiz_settings::create($quiz->id)->get_grade_calculator();
$gradecalculator->recompute_all_final_grades($quiz);

} else {
// We can rescale the grades efficiently.
$timemodified = time();
$DB->execute("
UPDATE {quiz_grades}
SET grade = ? * grade, timemodified = ?
WHERE quiz = ?
", [$newgrade / $oldgrade, $timemodified, $quiz->id]);
}

if ($oldgrade > 1e-7) {
// Update the quiz_feedback table.
$factor = $newgrade/$oldgrade;
$DB->execute("
UPDATE {quiz_feedback}
SET mingrade = ? * mingrade, maxgrade = ? * maxgrade
WHERE quizid = ?
", [$factor, $factor, $quiz->id]);
}

// Update grade item and send all grades to gradebook.
quiz_grade_item_update($quiz);
quiz_update_grades($quiz);

$transaction->allow_commit();

// Log quiz grade updated event.
// We use $num + 0 as a trick to remove the useless 0 digits from decimals.
$cm = get_coursemodule_from_instance('quiz', $quiz->id);
$event = \mod_quiz\event\quiz_grade_updated::create([
'context' => \context_module::instance($cm->id),
'objectid' => $quiz->id,
'other' => [
'oldgrade' => $oldgrade + 0,
'newgrade' => $newgrade + 0
]
]);
$event->trigger();
return true;
}

/**
* Save the overall grade for a user at a quiz in the quiz_grades table
*
Expand Down
1 change: 1 addition & 0 deletions mod/quiz/upgrade.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ This files describes API changes in the quiz code.
- quiz_update_sumgrades -> recompute_quiz_sumgrades
- quiz_update_all_attempt_sumgrades -> recompute_all_attempt_sumgrades
- quiz_update_all_final_grades -> recompute_all_final_grades
- quiz_set_grade -> update_quiz_maximum_grade

* Final deprecation (complete removal) of the following functions which were deprecated long ago:
- quiz_groups_member_added_handler - deprecated since 2.6
Expand Down

0 comments on commit ff3f4eb

Please sign in to comment.