Merge branch 'MDL-85012-main' of https://github.com/mihailges/moodle

This commit is contained in:
Jun Pataleta 2025-04-07 12:06:13 +08:00
commit 1d258a2148
No known key found for this signature in database
GPG Key ID: F83510526D99E2C7
15 changed files with 72 additions and 212 deletions

View File

@ -241,11 +241,6 @@ if (has_capability('moodle/grade:manage', $systemcontext)
new lang_string('gradepenalty_enabledmodules', 'grades'),
new lang_string('gradepenalty_enabledmodules_help', 'grades'), [], $options));
// Option to apply penalty to overridden grades.
$temp->add(new admin_setting_configcheckbox('gradepenalty_overriddengrade',
new lang_string('gradepenalty_overriddengrade', 'grades'),
new lang_string('gradepenalty_overriddengrade_help', 'grades'), 0));
$ADMIN->add('gradepenalty', $temp);
}

View File

@ -23,8 +23,6 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['applypenaltytext'] = 'Penalty exemption';
$string['applypenaltytooltip'] = 'If the box is checked, penalty will not be applied to the overridden grade.';
$string['aria:dropdowncolumns'] = 'Collapsed columns found';
$string['clearsearch'] = 'Clear searched users';
$string['collapsedcolumns'] = 'Collapsed columns <span class="badge rounded-pill bg-primary text-white ms-1" data-collapse="count">{$a}</span>';

View File

@ -243,29 +243,17 @@ class grade_report_grader extends grade_report {
continue;
}
// Detect changes in exemption checkbox.
if ($oldvalue->can_apply_penalty_to_overridden_mark()) {
if (!isset($data->exemption[$userid][$itemid])) {
$newvalue = format_float($postedvalue - $oldvalue->deductedmark,
$oldvalue->grade_item->get_decimals());
} else {
$newvalue = $postedvalue;
}
} else {
$newvalue = $postedvalue;
}
// If the grade item uses a custom scale
if (!empty($oldvalue->grade_item->scaleid)) {
if ((int)$oldvalue->finalgrade === (int)$newvalue) {
if ((int)$oldvalue->finalgrade === (int)$postedvalue) {
continue;
}
} else {
// The grade item uses a numeric scale
// Format the finalgrade from the DB so that it matches the grade from the client
if ($newvalue === format_float($oldvalue->finalgrade, $oldvalue->grade_item->get_decimals())) {
if ($postedvalue === format_float($oldvalue->finalgrade, $oldvalue->grade_item->get_decimals())) {
continue;
}
}
@ -340,19 +328,8 @@ class grade_report_grader extends grade_report {
}
}
// Save final grade, without penalty.
$gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', false,
FORMAT_MOODLE, null, null, true);
// Save overridden mark, without penalty.
$gradeitem->update_overridden_mark($userid, $finalgrade);
// Apply penalty.
if ($oldvalue->can_apply_penalty_to_overridden_mark() && !isset($data->exemption[$userid][$itemid])) {
// Apply penalty.
$gradeitem->update_final_grade($userid, $newvalue, 'gradepenalty', false,
FORMAT_MOODLE, null, null, true);
}
}
}
}
@ -1162,26 +1139,6 @@ class grade_report_grader extends grade_report {
if ($context->statusicons) {
$context->extraclasses .= ' statusicons';
}
// Show option for user to apply penalty or not.
if ($grade->can_apply_penalty_to_overridden_mark()) {
$context->canapplypenalty = true;
if ($grade->is_penalty_applied_to_final_grade()) {
// We are editing the original grade value, ie, before applying penalty.
$context->value = format_float($gradeval + $grade->deductedmark, $decimalpoints);
} else {
$context->value = $value;
}
// Current grade.
$context->effectivegrade = $value;
$context->deductedmark = format_float($grade->deductedmark, $decimalpoints);
$context->penaltyexempt = !$grade->is_penalty_applied_to_final_grade();
$context->exemptionid = 'exemption' . $userid . '_' . $item->id;
$context->exemptionname = 'exemption[' . $userid . '][' . $item->id .']';
$context->exemptionlabel = $gradelabel . ' ' .
get_string('applypenaltytext', 'gradereport_grader');
$context->exemptiontooltip = get_string('applypenaltytooltip', 'gradereport_grader');
}
} else {
$context->extraclasses = 'gradevalue' . $hidden . $gradepass;
$context->text = format_float($gradeval, $decimalpoints);

View File

@ -30,11 +30,7 @@
"extraclasses": "statusicons",
"value": "Text information",
"tabindex": "1",
"name": "grade[313][624]",
"canapplypenalty": "false",
"exemptionname": "exemption[3][2]",
"exemptionid": "exemption3_2",
"exemptionlabel": "Exempt penalty"
"name": "grade[313][624]"
}
}}
<div class="d-flex flex-column h-100" data-collapse="content">
@ -45,12 +41,7 @@
{{>core_grades/grades/grader/scale}}
{{/scale}}
{{^scale}}
{{#canapplypenalty}}
{{>core_grades/grades/grader/overridden_with_penalty}}
{{/canapplypenalty}}
{{^canapplypenalty}}
{{>core_grades/grades/grader/input}}
{{/canapplypenalty}}
{{>core_grades/grades/grader/input}}
{{/scale}}
{{/iseditable}}
{{^iseditable}}

View File

@ -104,13 +104,4 @@ class behat_gradereport_grader extends behat_base {
),
];
}
/**
* Enable penalty for overridden grade.
*
* @Given I enable grade penalties for overridden grades
*/
public function i_enable_grade_penalties_for_overridden_grades(): void {
set_config('gradepenalty_overriddengrade', 1);
}
}

View File

@ -1,11 +1,11 @@
@gradereport @gradereport_grader @gradereport_grader_deduction
@gradereport @gradereport_grader @gradereport_grade_override
Feature: As a teacher, I want to override a grade with a deduction and check the gradebook.
The deducted mark should not affect the overridden grade.
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And I enable grade penalties for overridden grades
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
@ -33,19 +33,16 @@ Feature: As a teacher, I want to override a grade with a deduction and check the
And I turn editing mode on
And I set the following fields to these values:
| Student 1 Manual grade 01 grade | 80 |
| Student 1 Manual grade 01 Penalty exemption | 0 |
| Student 1 Manual grade 02 Penalty exemption | 1 |
And I click on "Save changes" "button"
When I turn editing mode off
Then the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- |
| Student 1 | student1@example.com | 70 | 100 | 170 |
| -1- | -2- | -3- | -4- | -5- |
| Student 1 | student1@example.com | 80 | 80 | 160 |
When I turn editing mode on
And I set the following fields to these values:
| Student 1 Manual grade 02 grade | 100 |
| Student 1 Manual grade 02 Penalty exemption | 0 |
And I click on "Save changes" "button"
And I turn editing mode off
Then the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- |
| Student 1 | student1@example.com | 70 | 80 | 150 |
| Student 1 | student1@example.com | 80 | 100 | 180 |

View File

@ -1,54 +0,0 @@
{{!
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 core_grades/grades/grader/overridden_with_penalty
Input template for grader report cell.
Example context (json):
{
"deductedmark": "30.00",
"effectivegrade": "70.00",
"exemptionid": "exemption3_2",
"exemptionlabel": "Exempt penalty",
"exemptiontooltip": "Exempt penalty",
"exemptionname": "exemption[3][2]",
"penaltyexempt": "true",
"id": "grade_313_624",
"label": "grade_313_624",
"name": "grade[313][624]"
}
}}
<div class="container">
<div class="row">
<div class="me-0">{{#str}}originalgrade, core_grades{{/str}}: </div>
{{>core_grades/grades/grader/input}}
</div>
<div class="row">
<div class="me-0">{{#str}}currentgrade, core_grades{{/str}}: {{{effectivegrade}}}</div>
</div>
<div class="row">
<div class="me-0" title="{{exemptiontooltip}}">{{#str}}exemptpenalty, core_grades{{/str}}:</div>
<label for="{{exemptionid}}" class="accesshide">{{{exemptionlabel}}}</label>
<input type="checkbox" name="{{exemptionname}}"
value="{{deductedmark}}" id="{{exemptionid}}"
{{#penaltyexempt}}checked{{/penaltyexempt}}>
</div>
</div>

View File

@ -213,7 +213,6 @@ $string['eventscaleupdated'] = 'Scale updated';
$string['eventusergraded'] = 'User graded';
$string['excluded'] = 'Excluded';
$string['excluded_help'] = 'If ticked, the grade will not be included in any aggregation.';
$string['exemptpenalty'] = 'Exempt penalty';
$string['expand'] = 'Expand category';
$string['expandcriterion'] = 'Expand criterion';
$string['export'] = 'Export';
@ -337,8 +336,6 @@ $string['gradepenalty'] = 'Grade penalties';
$string['gradepenalty_enabledmodules'] = 'Enabled activities';
$string['gradepenalty_enabledmodules_help'] = 'Enable grade penalties for the selected activities.';
$string['gradepenalty_indicator_info'] = 'Late penalty applied -{$a} marks';
$string['gradepenalty_overriddengrade'] = 'Apply penalty to overridden grades';
$string['gradepenalty_overriddengrade_help'] = 'If enabled, the penalty will be applied to overridden grades.';
$string['gradepenalty_supportedplugins'] = 'Manage supported activities';
$string['gradepointdefault'] = 'Grade point default';
$string['gradepointdefault_help'] = 'This setting determines the default value for the grade point value available in a grade item.';

View File

@ -2061,7 +2061,6 @@
<FIELD NAME="aggregationstatus" TYPE="char" LENGTH="10" NOTNULL="true" DEFAULT="unknown" SEQUENCE="false" COMMENT="One of several values describing how this grade_grade was used when calculating the aggregation. Possible values are &quot;unknown&quot;, &quot;dropped&quot;, &quot;novalue&quot;, &quot;used&quot;"/>
<FIELD NAME="aggregationweight" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="If the aggregationstatus == 'included', then this is the percent this item contributed to the aggregation."/>
<FIELD NAME="deductedmark" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The mark deducted from final grade"/>
<FIELD NAME="overriddenmark" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The overridden mark before applied penalty"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>

View File

@ -1669,5 +1669,17 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2025040100.01);
}
// Remove overriddenmark field from grade_grades.
if ($oldversion < 2025040700.00) {
$table = new xmldb_table('grade_grades');
$field = new xmldb_field('overriddenmark');
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2025040700.00);
}
return true;
}

View File

@ -50,7 +50,7 @@ class grade_grade extends grade_object {
public $required_fields = array('id', 'itemid', 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 'locked',
'locktime', 'exported', 'overridden', 'excluded', 'timecreated',
'timemodified', 'aggregationstatus', 'aggregationweight', 'deductedmark', 'overriddenmark');
'timemodified', 'aggregationstatus', 'aggregationweight', 'deductedmark');
/**
* Array of optional fields with default values (these should match db defaults)
@ -221,9 +221,6 @@ class grade_grade extends grade_object {
/** @var float $deductedmark mark deducted from final grade */
public float $deductedmark = 0;
/** @var float $overriddenmark mark overridden by teacher */
public float $overriddenmark = 0;
/**
* Returns array of grades for given grade_item+users
*
@ -1291,44 +1288,16 @@ class grade_grade extends grade_object {
return $this->grade_item->get_context();
}
/**
* Determine if penalty is applied to this overridden mark.
*
* @return bool whether penalty is applied
*/
public function can_apply_penalty_to_overridden_mark(): bool {
// Check config.
if (!get_config('core', 'gradepenalty_overriddengrade')) {
return false;
}
// Check if the raw grade was deducted.
if ($this->deductedmark <= 0) {
return false;
}
return true;
}
/**
* Whether the penalty is applied to this overridden mark.
*
* @return bool whether penalty is applied
*/
public function is_penalty_applied_to_overridden_mark(): bool {
return $this->overridden > 0 && $this->overriddenmark > $this->finalgrade;
}
/**
* Whether the penalty is applied to this final grade.
*
* @return bool whether penalty is applied
*/
public function is_penalty_applied_to_final_grade(): bool {
if ($this->overridden > 0) {
return $this->is_penalty_applied_to_overridden_mark();
} else {
return $this->deductedmark > 0;
// Return false if the grade is overridden.
if ($this->is_overridden()) {
return false;
}
return $this->deductedmark > 0;
}
}

View File

@ -2086,11 +2086,10 @@ class grade_item extends grade_object {
}
// end of hack alert
// Default deduction is 0.
$grade->deductedmark = 0;
// Default original overridden mark is 0.
$grade->overriddenmark = 0;
// Only reset the deducted mark if the grade has changed.
if ($grade->timemodified !== $oldgrade->timemodified) {
$grade->deductedmark = 0;
}
$gradechanged = false;
if (empty($grade->id)) {
@ -2167,21 +2166,6 @@ class grade_item extends grade_object {
$grade->update();
}
/**
* Update overridden mark for given user
*
* @param int $userid The graded user
* @param float $overriddenmark The mark deducted from final grade
*/
public function update_overridden_mark(int $userid, float $overriddenmark): void {
$grade = new grade_grade([
'itemid' => $this->id,
'userid' => $userid,
]);
$grade->overriddenmark = $overriddenmark;
$grade->update();
}
/**
* Calculates final grade values using the formula in the calculation property.
* The parameters are taken from final grades of grade items in current course only.

View File

@ -2075,16 +2075,10 @@ class assign {
// Add penalty indicator, icon only.
$penaltyindicator = '';
if ($deductedmark > 0) {
$gradeitem = $this->get_grade_item();
$ispenaltyapplied = $gradeitem && $gradeitem->get_grade($userid)->is_penalty_applied_to_final_grade();
// If the user is set, we need to check if the penalty is applied to overridden grade.
if ($userid == 0 || $ispenaltyapplied) {
$usergrade = new \grade_grade();
$usergrade->deductedmark = $deductedmark;
$indicator = new \core_grades\output\penalty_indicator(2, $usergrade);
$renderer = $PAGE->get_renderer('core_grades');
$penaltyindicator = $renderer->render_penalty_indicator($indicator);
}
$gradegrade = new \grade_grade();
$gradegrade->deductedmark = $deductedmark;
$gradegrade->overridden = $userid > 0 ? $this->get_grade_item()->get_grade($userid)->overridden : 0;
$penaltyindicator = \core_grades\penalty_manager::show_penalty_indicator($gradegrade);
}
return $penaltyindicator . $o;
@ -5423,7 +5417,15 @@ class assign {
);
$gradefordisplay = $gradebookgrade->str_long_grade;
} else {
$gradefordisplay = $this->display_grade($gradebookgrade->grade, false, 0, 0, $gradebookgrade->deductedmark);
// This grade info is the grade from gradebook.
// We need user id to determine if the grade is overridden or not.
$gradefordisplay = $this->display_grade(
$gradebookgrade->grade,
false,
$user->id,
0,
$gradebookgrade->deductedmark
);
}
$gradeddate = $gradebookgrade->dategraded;
@ -5630,6 +5632,8 @@ class assign {
}
}
// The assign grade for each attempt is not stored in the gradebook.
// We need to calculate them from assign_grade records.
[$penalisedgrade, $deductedmark] = $this->calculate_penalised_grade($grade);
// Now get the gradefordisplay.
@ -5641,6 +5645,7 @@ class assign {
$penalisedgrade,
$cangrade);
} else {
// We do not need user id here as the overriden grade should not affect the previous attempts.
$grade->gradefordisplay = $this->display_grade($penalisedgrade, false, 0, 0, $deductedmark);
}
@ -7880,12 +7885,11 @@ class assign {
// Penalty indicator.
$userassigngrade = $gradinginfo->items[0]->grades[$userid];
if (isset($userassigngrade->grade) && $userassigngrade->deductedmark > 0) {
if (isset($userassigngrade->grade)) {
$gradegrade = new \grade_grade();
$gradegrade->deductedmark = $userassigngrade->deductedmark;
$indicator = new \core_grades\output\penalty_indicator(2, $gradegrade);
$renderer = $PAGE->get_renderer('core_grades');
$penaltyindicator = $renderer->render_penalty_indicator($indicator);
$gradegrade->overridden = $userassigngrade->overridden;
$penaltyindicator = \core_grades\penalty_manager::show_penalty_indicator($gradegrade);
$gradestring = $penaltyindicator . $gradestring;
}

View File

@ -103,12 +103,32 @@ Feature: Check that the assignment grade can be updated correctly
| assign | user | onlinetext |
| Test assignment name | student1 | I'm the student first submission |
And I am on the "Test assignment name" Activity page logged in as teacher1
When I change window size to "large"
And I change window size to "large"
And I go to "Student 1" "Test assignment name" activity advanced grading page
And I set the field "Grade out of 100" to "100"
When I set the field "Grade out of 100" to "100"
And I set the field "Notify student" to "0"
And I press "Save changes"
Then the "title" attribute of ".penalty-indicator-icon" "css_element" should contain "Late penalty applied -10.00 marks"
And I follow "View all submissions"
And "Student 1" row "Grade" column of "generaltable" table should contain "100.00"
And "Student 1" row "Final grade" column of "generaltable" table should contain "90.00"
And the "title" attribute of ".penalty-indicator-icon" "css_element" should contain "Late penalty applied -10.00 marks"
# Override the grade.
And I am on the "Course 1" "grades > Grader report > View" page
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- |
| Student 1 | student10@example.com | 90 | 90 |
And the "title" attribute of ".penalty-indicator-icon" "css_element" should contain "Late penalty applied -10.00 marks"
And I turn editing mode on
And I set the following fields to these values:
| Student 1 Test assignment name grade | 100 |
And I click on "Save changes" "button"
And I turn editing mode off
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- |
| Student 1 | student10@example.com | 100 | 100 |
And ".penalty-indicator-icon" "css_element" should not exist
And I go to "Student 1" "Test assignment name" activity advanced grading page
And ".penalty-indicator-icon" "css_element" should not exist
And I follow "View all submissions"
And ".penalty-indicator-icon" "css_element" should not exist

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2025040400.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2025040700.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '5.0rc1 (Build: 20250404)'; // Human-friendly version name