MDL-48634 grades: Add an option to rescale when changing the maxgrade

This commit is contained in:
Damyon Wiese 2015-04-15 07:57:28 -04:00 committed by Mark Nelson
parent 9d5d9c64ff
commit d629c601c5
12 changed files with 422 additions and 11 deletions

View file

@ -43,14 +43,29 @@ require_once($CFG->dirroot.'/lib/grade/grade_scale.php');
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
class MoodleQuickForm_modgrade extends MoodleQuickForm_group {
/** @var boolean $isupdate Is this an add or an update ? */
public $isupdate = false;
/** @var float $currentgrade The current grademax for the grade_item */
public $currentgrade = false;
/** @var boolean $hasgrades Has this grade_item got any real grades (with values) */
public $hasgrades = false;
/** @var boolean $canrescale Does this activity support rescaling grades? */
public $canrescale = false;
/**
* Constructor
*
* @param string $elementname Element's name
* @param mixed $elementlabel Label(s) for an element
* @param array $options Options to control the element's display. Not used.
* @param array $options Options to control the element's display. Required - must contain the following options:
* 'isupdate' - is this a new module or are we editing an existing one?
* 'currentgrade' - the current grademax in the database for this gradeitem
* 'hasgrades' - whether or not the grade_item has existing grade_grades
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementname = null, $elementlabel = null, $options = array(), $attributes = null) {
@ -59,6 +74,13 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
$this->_persistantFreeze = true;
$this->_appendName = true;
$this->_type = 'modgrade';
$this->isupdate = !empty($options['isupdate']);
$this->currentgrade = false;
if (isset($options['currentgrade'])) {
$this->currentgrade = $options['currentgrade'];
}
$this->hasgrades = !empty($options['hasgrades']);
$this->canrescale = !empty($options['canrescale']);
}
/**
@ -75,7 +97,7 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
* Create elements for this group.
*/
public function _createElements() {
global $COURSE, $CFG;
global $COURSE, $CFG, $OUTPUT;
$attributes = $this->getAttributes();
if (is_null($attributes)) {
$attributes = array();
@ -113,6 +135,16 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
$typeselectid = $this->generate_modgrade_subelement_id('modgrade_type');
$typeselect->updateAttributes(array('id' => $typeselectid));
// Check box for options for processing existing grades.
if ($this->isupdate && $this->hasgrades && $this->canrescale) {
$langrescalegrades = get_string('modgraderescalegrades', 'grades');
$rescalegradesselect = @MoodleQuickForm::createElement('selectyesno',
'modgrade_rescalegrades',
$langrescalegrades);
$rescalegradesselect->_generateId();
$rescalegradesid = $rescalegradesselect->getAttribute('id');
}
// Add elements.
// Grade type select box.
@ -132,6 +164,15 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
$this->_elements[] = @MoodleQuickForm::createElement('static', 'pointlabel', '', $label);
$this->_elements[] = $maxgrade;
$this->_elements[] = @MoodleQuickForm::createElement('static', 'pointspacer', '', '<br />');
if ($this->isupdate && $this->hasgrades && $this->canrescale) {
// We need to know how to apply any changes to maxgrade - ie to either update, or don't touch exising grades.
$label = html_writer::tag('label', $rescalegradesselect->getLabel(), array('for' => $rescalegradesid));
$labelhelp = new help_icon('modgraderescalegrades', 'grades');
$this->_elements[] = @MoodleQuickForm::createElement('static', 'scalelabel', '', $label . $OUTPUT->render($labelhelp));
$this->_elements[] = $rescalegradesselect;
$this->_elements[] = @MoodleQuickForm::createElement('static', 'scalespacer', '', '<br />');
}
}
/**
@ -156,8 +197,9 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
$type = (isset($vals['modgrade_type'])) ? $vals['modgrade_type'] : 'none';
$point = (isset($vals['modgrade_point'])) ? $vals['modgrade_point'] : null;
$scale = (isset($vals['modgrade_scale'])) ? $vals['modgrade_scale'] : null;
$rescalegrades = (isset($vals['modgrade_rescalegrades'])) ? $vals['modgrade_rescalegrades'] : null;
$return = $this->process_value($type, $scale, $point);
return array($this->getName() => $return);
return array($this->getName() => $return, $this->getName() . '_rescalegrades' => $rescalegrades);
}
/**
@ -231,6 +273,7 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
// Set disable actions.
$caller->disabledIf($name.'[modgrade_scale]', $name.'[modgrade_type]', 'neq', 'scale');
$caller->disabledIf($name.'[modgrade_point]', $name.'[modgrade_type]', 'neq', 'point');
$caller->disabledIf($name.'[modgrade_rescalegrades]', $name.'[modgrade_type]', 'neq', 'point');
// Set validation rules for the sub-elements belonging to this element.
// A handy note: the parent scope of a closure is the function in which the closure was declared.

View file

@ -354,6 +354,20 @@ class grade_item extends grade_object {
return grade_object::fetch_helper('grade_items', 'grade_item', $params);
}
/**
* Check to see if there are any existing grades for this grade_item.
*
* @return boolean - true if there are valid grades for this grade_item.
*/
public function has_grades() {
global $DB;
$count = $DB->count_records_select('grade_grades',
'itemid = :gradeitemid AND finalgrade IS NOT NULL',
array('gradeitemid' => $this->id));
return $count > 0;
}
/**
* Finds and returns all grade_item instances based on params.
*
@ -819,6 +833,60 @@ class grade_item extends grade_object {
}
}
/**
* Update the rawgrademax and rawgrademin for all grade_grades records for this item.
* Scale every rawgrade to maintain the percentage. This function should be called
* after the gradeitem has been updated to the new min and max values.
*
* @param float $oldgrademin The previous grade min value
* @param float $oldgrademax The previous grade max value
* @param float $newgrademin The new grade min value
* @param float $newgrademax The new grade max value
* @param string $source from where was the object inserted (mod/forum, manual, etc.)
* @return bool True on success
*/
public function rescale_grades_keep_percentage($oldgrademin, $oldgrademax, $newgrademin, $newgrademax, $source = null) {
global $DB;
if (empty($this->id)) {
return false;
}
if ($oldgrademax <= $oldgrademin) {
// Grades cannot be scaled.
return false;
}
$scale = ($newgrademax - $newgrademin) / ($oldgrademax - $oldgrademin);
if (($newgrademax - $newgrademin) <= 1) {
// We would lose too much precision, lets bail.
return false;
}
$rs = $DB->get_recordset('grade_grades', array('itemid' => $this->id));
foreach ($rs as $graderecord) {
// For each record, create an object to work on.
$grade = new grade_grade($graderecord, false);
// Set this object in the item so it doesn't re-fetch it.
$grade->grade_item = $this;
// Updating the raw grade automatically updates the min/max.
if ($this->is_raw_used()) {
$rawgrade = (($grade->rawgrade - $oldgrademin) * $scale) + $newgrademin;
$this->update_raw_grade(false, $rawgrade, $source, false, FORMAT_MOODLE, null, null, null, $grade);
} else {
$finalgrade = (($grade->finalgrade - $oldgrademin) * $scale) + $newgrademin;
$this->update_final_grade($grade->userid, $finalgrade, $source);
}
}
$rs->close();
// Mark this item for regrading.
$this->force_regrading();
return true;
}
/**
* Sets this grade_item's needsupdate to true. Also marks the course item as needing update.
*
@ -1649,6 +1717,8 @@ class grade_item extends grade_object {
$oldgrade->overridden = $grade->overridden;
$oldgrade->feedback = $grade->feedback;
$oldgrade->feedbackformat = $grade->feedbackformat;
$oldgrade->rawgrademin = $grade->rawgrademin;
$oldgrade->rawgrademax = $grade->rawgrademax;
// MDL-31713 rawgramemin and max must be up to date so conditional access %'s works properly.
$grade->rawgrademin = $this->grademin;
@ -1687,6 +1757,8 @@ class grade_item extends grade_object {
} else if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
or $grade->feedback !== $oldgrade->feedback
or $grade->feedbackformat != $oldgrade->feedbackformat
or grade_floats_different($grade->rawgrademin, $oldgrade->rawgrademin)
or grade_floats_different($grade->rawgrademax, $oldgrade->rawgrademax)
or ($oldgrade->overridden == 0 and $grade->overridden > 0)) {
$grade->timemodified = time(); // hack alert - date graded
$result = $grade->update($source);

View file

@ -50,6 +50,7 @@ class core_grade_item_testcase extends grade_base_testcase {
$this->sub_test_grade_item_load_item_category();
$this->sub_test_grade_item_regrade_final_grades();
$this->sub_test_grade_item_adjust_raw_grade();
$this->sub_test_grade_item_rescale_grades_keep_percentage();
$this->sub_test_grade_item_set_locked();
$this->sub_test_grade_item_is_locked();
$this->sub_test_grade_item_set_hidden();
@ -371,6 +372,46 @@ class core_grade_item_testcase extends grade_base_testcase {
$this->assertEquals(round(1.6), round($grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax)));
}
protected function sub_test_grade_item_rescale_grades_keep_percentage() {
global $DB;
$gradeitem = new grade_item($this->grade_items[10], false); // 10 is the manual grade item.
// Create some grades to go with the grade item.
$gradeids = array();
$grade = new stdClass();
$grade->itemid = $gradeitem->id;
$grade->userid = $this->user[2]->id;
$grade->finalgrade = 10;
$grade->rawgrademax = $gradeitem->grademax;
$grade->rawgrademin = $gradeitem->grademin;
$grade->timecreated = time();
$grade->timemodified = time();
$gradeids[] = $DB->insert_record('grade_grades', $grade);
$grade->userid = $this->user[3]->id;
$grade->finalgrade = 50;
$grade->rawgrademax = $gradeitem->grademax;
$grade->rawgrademin = $gradeitem->grademin;
$gradeids[] = $DB->insert_record('grade_grades', $grade);
// Run the function.
$gradeitem->grademax = 33;
$gradeitem->grademin = 3;
$gradeitem->update();
$gradeitem->rescale_grades_keep_percentage(0, 100, 3, 33, 'test');
// Check that the grades were updated to match the grade item.
$grade = $DB->get_record('grade_grades', array('id' => $gradeids[0]));
$this->assertEquals($gradeitem->grademax, $grade->rawgrademax, 'Max grade mismatch', 0.0001);
$this->assertEquals($gradeitem->grademin, $grade->rawgrademin, 'Min grade mismatch', 0.0001);
$this->assertEquals(6, $grade->finalgrade, 'Min grade mismatch', 0.0001);
$grade = $DB->get_record('grade_grades', array('id' => $gradeids[1]));
$this->assertEquals($gradeitem->grademax, $grade->rawgrademax, 'Max grade mismatch', 0.0001);
$this->assertEquals($gradeitem->grademin, $grade->rawgrademin, 'Min grade mismatch', 0.0001);
$this->assertEquals(18, $grade->finalgrade, 'Min grade mismatch', 0.0001);
}
protected function sub_test_grade_item_set_locked() {
// Getting a grade_item from the DB as set_locked() will fail if the grade items needs to be updated
// also needs to have at least one grade_grade or $grade_item->get_final(1) returns null.