From 27f95e9b479475af7dedfa4bca405026744464a7 Mon Sep 17 00:00:00 2001 From: nicolasconnault Date: Thu, 3 May 2007 07:10:22 +0000 Subject: [PATCH] MDL-9506 Upgraded grade_item::adjust_grade with Darlene's more elegant formula. Completed grade_category::get_children($depth, $arraytype). Added 4 constants to help that method. Added and passed all unit tests (111 now). --- lib/grade/grade_category.php | 141 ++++++++++++++++++++++++++------ lib/grade/grade_item.php | 26 +++--- lib/gradelib.php | 4 + lib/simpletest/testgradelib.php | 58 ++++++++++--- 4 files changed, 175 insertions(+), 54 deletions(-) diff --git a/lib/grade/grade_category.php b/lib/grade/grade_category.php index 9b34efd204..341bc3f718 100644 --- a/lib/grade/grade_category.php +++ b/lib/grade/grade_category.php @@ -49,7 +49,7 @@ class grade_category extends grade_object { * @var int $parent */ var $parent; - + /** * The number of parents this category has. * @var int $depth @@ -148,8 +148,7 @@ class grade_category extends grade_object { * @param string $fields * @return object grade_category object or false if none found. */ - function fetch($field1, $value1, $field2='', $value2='', $field3='', $value3='', $fields="*") - { + function fetch($field1, $value1, $field2='', $value2='', $field3='', $value3='', $fields="*") { if ($grade_category = get_record('grade_categories', $field1, $value1, $field2, $value2, $field3, $value3, $fields)) { if (isset($this) && get_class($this) == 'grade_category') { foreach ($grade_category as $param => $value) { @@ -169,7 +168,7 @@ class grade_category extends grade_object { * In addition to the normal insert() defined in grade_object, this method sets the depth * and path for this object, and update the record accordingly. The reason why this must * be done here instead of in the constructor, is that they both need to know the record's - * id number, which only gets created at insertion time. + * id number, which only gets created at insertion time. */ function insert() { $result = parent::insert(); @@ -212,33 +211,125 @@ class grade_category extends grade_object { * @return array Array of child objects (grade_category and grade_item). */ function get_children($depth=1, $arraytype='nested') { - $children = array(); + $children_array = array(); + + // Set up $depth for recursion + $newdepth = $depth; + if ($depth > 1) { + $newdepth--; + } + + $childrentype = $this->get_childrentype(); - if ($depth == 1) { - if (!empty($this->children)) { - return $this->children; + if ($childrentype == 'grade_item') { + $children = get_records('grade_items', 'categoryid', $this->id, 'id'); + // No need to proceed with recursion + $children_array = $this->children_to_array($children, $arraytype, 'grade_item'); + $this->children = $this->children_to_array($children, 'flat', 'grade_item'); + } elseif ($childrentype == 'grade_category') { + $children = get_records('grade_categories', 'parent', $this->id, 'id'); + if ($depth == 1) { + $children_array = $this->children_to_array($children, $arraytype, 'grade_category'); + $this->children = $this->children_to_array($children, 'flat', 'grade_category'); } else { - $cat = new grade_category(); - $cat->parent = $this->id; - $children = $cat->fetch_all_using_this(); - $item = new grade_item(); - $item->categoryid = $this->id; - $item_children = $item->fetch_all_using_this(); - - if (!empty($children)) { - $children = array_merge($children, $item_children); - } else { - $children = $item_children; + foreach ($children as $id => $child) { + $cat = new grade_category($child, false); + + if ($cat->has_children()) { + if ($arraytype == 'nested') { + $children_array[] = array('object' => $cat, 'children' => $cat->get_children($newdepth, $arraytype)); + } else { + $children_array[] = $cat; + $cat_children = $cat->get_children($newdepth, $arraytype); + foreach ($cat_children as $id => $cat_child) { + $children_array[] = new grade_category($cat_child, false); + } + } + } else { + if ($arraytype == 'nested') { + $children_array[] = array('object' => $cat); + } else { + $children_array[] = $cat; + } + } } + } + } else { + return null; + } + + return $children_array; + } + + /** + * Given an array of stdClass children of a certain $object_type, returns a flat or nested + * array of these children, ready for appending to a tree built by get_children. + * @static + * @param array $children + * @param string $arraytype + * @param string $object_type + * @return array + */ + function children_to_array($children, $arraytype='nested', $object_type='grade_item') { + $children_array = array(); + + foreach ($children as $id => $child) { + if ($arraytype == 'nested') { + $children_array[] = array('object' => new $object_type($child, false)); + } else { + $children_array[] = new $object_type($child); + } + } - $this->children = $children; + return $children_array; + } + + /** + * Returns true if this category has any child grade_category or grade_item. + * @return int number of direct children, or false if none found. + */ + function has_children() { + return count_records('grade_categories', 'parent', $this->id) + count_records('grade_items', 'categoryid', $this->id); + } + + /** + * This method checks whether an existing child exists for this + * category. If the new child is of a different type, the method will return false (not allowed). + * Otherwise it will return true. + * @param object $child This must be a complete object, not a stdClass + * @return boolean Success or failure + */ + function can_add_child($child) { + if ($this->has_children()) { + if (get_class($child) != $this->get_childrentype()) { + return false; + } else { + return true; + } + } else { + return true; + } + } + + /** + * Check the type of the first child of this category, to see whether it is a + * grade_category or a grade_item, and returns that type as a string (get_class). + * @return string + */ + function get_childrentype() { + $children = $this->children; + if (empty($this->children)) { + $count_item_children = count_records('grade_items', 'categoryid', $this->id); + $count_cat_children = count_records('grade_categories', 'parent', $this->id); + if ($count_item_children > 0) { + return 'grade_item'; + } elseif ($count_cat_children > 0) { + return 'grade_category'; + } else { + return null; } - } elseif ($depth > 1) { - // TODO implement - } elseif ($depth == 0) { - // TODO implement } - return $children; + return get_class($children[0]); } } diff --git a/lib/grade/grade_item.php b/lib/grade/grade_item.php index 91561490e6..9e08e4d85e 100644 --- a/lib/grade/grade_item.php +++ b/lib/grade/grade_item.php @@ -227,7 +227,6 @@ class grade_item extends grade_object { } } - /** * Returns the raw values for this grade item (as imported by module or other source). * @param int $userid Optional: to retrieve a single raw grade @@ -458,33 +457,28 @@ class grade_item extends grade_object { 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; + $grade_raw->grademax = count($grade_raw->scale->scale_items) - 1; + $this->grademax = count($this->scale->scale_items) - 1; + $grade_raw->grademin = 0; + $this->grademin = 0; } else { // Something's wrong, the raw grade has no value!? return false; } - // 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; + /** + * Darlene's formula + */ + $factor = ($gradevalue - $grade_raw->grademin) / ($grade_raw->grademax - $grade_raw->grademin); + $diff = $this->grademax - $this->grademin; + $gradevalue = $factor * $diff + $this->grademin; - // 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 diff --git a/lib/gradelib.php b/lib/gradelib.php index ff1e3028a6..bea775fe55 100644 --- a/lib/gradelib.php +++ b/lib/gradelib.php @@ -36,6 +36,10 @@ define('GRADE_AGGREGATE_MEAN', 0); define('GRADE_AGGREGATE_MEDIAN', 1); define('GRADE_AGGREGATE_SUM', 2); define('GRADE_AGGREGATE_MODE', 3); +define('GRADE_CHILDTYPE_ITEM', 0); +define('GRADE_CHILDTYPE_CAT', 1); +define('GRADE_ITEM', 0); // Used to compare class names with CHILDTYPE values +define('GRADE_CATEGORY', 1); // Used to compare class names with CHILDTYPE values require_once($CFG->libdir . '/grade/grade_category.php'); require_once($CFG->libdir . '/grade/grade_item.php'); diff --git a/lib/simpletest/testgradelib.php b/lib/simpletest/testgradelib.php index 58ba828153..650a5ed95e 100644 --- a/lib/simpletest/testgradelib.php +++ b/lib/simpletest/testgradelib.php @@ -45,16 +45,14 @@ require_once($CFG->libdir . '/dmllib.php'); * 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%"'); -*/ /** * Here is a brief explanation of the test data set up in these unit tests. - * category1 => array(grade_item1, grade_item3, category2 => array(grade_item2)) + * category1 => array(category2 => array(grade_item1, grade_item2), category3 => array(grade_item3)) * 3 users for 3 grade_items */ class gradelib_test extends UnitTestCase { @@ -151,6 +149,22 @@ class gradelib_test extends UnitTestCase { if ($grade_category->id = insert_record('grade_categories', $grade_category)) { $this->grade_categories[] = $grade_category; } + + $grade_category = new stdClass(); + + $grade_category->fullname = 'unittestcategory3'; + $grade_category->courseid = $this->courseid; + $grade_category->aggregation = GRADE_AGGREGATE_MODE; + $grade_category->keephigh = 100; + $grade_category->droplow = 10; + $grade_category->hidden = 0; + $grade_category->parent = $this->grade_categories[0]->id; + $grade_category->timecreated = mktime(); + $grade_category->timemodified = mktime(); + + if ($grade_category->id = insert_record('grade_categories', $grade_category)) { + $this->grade_categories[] = $grade_category; + } } /** @@ -160,7 +174,7 @@ class gradelib_test extends UnitTestCase { $grade_item = new stdClass(); $grade_item->courseid = $this->courseid; - $grade_item->categoryid = $this->grade_categories[0]->id; + $grade_item->categoryid = $this->grade_categories[1]->id; $grade_item->itemname = 'unittestgradeitem1'; $grade_item->itemtype = 'mod'; $grade_item->itemmodule = 'quiz'; @@ -196,7 +210,7 @@ class gradelib_test extends UnitTestCase { $grade_item = new stdClass(); $grade_item->courseid = $this->courseid; - $grade_item->categoryid = $this->grade_categories[0]->id; + $grade_item->categoryid = $this->grade_categories[2]->id; $grade_item->itemname = 'unittestgradeitem3'; $grade_item->itemtype = 'mod'; $grade_item->itemmodule = 'forum'; @@ -569,7 +583,7 @@ class gradelib_test extends UnitTestCase { $grade_category->timecreated = mktime(); $grade_category->timemodified = mktime(); - $grade_category->id = grade_create_category($this->courseid, 'unittestcategory2', $this->grade_items, GRADE_AGGREGATE_MEAN); + $grade_category->id = grade_create_category($this->courseid, 'unittestcategory4', $this->grade_items, GRADE_AGGREGATE_MEAN); $last_grade_category = end($this->grade_categories); $this->assertEqual($grade_category->id, $last_grade_category->id + 1); @@ -750,7 +764,7 @@ class gradelib_test extends UnitTestCase { $this->assertTrue(method_exists($grade_item, 'get_category')); $category = $grade_item->get_category(); - $this->assertEqual($this->grade_categories[0]->fullname, $category->fullname); + $this->assertEqual($this->grade_categories[1]->fullname, $category->fullname); } /** @@ -955,13 +969,31 @@ class gradelib_test extends UnitTestCase { function test_grade_category_get_children() { $category = new grade_category($this->grade_categories[0]); - $this->assertTrue(method_exists($category, 'get_children')); - - $this->assertEqual(3, count($category->get_children())); - $this->assertEqual(5, count($category->get_children(0))); + $children_array = $category->get_children(0); + $this->assertTrue(is_array($children_array)); + $this->assertTrue(!empty($children_array[0])); + $this->assertTrue(!empty($children_array[0]['object'])); + $this->assertTrue(!empty($children_array[0]['children'])); + $this->assertEqual($this->grade_categories[1]->id, $children_array[0]['object']->id); + $this->assertEqual($this->grade_categories[2]->id, $children_array[1]['object']->id); + $this->assertEqual($this->grade_items[0]->id, $children_array[0]['children'][0]['object']->id); + $this->assertEqual($this->grade_items[1]->id, $children_array[0]['children'][1]['object']->id); + $this->assertEqual($this->grade_items[2]->id, $children_array[1]['children'][0]['object']->id); + + $children_array = $category->get_children(0, 'flat'); + $this->assertEqual(5, count($children_array)); - $category = new grade_category($this->grade_categories[1]); - $this->assertEqual(1, count($category->get_children())); + $children_array = $category->get_children(1, 'flat'); + $this->assertEqual(2, count($children_array)); + } + + function test_grade_category_children_to_array() { + $children = get_records('grade_items', 'categoryid', $this->grade_categories[1]->id); + $children_array = grade_category::children_to_array($children, 'nested', 'grade_item'); + $this->assertTrue(is_array($children_array)); + $this->assertTrue(isset($children_array[0])); + $this->assertTrue(isset($children_array[0]['object'])); + $this->assertEqual($this->grade_items[0]->id, $children_array[0]['object']->id); } // GRADE_CALCULATION OBJECT -- 2.39.5