From d5bdb228a57e36fd22972ac40dbb16e630de89a3 Mon Sep 17 00:00:00 2001 From: nicolasconnault Date: Tue, 1 May 2007 05:45:54 +0000 Subject: [PATCH] MDL-9506 Finished grade_item::adjust_grade, which now also adjusts scale grades correctly. Added grade_scale object to wrap around the current non-normalized scale DB table. When/if we upgrade to grade_scales and grade_scale_items DB tables, changing the grade_scale class will be easy. --- lib/grade/grade_grades_raw.php | 19 ++++++- lib/grade/grade_item.php | 66 ++++++++++++++++------ lib/gradelib.php | 1 + lib/simpletest/testgradelib.php | 98 +++++++++++++++++++++++++++++++-- 4 files changed, 162 insertions(+), 22 deletions(-) diff --git a/lib/grade/grade_grades_raw.php b/lib/grade/grade_grades_raw.php index 02b81c4e09..79216c3d98 100644 --- a/lib/grade/grade_grades_raw.php +++ b/lib/grade/grade_grades_raw.php @@ -36,7 +36,7 @@ class grade_grades_raw extends grade_object { * Array of class variables that are not part of the DB table fields * @var array $nonfields */ - var $nonfields = array('table', 'nonfields'); + var $nonfields = array('table', 'nonfields', 'scale'); /** * The id of the grade_item this raw grade belongs to. @@ -79,7 +79,13 @@ class grade_grades_raw extends grade_object { * @var int $scaleid */ var $scaleid; - + + /** + * A grade_scale object (referenced by $this->scaleid). + * @var object $scale + */ + var $scale; + /** * The userid of the person who last modified this grade. * @var int $usermodified @@ -93,6 +99,10 @@ class grade_grades_raw extends grade_object { */ function grade_grades_raw($params=NULL, $fetch=true) { $this->grade_object($params, $fetch); + if (!empty($this->scaleid)) { + $this->scale = new grade_scale(array('id' => $this->scaleid)); + $this->scale->load_items(); + } } @@ -138,6 +148,11 @@ class grade_grades_raw extends grade_object { global $USER; $oldgrade = $this->gradevalue; $this->gradevalue = $newgrade; + + // Update this scaleid if it has changed (use the largest integer (most recent)) + if ($this->scale->id != $this->scaleid) { + $this->scaleid = max($this->scale->id, $this->scaleid); + } $result = parent::update(); diff --git a/lib/grade/grade_item.php b/lib/grade/grade_item.php index c19670322c..00d865cf83 100644 --- a/lib/grade/grade_item.php +++ b/lib/grade/grade_item.php @@ -40,7 +40,7 @@ class grade_item extends grade_object { * Array of class variables that are not part of the DB table fields * @var array $nonfields */ - var $nonfields = array('table', 'nonfields', 'calculation', 'grade_grades_raw'); + var $nonfields = array('table', 'nonfields', 'calculation', 'grade_grades_raw', 'scale'); /** * The course this grade_item belongs to. @@ -109,11 +109,17 @@ class grade_item extends grade_object { var $grademin; /** - * The scale this grade is based on, if applicable. + * id of the scale, if this grade is based on a scale. + * @var int $scaleid + */ + var $scaleid; + + /** + * A grade_scale object (referenced by $this->scaleid). * @var object $scale */ var $scale; - + /** * The Outcome this grade is associated with, if applicable. * @var object $outcome @@ -180,6 +186,19 @@ class grade_item extends grade_object { */ var $grade_grades_final = array(); + /** + * Constructor. Extends the basic functionality defined in grade_object. + * @param array $params Can also be a standard object. + * @param boolean $fetch Wether or not to fetch the corresponding row from the DB. + */ + function grade_item($params=NULL, $fetch=true) { + $this->grade_object($params, $fetch); + if (!empty($this->scaleid)) { + $this->scale = new grade_scale(array('id' => $this->scaleid)); + $this->scale->load_items(); + } + } + /** * Finds and returns a grade_item object based on 1-3 field values. * @@ -433,33 +452,48 @@ class grade_item extends grade_object { * @return mixed */ function adjust_grade($grade_raw, $gradevalue=NULL) { + $raw_offset = 0; + $item_offset = 0; + if (!empty($grade_raw->gradevalue)) { // Dealing with numerical grade if (empty($gradevalue)) { $gradevalue = $grade_raw->gradevalue; } + + // Adjust both scales to 0-? and store offsets + $raw_offset = -(0 - $grade_raw->grademin); + $item_offset = -(0 - $this->grademin); + $raw_adjusted_max = $grade_raw->grademax - $raw_offset; + $item_adjusted_max = $this->grademax - $item_offset; + } elseif(!empty($grade_raw->gradescale)) { // Dealing with a scale value if (empty($gradevalue)) { $gradevalue = $grade_raw->gradescale; } + + $raw_adjusted_max = count($grade_raw->scale->scale_items) - 1; + $item_adjusted_max = count($this->scale->scale_items) - 1; } else { // Something's wrong, the raw grade has no value!? - + return false; } - - // Adjust both scales to 0-? and store offsets - $raw_offset = -(0 - $grade_raw->grademin); - $item_offset = -(0 - $this->grademin); - $raw_adjusted_max = $grade_raw->grademax - $raw_offset; - $item_adjusted_max = $this->grademax - $item_offset; - - // Compute factor from adjusted scales + // Compute factor from grademax of adjusted scales $factor = ($item_adjusted_max) / ($raw_adjusted_max); + + // Multiply adjusted grade value (using source offset) by factor $gradevalue = ($gradevalue - $raw_offset) * $factor; - $gradevalue += $item_offset; - // Apply factors - $gradevalue *= $this->multfactor; - $gradevalue += $this->plusfactor; + // Add target offset to resulting grade value + $gradevalue += $item_offset; + + // Apply rounding or factors, depending on whether it's a scale or value + if (!empty($grade_raw->gradevalue)) { + // Apply other grade_item factors + $gradevalue *= $this->multfactor; + $gradevalue += $this->plusfactor; + } elseif (!empty($grade_raw->gradescale)) { + $gradevalue = (int) round($gradevalue); + } return $gradevalue; } diff --git a/lib/gradelib.php b/lib/gradelib.php index e713d59e8b..ff1e3028a6 100644 --- a/lib/gradelib.php +++ b/lib/gradelib.php @@ -42,6 +42,7 @@ require_once($CFG->libdir . '/grade/grade_item.php'); require_once($CFG->libdir . '/grade/grade_calculation.php'); require_once($CFG->libdir . '/grade/grade_grades_raw.php'); require_once($CFG->libdir . '/grade/grade_grades_final.php'); +require_once($CFG->libdir . '/grade/grade_scale.php'); /** * Extracts from the gradebook all the grade items attached to the calling object. diff --git a/lib/simpletest/testgradelib.php b/lib/simpletest/testgradelib.php index 56a0d331fc..954b34ad03 100644 --- a/lib/simpletest/testgradelib.php +++ b/lib/simpletest/testgradelib.php @@ -41,11 +41,14 @@ require_once($CFG->libdir . '/dmllib.php'); /** * A cleanup of the tables is a good idea before we start, in case the last unit test - * crashed before running its tearDown method. + * crashed before running its tearDown method. Be careful because ANY record matching + * this search (%unittest%) will be deleted! Maybe a good idea to switch this off in + * production environment. */ delete_records_select('grade_categories', 'fullname LIKE "%unittest%"'); delete_records_select('grade_items', 'itemname LIKE "%unittest%"'); delete_records_select('grade_calculation', 'calculation LIKE "%unittest%"'); +delete_records_select('scale', 'name LIKE "%unittest%"'); class gradelib_test extends UnitTestCase { @@ -62,7 +65,8 @@ class gradelib_test extends UnitTestCase { 'grade_grades_final', 'grade_grades_text', 'grade_outcomes', - 'grade_history'); + 'grade_history', + 'scale'); var $grade_items = array(); var $grade_categories = array(); @@ -72,6 +76,7 @@ class gradelib_test extends UnitTestCase { var $grade_grades_text = array(); var $grade_outcomes = array(); var $grade_history = array(); + var $scale = array(); var $courseid = 1; var $userid = 1; @@ -472,6 +477,36 @@ class gradelib_test extends UnitTestCase { } + /** + * Load scale data into the database, and adds the corresponding objects to this class' variable. + */ + function load_scale() { + $scale = new stdClass(); + + $scale->name = 'unittestscale1'; + $scale->courseid = $this->courseid; + $scale->userid = $this->userid; + $scale->scale = 'Way off topic, Not very helpful, Fairly neutral, Fairly helpful, Supportive, Some good information, Perfect answer!'; + $scale->description = 'This scale defines some of qualities that make posts helpful within the Moodle help forums.\n Your feedback will help others see how their posts are being received.'; + $scale->timemodified = mktime(); + + if ($scale->id = insert_record('scale', $scale)) { + $this->scale[] = $scale; + } + + $scale = new stdClass(); + + $scale->name = 'unittestscale2'; + $scale->courseid = $this->courseid; + $scale->userid = $this->userid; + $scale->scale = 'Distinction, Very Good, Good, Pass, Fail'; + $scale->description = 'This scale is used to mark standard assignments.'; + $scale->timemodified = mktime(); + + if ($scale->id = insert_record('scale', $scale)) { + $this->scale[] = $scale; + } + } /** * TESTS BEGIN HERE */ @@ -537,7 +572,7 @@ class gradelib_test extends UnitTestCase { $params->iteminstance = 4; $params->iteminfo = 'Grade item used for unit testing'; - $grade_item = new grade_item($params); + $grade_item = new grade_item($params, false); $this->assertEqual($params->courseid, $grade_item->courseid); $this->assertEqual($params->categoryid, $grade_item->categoryid); @@ -752,7 +787,6 @@ class gradelib_test extends UnitTestCase { // Try a larger maximum grade $grade_item->grademax = 150; $grade_item->grademin = 0; - $this->assertEqual(60, $grade_item->adjust_grade($grade_raw)); // Try larger minimum grade @@ -791,6 +825,22 @@ class gradelib_test extends UnitTestCase { $this->assertEqual(round(1.6), round($grade_item->adjust_grade($grade_raw))); } + function test_grade_item_adjust_scale_grade() { + // Load raw grade and its scale + $grade_raw = new grade_grades_raw(array('scaleid' => $this->scale[0]->id)); + $grade_raw->gradescale = 4; + $this->assertEqual('Fairly neutral', $grade_raw->scale->scale_items[2]); + + // Load grade item and its scale + $grade_item = new grade_item(array('scaleid' => $this->scale[1]->id)); + $this->assertEqual('Very Good', $grade_item->scale->scale_items[1]); + + // Test grade_item::adjust_scale + $this->assertEqual(3, $grade_item->adjust_grade($grade_raw)); + $grade_raw->gradescale = 6; + $this->assertEqual(4, $grade_item->adjust_grade($grade_raw)); + } + // GRADE_CATEGORY OBJECT function test_grade_category_construct() { @@ -815,6 +865,46 @@ class gradelib_test extends UnitTestCase { // GRADE_CALCULATION OBJECT +// SCALE OBJECT + + function test_scale_constructor() { + $params = new stdClass(); + + $params->name = 'unittestscale3'; + $params->courseid = $this->courseid; + $params->userid = $this->userid; + $params->scale = 'Distinction, Very Good, Good, Pass, Fail'; + $params->description = 'This scale is used to mark standard assignments.'; + $params->timemodified = mktime(); + + $scale = new grade_scale($params, false); + + $this->assertEqual($params->name, $scale->name); + $this->assertEqual($params->scale, $scale->scale); + $this->assertEqual($params->description, $scale->description); + + } + + function test_scale_load_items() { + $scale = new grade_scale($this->scale[0]); + $this->assertTrue(method_exists($scale, 'load_items')); + + $scale->load_items(); + $this->assertEqual(7, count($scale->scale_items)); + $this->assertEqual('Fairly neutral', $scale->scale_items[2]); + } + + function test_scale_compact_items() { + $scale = new grade_scale($this->scale[0]); + $this->assertTrue(method_exists($scale, 'compact_items')); + + $scale->load_items(); + $scale->scale = null; + $scale->compact_items(); + + // The original string and the new string may have differences in whitespace around the delimiter, and that's OK + $this->assertEqual(preg_replace('/\s*,\s*/', ',', $this->scale[0]->scale), $scale->scale); + } // SCENARIOS: define use cases here by defining tests and implementing code until the tests pass. } -- 2.39.5