]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-9137 various grading improvements
authorskodak <skodak>
Mon, 18 Jun 2007 13:43:40 +0000 (13:43 +0000)
committerskodak <skodak>
Mon, 18 Jun 2007 13:43:40 +0000 (13:43 +0000)
1/ initial support for migration of old grade_items and categories (not tested)
2/ rewritten grade update and calculation logic
3/ initial support for calculation formulas
4/ minor API refactoring and cleanup
5/ various small bugfixes
6/ fixed use of grademax with scales
7/ fixed some unit tests

TODO:
* implement proper locking of grades - needs discussion
* force recalculation of all formulas after adding/removing/changing of grade items
* better delete flag support
* support for NULLs n backup - Eloy already proposed a solution
* support for NULLs in set_field()
* speedup
* more unit tests nd functional tests

14 files changed:
grade/export/lib.php
lib/evalmath/evalmath.class.php
lib/grade/grade_calculation.php
lib/grade/grade_category.php
lib/grade/grade_grades_raw.php
lib/grade/grade_item.php
lib/gradelib.php
lib/simpletest/fixtures/gradetest.php
lib/simpletest/grade/simpletest/testgradecategory.php
lib/simpletest/grade/simpletest/testgradeitem.php
lib/simpletest/grade/simpletest/testgraderaw.php
lib/simpletest/grade/simpletest/testgradetree.php
lib/simpletest/testgradelib.php
lib/simpletest/testmathslib.php

index ed8b8387d6d074d375781c471b75de8d454865d6..791b4f3dbc0709473f718bc55109ce96c6e3a355 100755 (executable)
@@ -82,6 +82,10 @@ class grade_export {
         $this->id = $id;
         $this->course = $course;
 
+        // first make sure we have all final grades
+        // TODO: check that no grade_item has needsupdate set
+        grade_update_final_grades();
+
         /// Check to see if groups are being used in this course
         if ($groupmode = groupmode($course)) {   // Groups are being used
             
@@ -129,7 +133,6 @@ class grade_export {
         if ($gradeitems) {
             foreach ($gradeitems as $gradeitem) {
               
-                $gradeitem -> generate_final();
                 // load as an array of grade_final objects
                 if ($itemgrades = $gradeitem -> load_final()) {                    
                     
index 37a419715c1ae5a16786daabc73060e47272be76..197bae417097ba0ae37cf34e0e40c1175cb7b7f1 100644 (file)
@@ -124,7 +124,7 @@ class EvalMath {
         if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end\r
         //===============\r
         // is it a variable assignment?\r
-        if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {\r
+        if (preg_match('/^\s*([a-z][a-z0-9]*)\s*=\s*(.+)$/', $expr, $matches)) {\r
             if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant\r
                 return $this->trigger("cannot assign to constant '$matches[1]'");\r
             }\r
@@ -133,7 +133,7 @@ class EvalMath {
             return $this->v[$matches[1]]; // and return the resulting value\r
         //===============\r
         // is it a function assignment?\r
-        } elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {\r
+        } elseif (preg_match('/^\s*([a-z][a-z0-9]*)\s*\(\s*([a-z][a-z0-9]*(?:\s*,\s*[a-z][a-z0-9]*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {\r
             $fnn = $matches[1]; // get the function name\r
             if (in_array($matches[1], $this->fb)) { // make sure it isn't built in\r
                 return $this->trigger("cannot redefine built-in function '$matches[1]()'");\r
@@ -142,7 +142,7 @@ class EvalMath {
             if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix\r
             for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables\r
                 $token = $stack[$i];\r
-                if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {\r
+                if (preg_match('/^[a-z][a-z0-9]*$/', $token) and !in_array($token, $args)) {\r
                     if (array_key_exists($token, $this->v)) {\r
                         $stack[$i] = $this->v[$token];\r
                     } else {\r
@@ -193,7 +193,7 @@ class EvalMath {
         while(1) { // 1 Infinite Loop ;)\r
             $op = substr($expr, $index, 1); // get the first character at the current index\r
             // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand\r
-            $ex = preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);\r
+            $ex = preg_match('/^([a-z][a-z0-9]*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);\r
             //===============\r
             if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?\r
                 $stack->push('_'); // put a negation on the stack\r
@@ -220,7 +220,7 @@ class EvalMath {
                     if (is_null($o2)) return $this->trigger("unexpected ')'");\r
                     else $output[] = $o2;\r
                 }\r
-                if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) { // did we just close a function?\r
+                if (preg_match("/^([a-z][a-z0-9]*)\($/", $stack->last(2), $matches)) { // did we just close a function?\r
                     $fnn = $matches[1]; // get the function name\r
                     $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)\r
                     $fn = $stack->pop();\r
@@ -248,7 +248,7 @@ class EvalMath {
                     else $output[] = $o2; // pop the argument expression stuff and push onto the output\r
                 }\r
                 // make sure there was a function\r
-                if (!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches))\r
+                if (!preg_match("/^([a-z][a-z0-9]*)\($/", $stack->last(2), $matches))\r
                     return $this->trigger("unexpected ','");\r
                 $stack->push($stack->pop()+1); // increment the argument count\r
                 $stack->push('('); // put the ( back on, we'll need to pop back to it again\r
@@ -263,7 +263,7 @@ class EvalMath {
             } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?\r
                 $expecting_op = true;\r
                 $val = $match[1];\r
-                if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...\r
+                if (preg_match("/^([a-z][a-z0-9]*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...\r
                     if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f) or array_key_exists($matches[1], $this->fc)) { // it's a func\r
                         $stack->push($val);\r
                         $stack->push(1);\r
@@ -281,7 +281,7 @@ class EvalMath {
             } elseif ($op == ')') {\r
                 //it could be only custom function with no params or general error\r
                 if ($stack->last() != '(' or $stack->last(2) != 1) return $this->trigger("unexpected ')'");\r
-                if (preg_match("/^([a-z]\w*)\($/", $stack->last(3), $matches)) { // did we just close a function?\r
+                if (preg_match("/^([a-z][a-z0-9]*)\($/", $stack->last(3), $matches)) { // did we just close a function?\r
                     $stack->pop();// (\r
                     $stack->pop();// 1\r
                     $fn = $stack->pop();\r
index e3b75fb1b472478e5373a40671c4ccb90b8b57cd..b9a6b17e949fed4f3b47bcb19753f7def2dde64e 100644 (file)
@@ -40,8 +40,8 @@ class grade_calculation 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', 'formula', 'useditems', 'grade_item');
+
     /**
      * A reference to the grade_item this calculation belongs to.
      * @var int $itemid
@@ -59,21 +59,133 @@ class grade_calculation extends grade_object {
      * @var int $usermodified
      */
     var $usermodified;
-    
+
+    /**
+     * Calculation formula object
+     */
+    var $formula;
+
+    /**
+     * List of other items this calculation depends on
+     */
+    var $useditems;
+
+    /**
+     * Grade item object
+     */
+    var $grade_item;
+
     /**
-     * A formula parser object.
-     * @var object $parser
-     * @TODO implement parsing of formula and calculation MDL-9643
+     * Applies the formula represented by this object. The parameteres are taken from final
+     * grades of grade items in current course only.
+     * @return boolean false if error
      */
-    var $parser;
+    function compute() {
+        global $CFG;
+        require_once($CFG->libdir.'/mathslib.php');
+
+        if (empty($this->id) or empty($this->itemid)) {
+            debugging('Can not initialize calculation!');
+            return false;
+        }
+
+        // init grade_item
+        $this->grade_item = grade_item::fetch('id', $this->itemid);
+
+        //init used items
+        $this->useditems = $this->dependson();
+
+        // init maths library
+        $this->formula = new calc_formula($this->calculation);
+
+        // where to look for final grades
+        $gis = implode(',', array_merge($this->useditems, array($this->itemid)));
+
+        $sql = "SELECT f.*
+                  FROM {$CFG->prefix}grade_grades_final f, {$CFG->prefix}grade_items gi
+                 WHERE gi.id = f.itemid AND gi.courseid={$this->grade_item->courseid} AND gi.id IN ($gis)
+              ORDER BY f.userid";
+
+        $return = true;
+
+        if ($rs = get_recordset_sql($sql)) {
+            if ($rs->RecordCount() > 0) {
+                $prevuser = 0;
+                $grades   = array();
+                $final    = null;
+                while ($subfinal = rs_fetch_next_record($rs)) {
+                    if ($subfinal->userid != $prevuser) {
+                        if (!$this->_use_formula($prevuser, $grades, $final)) {
+                            $return = false;
+                        }
+                        $prevuser = $subfinal->userid;
+                        $grades   = array();
+                        $final    = null;
+                    }
+                    if ($subfinal->itemid == $this->grade_item->id) {
+                        $final = grade_grades_final::fetch('id', $subfinal->id);
+                    }
+                    $grades['gi'.$subfinal->itemid] = $subfinal->gradevalue;
+                }
+                if (!$this->_use_formula($prevuser, $grades, $final)) {
+                    $return = false;
+                }
+            }
+        }
+
+        return $return;
+    }
+
+    /**
+     * internal function - does the final grade calculation
+     */
+    function _use_formula($userid, $params, $final) {
+        if (empty($userid)) {
+            return true;
+        }
+
+        // add missing final grade values - use 0
+        foreach($this->useditems as $gi) {
+            if (!array_key_exists('gi'.$gi, $params)) {
+                $params['gi'.$gi] = 0;
+            } else {
+                $params['gi'.$gi] = (float)$params[$gi];
+            }
+        }
+
+        // can not use own final grade during calculation
+        unset($params['gi'.$this->grade_item->id]);
+
+        // do the calculation
+        $this->formula->set_params($params);
+        $result = $this->formula->evaluate();
+
+        // insert final grade if needed
+        if (empty($final)) {
+            $this->grade_item->grade_grades_final[$userid] = new grade_grades_final(array('itemid'=>$this->grade_item->id, 'userid'=>$userid));
+            $this->grade_item->grade_grades_final[$userid]->insert();
+        }
+
+        // store the result
+        if ($result === false) {
+            $final->grade_value = null;
+            $final->update();
+            return false;
+
+        } else {
+            $final = $result;
+            $this->grade_item->grade_grades_final[$userid]->update();
+            return true;
+        }
+    }
 
     /**
-     * Applies the formula represented by this object to the value given, and returns the result.
-     * @param float $oldvalue
-     * @return float result
+     * Finds out on which other items does this calculation depend
+     * @return array of grade_item ids this one depends on
      */
-    function compute($oldvalue) {
-        return $oldvalue; // TODO implement computation using parser
+    function dependson() {
+        preg_match_all('/gi([0-9]+)/i', $this->calculation, $matches);
+        return ($matches[1]);
     }
 
     /**
@@ -89,7 +201,7 @@ class grade_calculation extends grade_object {
      * @param string $fields
      * @return object grade_calculation 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_calculation = get_record('grade_calculations', $field1, $value1, $field2, $value2, $field3, $value3, $fields)) {
             if (isset($this) && get_class($this) == 'grade_calculation') {
                 print_object($this);
index 15f4dd91baa2e8f04c4b5eeae064b563b4453352..94f27c1ea476efc997edfc3aca98ad00f99bcf67 100644 (file)
@@ -125,16 +125,8 @@ class grade_category extends grade_object {
      * @param boolean $fetch Whether or not to fetch the corresponding row from the DB.
      * @param object $grade_item The associated grade_item object can be passed during construction.
      */
-    function grade_category($params=NULL, $fetch=true, $grade_item=NULL) {
+    function grade_category($params=NULL, $fetch=true) {
         $this->grade_object($params, $fetch);
-        if (!empty($grade_item) && $grade_item->itemtype == 'category') {
-            $this->grade_item = $grade_item;
-            if (empty($this->grade_item->iteminstance)) {
-                $this->grade_item->iteminstance = $this->id;
-                $this->grade_item->update();
-            }
-        }
-
         $this->path = grade_category::build_path($this);
     }
 
@@ -242,7 +234,10 @@ class grade_category extends grade_object {
      * This method also creates an associated grade_item if this wasn't done during construction.
      */
     function insert() {
-        $result = parent::insert();
+        if (!parent::insert()) {
+            debugging("Could not insert this category: " . print_r($this, true));
+            return false;
+        }
 
         $this->path = grade_category::build_path($this);
 
@@ -255,30 +250,19 @@ class grade_category extends grade_object {
 
         $this->update();
 
-        if (empty($this->grade_item)) {
-            $grade_item = new grade_item();
-            $grade_item->iteminstance = $this->id;
-            $grade_item->itemtype = 'category';
+        // initialize grade_item for this category
+        $this->grade_item = $this->get_grade_item();
 
-            if (!$grade_item->insert()) {
-                debugging("Could not insert this grade_item in the database: " . print_r($grade_item, true));
+        // Notify parent category of need to update.
+        $this->load_parent_category();
+        if (!empty($this->parent_category)) {
+            if (!$this->parent_category->flag_for_update()) {
+                debugging("Could not notify parent category of the need to update its final grades.");
                 return false;
             }
-
-            $this->grade_item = $grade_item;
         }
 
-        // Notify parent category of need to update.
-        if ($result) {
-            $this->load_parent_category();
-            if (!empty($this->parent_category)) {
-                if (!$this->parent_category->flag_for_update()) {
-                    debugging("Could not notify parent category of the need to update its final grades.");
-                    return false;
-                }
-            }
-        }
-        return $result;
+        return true;
     }
 
     /**
@@ -315,15 +299,15 @@ class grade_category extends grade_object {
      * @return boolean Success or failure
      */
     function flag_for_update() {
+        if (empty($this->id)) {
+            debugging("Needsupdate requested before insering grade category.");
+            return true;
+        }
+
         $result = true;
 
         $this->load_grade_item();
 
-        if (empty($this->grade_item)) {
-            die("Associated grade_item object does not exist for this grade_category!" . print_object($this));
-            // TODO Send error message, this is a critical error: each category MUST have a matching grade_item object and load_grade_item() is supposed to create one!
-        }
-
         $paths = explode('/', $this->path);
 
         // Remove the first index, which is always empty
@@ -348,60 +332,155 @@ class grade_category extends grade_object {
      * raw and final grades, which means that ultimately we must get grade_items as children. The category's aggregation
      * method is used to generate these raw grades, which can then be used by the category's associated grade_item
      * to apply calculations to and generate final grades.
+     *
+     * This function must be use ONLY from grade_item::update_final_grade(),
+     * because the calculation must be done in correct order!!
+
      * Steps to follow:
-     *  1. If the children are categories, AND their grade_item's needsupdate is true call generate_grades() on each of them (recursion)
-     *  2. Get final grades from immediate children (if the children are categories, get the final grades from their grade_item)
+     *  1. Get final grades from immediate children (if the children are categories, get the final grades from their grade_item)
      *  3. Aggregate these grades
      *  4. Save them under $this->grade_item->grade_grades_raw
-     *  5. Use the grade_item's methods for generating the final grades.
      */
     function generate_grades() {
-        // 1. Get immediate children
-        $children = $this->get_children(1, 'flat');
+        global $CFG;
 
-        if (empty($children)) {
-            debugging("Could not generate grades for this category, it has no children.");
-            return false;
+        $grade_item = $this->get_grade_item();
+        if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
+            $grade_item->load_scale();
         }
 
-        // This assumes that all immediate children are of the same type (category OR item)
-        $childrentype = get_class(current($children));
+        $dependson = $grade_item->dependson();
+        $items = array();
+
+        foreach($dependson as $dep) {
+            $items[$dep] = grade_item::fetch('id', $dep);
+        }
 
-        $final_grades_for_aggregation = array();
+        // where to look for final grades
+        $gis = implode(',', array_merge($dependson, array($grade_item->id)));
 
-        // 2. Get final grades from immediate children, after generating them if needed.
-        // NOTE: Make sure that the arrays of final grades are indexed by userid. The resulting arrays are unlikely to match in sizes.
-        if ($childrentype == 'grade_category') {
-            foreach ($children as $id => $category) {
-                $category->load_grade_item();
+        $sql = "SELECT f.*
+                  FROM {$CFG->prefix}grade_grades_final f, {$CFG->prefix}grade_items gi
+                 WHERE gi.id = f.itemid AND gi.courseid={$grade_item->courseid} AND gi.id IN ($gis)
+              ORDER BY f.userid";
 
-                if ($category->grade_item->needsupdate) {
-                    $category->generate_grades();
+        if ($rs = get_recordset_sql($sql)) {
+            if ($rs->RecordCount() > 0) {
+                $prevuser = 0;
+                $grades   = array();
+                while ($subfinal = rs_fetch_next_record($rs)) {
+                    if ($subfinal->userid != $prevuser) {
+                        $this->aggregate_grades($prevuser, $items, $grades, $grade_item, $dependson);
+                        $prevuser = $subfinal->userid;
+                        $grades   = array();
+                    }
+                    $grades[$subfinal->itemid] = $subfinal->gradevalue;
                 }
+                $this->aggregate_grades($prevuser, $items, $grades, $grade_item, $dependson);
+            }
+        }
+
+        //TODO: set grade to null for raw grades that do not have corresponding final grade
+        //      using left join
+
+        return true;
+    }
+
+    /**
+     * internal function for vategory grades aggregation
+     */
+    function aggregate_grades($userid, $items, $grades, &$grade_item, $dependson) {
+        if (empty($userid)) {
+            return;
+        }
+
+        // remove the final item we used to get all existing final grades of this category
+        unset($grades[$grade_item->id]);
 
-                $final_grades_for_aggregation[] = $category->grade_item->get_standardised_final();
+        if (empty($grades) or empty($items) or ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE)) {
+            // no grading
+            if ($raw = grade_grades_raw::fetch('itemid', $grade_item->id, 'userid', $userid)) {
+                $raw->gradevalue = null;
+                $raw->update();
             }
-        } elseif ($childrentype == 'grade_item') {
-            foreach ($children as $id => $item) {
-                if ($item->needsupdate) {
-                    $item->generate_final();
-                }
+            return;
+        }
 
-                $final_grades_for_aggregation[] = $item->get_standardised_final();
+        // normalize the grades first - all will have value 0...1
+        foreach ($grades as $k=>$v) {
+            if (is_null($v)) {
+                // null means no grade
+                unset($grades[$k]);
+                continue;
             }
+            $grades[$k] = standardise_score($v, $items[$k]->grademin, $items[$k]->grademax, 0, 1);
         }
 
-        // 3. Aggregate the grades
-        $aggregated_grades = $this->aggregate_grades($final_grades_for_aggregation);
+        //sort and limit
+        $this->apply_limit_rules($grades);
+        sort($grades, SORT_NUMERIC);
 
-        // 4. Save the resulting array of grades as raw grades
-        $this->load_grade_item();
-        $this->grade_item->save_raw($aggregated_grades);
+        if (count($grades) == 0) {
+            // no grading yet
+            if ($raw = grade_grades_raw::fetch('itemid', $grade_item->id, 'userid', $userid)) {
+                $raw->gradevalue = null;
+                $raw->update();
+            }
+            return;
+        }
 
-        // 5. Use the grade_item's generate_final method
-        $this->grade_item->generate_final();
+        switch ($this->aggregation) {
+            case GRADE_AGGREGATE_MEDIAN: // Middle point value in the set: ignores frequencies
+                $num = count($grades);
+                $halfpoint = intval($num / 2);
+
+                if($num % 2 == 0) {
+                    $gradevalue = ($grades[ceil($halfpoint)] + $grades[floor($halfpoint)]) / 2;
+                } else {
+                    $gradevalue = $grades[$halfpoint];
+                }
+                break;
+            case GRADE_AGGREGATE_MIN:
+                $gradevalue = reset($grades);
+                break;
+
+            case GRADE_AGGREGATE_MAX:
+                $gradevalue = array_pop($grades);
+                break;
+
+            case GRADE_AGGREGATE_MEAN_ALL:    // Arithmetic average of all grade items including event NULLs; NULL grade caunted as minimum
+                $num = count($dependson);     // you can calculate sum from this one if you multiply it with count($this->dependson();-)
+                $sum = array_sum($grades);
+                $gradevalue = $sum / $num;
+                break;
+
+            case GRADE_AGGREGATE_MEAN_GRADED: // Arithmetic average of all final grades, unfinished not calculated
+                $num = count($grades);
+                $sum = array_sum($grades);
+                $gradevalue = $sum / $num;
+            default:
+                break;
+        }
+
+        $raw = new grade_grades_raw(array('itemid'=>$grade_item->id, 'userid'=>$userid));
+        $raw->gradevalue = $gradevalue;
+        $raw->gradetype  = $grade_item->gradetype;
+        $raw->scaleid    = $grade_item->scaleid;
+        $raw->grademin   = 0;
+        $raw->grademax   = 1;
+
+        // recalculate the gradevalue bvack to requested range
+        $raw->gradevalue = $grade_item->adjust_grade($raw);
+
+        $raw->grademin   = $grade_item->grademin;
+        $raw->grademax   = $grade_item->grademax;
+
+        if ($raw->id) {
+            $raw->update();
+        } else {
+            $raw->insert();
+        }
 
-        return true;
     }
 
     /**
@@ -410,7 +489,7 @@ class grade_category extends grade_object {
      * @param array $grades
      * @return array Limited grades.
      */
-    function apply_limit_rules($grades) {
+    function apply_limit_rules(&$grades) {
         rsort($grades, SORT_NUMERIC);
         if (!empty($this->droplow)) {
             for ($i = 0; $i < $this->droplow; $i++) {
@@ -421,89 +500,6 @@ class grade_category extends grade_object {
                 array_pop($grades);
             }
         }
-        sort($grades, SORT_NUMERIC);
-        return $grades;
-    }
-
-    /**
-     * Given an array of arrays of values, standardised from 0 to 1 and indexed by userid,
-     * uses this category's aggregation method to
-     * compute and return a single array of grade_raw objects with the aggregated gradevalue.
-     * @param array $raw_grade_sets
-     * @return array Raw grade objects
-     */
-    function aggregate_grades($final_grade_sets) {
-        if (empty($final_grade_sets)) {
-            debugging("Could not aggregate grades: no array of grades given to aggregate.");
-            return null;
-        }
-
-        $aggregated_grades = array();
-        $pooled_grades = array();
-
-        foreach ($final_grade_sets as $setkey => $set) {
-            foreach ($set as $userid => $final_grade) {
-                $this->load_grade_item();
-                $value = standardise_score((float) $final_grade, 0, 1, $this->grade_item->grademin, $this->grade_item->grademax);
-                $pooled_grades[$userid][] = (string) $value;
-            }
-        }
-
-        foreach ($pooled_grades as $userid => $grades) {
-            $aggregated_value = null;
-
-            $grades = $this->apply_limit_rules($grades);
-
-            if (count($grades) > 1) {
-
-                switch ($this->aggregation) {
-                    case GRADE_AGGREGATE_MEAN : // Arithmetic average
-                        $num = count($grades);
-                        $sum = array_sum($grades);
-                        $aggregated_value = $sum / $num;
-                        break;
-                    case GRADE_AGGREGATE_MEDIAN : // Middle point value in the set: ignores frequencies
-                        sort($grades);
-                        $num = count($grades);
-                        $halfpoint = intval($num / 2);
-
-                        if($num % 2 == 0) {
-                            $aggregated_value = ($grades[ceil($halfpoint)] + $grades[floor($halfpoint)]) / 2;
-                        } else {
-                            $aggregated_value = $grades[$halfpoint];
-                        }
-
-                        break;
-                    case GRADE_AGGREGATE_MODE : // Value that occurs most frequently. Not always useful (all values are likely to be different)
-                        // TODO implement or reject
-                        break;
-                    case GRADE_AGGREGATE_SUM : // I don't see much point to this one either
-                        $aggregated_value = array_sum($grades);
-                        break;
-                    default:
-                        $num = count($grades);
-                        $sum = array_sum($grades);
-                        $aggregated_value = $sum / $num;
-                        break;
-                }
-            } elseif (count($grades) == 1) {
-                $aggregated_value = $grades[0];
-            } else {
-                // TODO what happens if the droplow and keephigh rules have deleted all grades?
-                $aggregated_value = 0;
-            }
-
-            $grade_raw = new grade_grades_raw();
-
-            $grade_raw->userid = $userid;
-            $grade_raw->gradevalue = $aggregated_value;
-            $grade_raw->grademin = $this->grade_item->grademin;
-            $grade_raw->grademax = $this->grade_item->grademax;
-            $grade_raw->itemid = $this->grade_item->id;
-            $aggregated_grades[$userid] = $grade_raw;
-        }
-
-        return $aggregated_grades;
     }
 
     /**
@@ -687,21 +683,19 @@ class grade_category extends grade_object {
             return false;
         }
 
-        $grade_items = get_records_select('grade_items', "iteminstance = $this->id AND itemtype = 'category'", null, '*', 0, 1);
+        $grade_item = new grade_item(array('courseid'=>$this->courseid, 'itemtype'=>'category', 'iteminstance'=>$this->id), false);
+        if (!$grade_items = $grade_item->fetch_all_using_this()) {
+            // create a new one
+            $grade_item->gradetype = GRADE_TYPE_VALUE;
+            $grade_item->insert();
+
+        } else if (count($grade_items) == 1){
+            // found existing one
+            $grade_item = reset($grade_items);
 
-        if ($grade_items){
-            $params = current($grade_items);
-            $grade_item = new grade_item($params);
         } else {
-            $grade_item = new grade_item();
-        }
-
-        // If the associated grade_item isn't yet created, do it now. But first try loading it, in case it exists in DB.
-        if (empty($grade_item->id)) {
-            $grade_item->iteminstance = $this->id;
-            $grade_item->courseid = $this->courseid;
-            $grade_item->itemtype = 'category';
-            $grade_item->insert();
+            debugging("Found more than one grade_item attached to category id:".$this->id);
+            return false;
         }
 
         return $grade_item;
index baa41bcb60017dd1b24bf64ab578ad768b4347f0..63fdee81679b2738b31ca03226aabb131954d4f2 100644 (file)
@@ -289,7 +289,7 @@ class grade_grades_raw extends grade_object {
             $this->scaleid = $this->scale->id;
             $this->grademin = 0;
             $this->scale->load_items();
-            $this->grademax = count($this->scale->scale_items);
+            $this->grademax = count($this->scale->scale_items) - 1;
         }
 
         $trackhistory = false;
@@ -340,7 +340,7 @@ class grade_grades_raw extends grade_object {
         if (!empty($this->scaleid)) {
             $this->load_scale();
             $this->scale->load_items();
-            $this->grademax = count ($this->scale->scale_items);
+            $this->grademax = count ($this->scale->scale_items) - 1;
             $this->grademin = 0;
         }
 
index 3c7f4b99201680204efec8475379e1db894aa8a9..d8d4984703013b54092305805de4eb3c6d1336f9 100644 (file)
@@ -35,31 +35,31 @@ class grade_item extends grade_object {
      * @var string $table
      */
     var $table = 'grade_items';
-    
+
     /**
      * 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', 'grade_grades_final', 'scale', 'category', 'outcome');
-  
+
     /**
      * The course this grade_item belongs to.
      * @var int $courseid
      */
     var $courseid;
-    
+
     /**
      * The category this grade_item belongs to (optional).
-     * @var int $categoryid 
+     * @var int $categoryid
      */
     var $categoryid;
-    
+
     /**
      * The grade_category object referenced by $this->categoryid or $this->iteminstance (itemtype must be == 'category' in that case).
-     * @var object $category 
+     * @var object $category
      */
     var $category;
-    
+
     /**
      * A grade_category object this item used to belong to before getting updated. Will be deleted shortly.
      * @var object $old_parent
@@ -71,31 +71,31 @@ class grade_item extends grade_object {
      * @var string $itemname
      */
     var $itemname;
-    
+
     /**
      * e.g. 'mod', 'blocks', 'import', 'calculate' etc...
-     * @var string $itemtype 
+     * @var string $itemtype
      */
     var $itemtype;
-    
+
     /**
      * The module pushing this grade (e.g. 'forum', 'quiz', 'assignment' etc).
      * @var string $itemmodule
      */
     var $itemmodule;
-    
+
     /**
      * ID of the item module
      * @var int $iteminstance
      */
     var $iteminstance;
-    
+
     /**
      * Number of the item in a series of multiple grades pushed by an activity.
      * @var int $itemnumber
      */
     var $itemnumber;
-    
+
     /**
      * Info and notes about this item.
      * @var string $iteminfo
@@ -113,31 +113,31 @@ class grade_item extends grade_object {
      * @var int $gradetype
      */
     var $gradetype;
-    
+
     /**
      * Maximum allowable grade.
      * @var float $grademax
      */
     var $grademax;
-    
+
     /**
      * Minimum allowable grade.
      * @var float $grademin
      */
     var $grademin;
-    
+
     /**
      * 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 id of the optional grade_outcome associated with this grade_item.
      * @var int $outcomeid
@@ -149,43 +149,43 @@ class grade_item extends grade_object {
      * @var object $outcome
      */
     var $outcome;
-    
+
     /**
      * grade required to pass. (grademin < gradepass <= grademax)
      * @var float $gradepass
      */
     var $gradepass;
-    
+
     /**
      * Multiply all grades by this number.
      * @var float $multfactor
      */
     var $multfactor;
-    
+
     /**
      * Add this to all grades.
      * @var float $plusfactor
      */
     var $plusfactor;
-    
+
     /**
      * Sorting order of the columns.
      * @var int $sortorder
      */
     var $sortorder;
-    
+
     /**
      * Date until which to hide this grade_item. If null, 0 or false, grade_item is not hidden. Hiding prevents viewing.
      * @var int $hidden
      */
     var $hidden;
-    
+
     /**
      * Date until which to lock this grade_item. If null, 0 or false, grade_item is not locked. Locking prevents updating.
      * @var int $locked
      */
     var $locked = false;
-    
+
     /**
      * Whether or not the module instance referred to by this grade_item has been deleted.
      * @var int $deleted
@@ -203,7 +203,7 @@ class grade_item extends grade_object {
      * @var string $calculation
      */
     var $calculation;
-    
+
     /**
      * Array of grade_grades_raw objects linked to this grade_item. They are indexed by userid.
      * @var array $grade_grades_raw
@@ -224,7 +224,7 @@ class grade_item extends grade_object {
     function grade_item($params=NULL, $fetch=true) {
         $this->grade_object($params, $fetch);
     }
-   
+
     /**
      * In addition to update() as defined in grade_object, handle the grade_outcome and grade_scale objects.
      */
@@ -250,26 +250,18 @@ class grade_item extends grade_object {
         if ($this->gradetype == GRADE_TYPE_SCALE and !empty($this->scaleid)) {
             $this->load_scale();
 
-            if (!method_exists($this->scale, 'load_items')) {
-                debugging("The scale referenced by this grade_item ($this->scaleid) does not exist in the database. Grademax cannot be infered from the missing scale.");
-                return false;
-            }
-
-            $this->scale->load_items();
-            $this->grademax = count ($this->scale->scale_items);
-            $this->grademin = 0;
         } else {
             $this->scaleid = NULL;
             $this->scale = NULL;
         }
-        
+
         $qualifies = $this->qualifies_for_update();
 
         $result = parent::update();
-        
+
         if ($result && $qualifies) {
             $category = $this->get_category();
-            
+
             if (!empty($category)) {
                 $result = $result && $category->flag_for_update();
             }
@@ -290,7 +282,7 @@ class grade_item extends grade_object {
         }
 
         $db_item = new grade_item(array('id' => $this->id));
-        
+
         $gradetypediff = $db_item->gradetype != $this->gradetype;
         $grademaxdiff = $db_item->grademax != $this->grademax;
         $grademindiff = $db_item->grademin != $this->grademin;
@@ -320,7 +312,7 @@ class grade_item extends grade_object {
      * @param string $fields
      * @return object grade_item 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_item = get_record('grade_items', $field1, $value1, $field2, $value2, $field3, $value3, $fields)) {
             if (isset($this) && get_class($this) == 'grade_item') {
                 foreach ($grade_item as $param => $value) {
@@ -332,7 +324,7 @@ class grade_item extends grade_object {
                 $grade_item = new grade_item($grade_item);
                 return $grade_item;
             }
-        } else { 
+        } else {
             return false;
         }
     }
@@ -351,7 +343,7 @@ class grade_item extends grade_object {
         }
         return $result;
     }
-    
+
     /**
      * In addition to perform parent::insert(), this calls the grade_item's category's (if applicable) flag_for_update() method.
      * @return int ID of the new grade_item record.
@@ -359,6 +351,9 @@ class grade_item extends grade_object {
     function insert() {
         global $CFG;
 
+        // all new grade_items must be recalculated
+        $this->needsupdate = true;
+
         if (!isset($this->gradetype)) {
             $this->gradetype = GRADE_TYPE_VALUE;
         }
@@ -370,9 +365,7 @@ class grade_item extends grade_object {
         // Retrieve scale and infer grademax from it
         if ($this->gradetype == GRADE_TYPE_SCALE and !empty($this->scaleid)) {
             $this->load_scale();
-            $this->scale->load_items();
-            $this->grademax = count ($this->scale->scale_items);
-            $this->grademin = 0;
+
         } else {
             $this->scaleid = NULL;
             $this->scale = NULL;
@@ -383,7 +376,7 @@ class grade_item extends grade_object {
             $this->load_category();
             $this->courseid = $this->category->courseid;
         }
-       
+
         // If sortorder not given, extrapolate one
         if (empty($this->sortorder)) {
             $last_sortorder = get_field_select('grade_items', 'MAX(sortorder)', '');
@@ -399,16 +392,16 @@ class grade_item extends grade_object {
             if (!empty($this->itemmodule) && !empty($this->iteminstance)) {
                 $this->idnumber = "$this->itemmodule.$this->iteminstance";
             } else { // No itemmodule or iteminstance, generate a random idnumber
-                $this->idnumber = rand(0,9999999999); // TODO replace rand() with proper random generator 
+                $this->idnumber = rand(0,9999999999); // TODO replace rand() with proper random generator
             }
         }
-        
+
         // If a grade_item already exists with these itemtype, itemmodule and iteminstance
-        // but not itemnumber, generate an itemnumber.  
+        // but not itemnumber, generate an itemnumber.
         if (empty($this->itemnumber) && !empty($this->itemtype) && !empty($this->itemmodule) && !empty($this->iteminstance)) {
-            $existing_item = get_record('grade_items', 
-                'iteminstance', $this->iteminstance, 
-                'itemmodule', $this->itemmodule, 
+            $existing_item = get_record('grade_items',
+                'iteminstance', $this->iteminstance,
+                'itemmodule', $this->itemmodule,
                 'itemtype', $this->itemtype);
 
             if (empty($existing_item->itemnumber)) {
@@ -437,64 +430,6 @@ class grade_item extends grade_object {
         return $result;
     }
 
-    /**
-     * Takes an array of grade_grades_raw objects, indexed by userid, and saves each as a raw grade
-     * under this grade_item. This replaces any existing grades, after having logged each change in the history table.
-     * @param array $raw_grades
-     * @return boolean success or failure
-     */
-    function save_raw($raw_grades, $howmodified='module', $note=NULL) {
-        if (!empty($raw_grades) && is_array($raw_grades)) {
-            $this->load_raw();
-            
-            foreach ($raw_grades as $userid => $raw_grade) {
-                if (!empty($this->grade_grades_raw[$userid])) {
-                    $raw_grade->update($raw_grade->gradevalue, $howmodified, $note);
-                } else {
-                    $raw_grade->itemid = $this->id;
-                    if ($raw_grade->gradevalue > $raw_grade->grademax) {
-                        die("raw GRADE EXCEEDED grademax FIRST");
-                    }
-                    $raw_grade->insert();
-                }
-
-                $this->grade_grades_raw[$userid] = $raw_grade;
-            }
-        } else {
-            debugging("The data given to grade_item::save_raw($raw_grades) was not valid, it must be an array of raw grades.");
-            return false;
-        }
-    }
-
-    /**
-     * Once the raw_grades are imported or entered, this method uses the grade_item's calculation and rules to 
-     * generate final grade entries in the DB.
-     * @return array final grade objects (grade_grades_final).
-     */
-    function generate_final() {
-        if (empty($this->grade_grades_raw)) {
-            $this->load_raw();
-        }
-        
-        $success = true;
-        
-        foreach ($this->grade_grades_raw as $raw_grade) {
-            $final_grade = new grade_grades_final();
-            $final_grade->gradevalue = $this->adjust_grade($raw_grade);
-            $final_grade->itemid = $this->id;
-            $final_grade->userid = $raw_grade->userid;
-            
-            if ($final_grade->gradevalue > $this->grademax) {
-                debugging("FINAL GRADE EXCEEDED grademax FIRST");
-                return false;
-            }
-            $success = $success & $final_grade->insert();
-            $this->grade_grades_final[$final_grade->userid] = $final_grade;
-        }
-        
-        return $success;
-    }
-
     /**
      * Returns the locked state of this grade_item (if the grade_item is locked OR no specific
      * $userid is given) or the locked state of a specific grade within this item if a specific
@@ -511,16 +446,16 @@ class grade_item extends grade_object {
             return $final->locked;
         }
     }
-    
+
     /**
-     * Locks or unlocks this grade_item and (optionally) all its associated final grades. 
+     * Locks or unlocks this grade_item and (optionally) all its associated final grades.
      * @param boolean $update_final Whether to update final grades too
      * @param boolean $new_state Optional new state. Will use inverse of current state otherwise.
      * @return int Number of final grades changed, or false if error occurred during update.
      */
     function toggle_locking($update_final=false, $new_state=NULL) {
         $this->locked = !$this->locked;
-        
+
         if (!empty($new_state)) {
             $this->locked = $new_state;
         }
@@ -529,9 +464,9 @@ class grade_item extends grade_object {
             debugging("Could not update this grade_item's locked state in the database.");
             return false;
         }
-        
+
         $count = 0;
-        
+
         if ($update_final) {
             $this->load_final();
             foreach ($this->grade_grades_final as $id => $final) {
@@ -549,14 +484,14 @@ class grade_item extends grade_object {
     }
 
     /**
-     * Locks or unlocks this grade_item and (optionally) all its associated final grades. 
+     * Locks or unlocks this grade_item and (optionally) all its associated final grades.
      * @param boolean $update_final Whether to update final grades too
      * @param boolean $new_state Optional new state. Will use inverse of current state otherwise.
      * @return int Number of final grades changed, or false if error occurred during update.
      */
     function toggle_hiding($update_final=false, $new_state=NULL) {
         $this->hidden = !$this->hidden;
-        
+
         if (!empty($new_state)) {
             $this->hidden = $new_state;
         }
@@ -565,9 +500,9 @@ class grade_item extends grade_object {
             debugging("Could not update this grade_item's hidden state in the database.");
             return false;
         }
-        
+
         $count = 0;
-        
+
         if ($update_final) {
             $this->load_final();
             foreach ($this->grade_grades_final as $id => $final) {
@@ -583,66 +518,87 @@ class grade_item extends grade_object {
 
         return $count;
     }
-    
-    
+
+
     /**
      * Performs the necessary calculations on the grades_final referenced by this grade_item,
-     * and stores the results in grade_grades_final. Performs this for only one userid if 
-     * requested. Also resets the needs_update flag once successfully performed.
+     * and stores the results in grade_grades_final. Also resets the needsupdate flag
+     * once successfully performed.
      *
-     * @param int $userid
-     * @return int Number of grades updated, or false if error
+     * This function must be use ONLY from lib/gradeslib.php/grade_update_final_grades(),
+     * because the calculation must be done in correct order!!
+     *
+     * @return boolean true if ok, array of errors otherwise
      */
-    function update_final_grade($userid=NULL) {
-        if (empty($this->grade_grades_final)) {
-            $this->load_final();
-        }
-        if (empty($this->grade_grades_raw)) {
-            $this->load_raw();
-        }
-        
-        $count = 0;
-        
-        $grade_final_array = array();
-        $grade_raw_array   = array();
+    function update_final_grade() {
+        global $CFG;
 
-        if (!empty($userid)) {
-            $grade_final_array[$userid] = $this->grade_grades_final[$userid];
-            $grade_raw_array[$userid] = $this->grade_grades_raw[$userid];
-        } else {
-            $grade_final_array = $this->grade_grades_final;
-            $grade_raw_array = $this->grade_grades_raw;
-        }
+        $errors = array();
 
-        // The following code assumes that there is a grade_final object in DB for every
-        // grade_raw object. This assumption depends on the correct creation of grade_final entries.
-        // This also assumes that the two arrays $this->grade_grades_raw and its final counterpart are
-        // indexed by userid, not sequentially or by grade_id
-        if (count($this->grade_grades_final) != count($this->grade_grades_raw)) {
-            $this->generate_final();
-        }
+        if ($this->get_calculation()) {
+            // this is calculated grade
+            $this->upgrade_calculation_to_object();
+            if ($this->calculation->compute()) {
+                $this->needsupdate = false;
+                $this->update();
+                return true;
+            } else {
+                $errors[] = "Could not calculate grades for grade item id:".$this->id; // TODO: improve and localize
+            }
 
-        foreach ($grade_raw_array as $userid => $raw) {
-            $newgradevalue = $raw->gradevalue;
-            
-            if (!empty($this->calculation)) {
-                $this->upgrade_calculation_to_object();
-                $newgradevalue = $this->calculation->compute($raw->gradevalue);
+        } else if ($this->itemtype == 'category') {
+            // aggregate category grade item
+            $category = $this->get_category();
+            if (!$category->generate_grades()) {
+                $errors[] = "Could not calculate category grade item id:".$this->id; // TODO: improve and localize
             }
-            
-            $final = $this->grade_grades_final[$userid];
+        }
 
-            $final->gradevalue = $this->adjust_grade($raw, $newgradevalue);
-            
-            if ($final->update()) {
-                $count++;
-            } else {
-                debugging("Could not update a final grade in this grade_item.");
-                return false;
+        $sql = "SELECT r.*, f.userid AS fuserid, f.id AS fid, f.itemid AS fitemid, f.gradevalue AS fgradevalue
+                  FROM {$CFG->prefix}grade_grades_raw r
+                       LEFT JOIN {$CFG->prefix}grade_grades_final f ON f.itemid=r.itemid AND f.userid=r.userid
+                 WHERE r.itemid={$this->id}";
+
+        if ($rs = get_recordset_sql($sql)) {
+            if ($rs->RecordCount() > 0) {
+                while ($grade = rs_fetch_next_record($rs)) {
+                    if (!empty($errors) or is_null($grade->gradevalue)) {
+                        // unset existing final grade when no raw present or error
+                        $final = grade_grades_final::fetch('id', $grade->fid);
+                        $final->gradevalue = NULL;
+                        $final->update();
+                        continue;
+                    }
+
+                    $finalvalue = $this->adjust_grade($grade);
+
+                    if (is_null($grade->fid)) {
+                        // final grade does not exist yet
+                        $final = new grade_grades_final(array('itemid'=>$grade->itemid, 'userid'=>$grade->userid, 'gradevalue'=>$finalvalue, false));
+                        $final->insert();
+
+                    } else if ($finalvalue != $grade->fgradevalue) {
+                        // let's update the final grade
+                        $final = grade_grades_final::fetch('id', $grade->fid);
+                        $final->gradevalue = $finalvalue;
+                        $final->update();
+                    }
+                }
             }
         }
 
-        return $count;
+        //TODO: set grade to null for final grades that do not have corresponding raw grade
+        //      using left join SQL update only
+
+        if (!empty($errors)) {
+            $this->flag_for_update();
+            return $errors;
+
+        } else {
+            $this->needsupdate = false;
+            $this->update();
+            return true;
+        }
     }
 
     /**
@@ -656,58 +612,70 @@ class grade_item extends grade_object {
     }
 
     /**
-     * Given a float grade value or integer grade scale, applies a number of adjustment based on 
+     * Given a float grade value or integer grade scale, applies a number of adjustment based on
      * grade_item variables and returns the result.
-     * @param object $grade_raw The raw object to compare with this grade_item's rules
-     * @param mixed $gradevalue The new gradevalue (after calculations are performed).
-     *                          If null, the raw_grade's gradevalue will be used.
-     * @return mixed 
-     */
-    function adjust_grade($grade_raw, $gradevalue=NULL) {
-        $raw_offset = 0;
-        $item_offset = 0;
-        
+     * @param object $rawgrade The raw grade.
+     * @return mixed
+     */
+    function adjust_grade($rawgrade) {
+        $gradevalue = $rawgrade->gradevalue;
+
         if ($this->gradetype == GRADE_TYPE_VALUE) { // Dealing with numerical grade
-            if (empty($gradevalue)) {
-                $gradevalue = $grade_raw->gradevalue;
+
+            if ($this->grademax < $this->grademin) {
+                return null;
             }
 
-        } elseif($this->gradetype == GRADE_TYPE_SCALE) { // Dealing with a scale value
-            if (empty($gradevalue)) {
-                $gradevalue = $grade_raw->gradevalue;
+            if ($this->grademax == $this->grademin) {
+                return $this->grademax; // no range
             }
-            
-            // In case the scale objects haven't been loaded, do it now
-            if (empty($grade_raw->scale)) {
-                $grade_raw->load_scale();
+
+            // Standardise score to the new grade range
+            // NOTE: this is not compatible with current assignment grading
+            if ($rawgrade->grademin != $this->grademin or $rawgrade->grademax != $this->grademax) { 
+                $gradevalue = standardise_score($gradevalue, $rawgrade->grademin, $rawgrade->grademax, $this->grademin, $this->grademax);
             }
-            
+
+            // Apply other grade_item factors
+            $gradevalue *= $this->multfactor;
+            $gradevalue += $this->plusfactor;
+
+            return bounded_number($this->grademin, $gradevalue, $this->grademax);
+
+        } else if($this->gradetype == GRADE_TYPE_SCALE) { // Dealing with a scale value
             if (empty($this->scale)) {
                 $this->load_scale();
             }
+            
+            if ($this->grademax < 0) {
+                return null; // scale not present - no grade
+            }
+
+            if ($this->grademax == 0) {
+                return $this->grademax; // only one option
+            }
+
+            // Convert scale if needed
+            // NOTE: this is not compatible with current assignment grading
+            if ($rawgrade->grademax != $this->grademax and $rawgrade->grademax > 0) {
+                $gradevalue = standardise_score($gradevalue, $rawgrade->grademin, $rawgrade->grademax, $this->grademin, $this->grademax);
+            }
+
+            return (int)bounded_number(0, round($gradevalue), $this->grademax);
 
-            $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;
 
-        } elseif ($this->gradetype != GRADE_TYPE_TEXT) { // Something's wrong, the raw grade has no value!?
-            return "Error: The gradeitem did not have a valid gradetype value, was $this->gradetype instead";
+        } else if ($this->gradetype == GRADE_TYPE_TEXT or $this->gradetype == GRADE_TYPE_NONE) { // no value
+            // somebody changed the grading type when grades already existed
+            return null;
+
+        } else {
+            dubugging("Unkown grade type");
+            return null;;
         }
-           
-        // Standardise score to the new grade range
-        $gradevalue = standardise_score($gradevalue, $grade_raw->grademin, 
-                $grade_raw->grademax, $this->grademin, $this->grademax);
 
-        // Apply factors, depending on whether it's a scale or value
-        if ($this->gradetype == GRADE_TYPE_VALUE) {
-            // Apply other grade_item factors
-            $gradevalue *= $this->multfactor;
-            $gradevalue += $this->plusfactor;
-        }        
-        return $gradevalue;
-    } 
-    
+
+    }
+
     /**
      * Sets this grade_item's needsupdate to true. Also looks at parent category, if any, and calls
      * its flag_for_update() method.
@@ -738,42 +706,43 @@ class grade_item extends grade_object {
         $this->old_parent = $this->get_category();
         $this->category = null;
         $this->categoryid = null;
-        return $this->update();        
+        return $this->update();
     }
 
     /**
-     * Instantiates a grade_scale object whose data is retrieved from the DB, 
+     * Instantiates a grade_scale object whose data is retrieved from the DB,
      * if this item's scaleid variable is set.
      * @return object grade_scale
      */
     function load_scale() {
         if (!empty($this->scaleid)) {
             $this->scale = grade_scale::fetch('id', $this->scaleid);
-            if (method_exists($this->scale, 'load_items')) {
-                $this->scale->load_items();
-            } else { 
-                $this->scale = null;
-            } 
-        } 
+            $this->scale->load_items();
+            $this->grademax = count($this->scale->scale_items) - 1;
+            $this->grademin = 0;
+        } else {
+            $this->scale = null;
+        }
+
         return $this->scale;
     }
 
     /**
-     * Instantiates a grade_outcome object whose data is retrieved from the DB, 
+     * Instantiates a grade_outcome object whose data is retrieved from the DB,
      * if this item's outcomeid variable is set.
      * @return object grade_outcome
      */
     function load_outcome() {
         if (!empty($this->outcomeid)) {
             $this->outcome = grade_outcome::fetch('id', $this->outcomeid);
-        } 
+        }
         return $this->outcome;
     }
-    
+
     /**
      * Loads all the grade_grades_raw objects for this grade_item from the DB into grade_item::$grade_grades_raw array.
      * @return array grade_grades_raw objects
-     */      
+     */
     function load_raw() {
         $grade_raw_array = get_records('grade_grades_raw', 'itemid', $this->id);
 
@@ -789,47 +758,24 @@ class grade_item extends grade_object {
 
     /**
      * Loads all the grade_grades_final objects for this grade_item from the DB into grade_item::$grade_grades_final array.
-     * @param boolean $generatefakenullgrades If set to true, AND $CFG->usenullgrades is true, will replace missing grades with grades, gradevalue=grademin
      * @return array grade_grades_final objects
-     */      
-    function load_final($generatefakenullgrades=false) {
+     */
+    function load_final() {
         global $CFG;
 
+        $this->grade_grades_final = array();
+
         $grade_final_array = get_records('grade_grades_final', 'itemid', $this->id);
-        
-        if (empty($grade_final_array)) {
-            $this->generate_final();
-            $grade_final_array = get_records('grade_grades_final', 'itemid', $this->id);
-        }
-        
+
         if (empty($grade_final_array)) {
-            debugging("No final grades recorded for this grade_item");
-            return false;
+            return array();
         }
 
         foreach ($grade_final_array as $f) {
             $this->grade_grades_final[$f->userid] = new grade_grades_final($f);
         }
 
-        $returnarray = fullclone($this->grade_grades_final);
-
-        // If we are generating fake null grades, we have to get a list of users
-        if ($generatefakenullgrades && $CFG->usenullgrades) {
-            $users = get_records_sql_menu('SELECT userid AS "user", userid FROM ' . $CFG->prefix . 'grade_grades_final GROUP BY userid ORDER BY userid');
-            if (!empty($users) && is_array($users)) {
-                foreach ($users as $userid) {
-                    if (!isset($returnarray[$userid])) {
-                        $fakefinal = new grade_grades_final();
-                        $fakefinal->itemid = $this->id;
-                        $fakefinal->userid = $userid;
-                        $fakefinal->gradevalue = $this->grademin;
-                        $returnarray[$userid] = $fakefinal;
-                    }
-                }
-            }
-        }
-
-        return $returnarray;
+        return fullclone($this->grade_grades_final);
     }
 
     /**
@@ -839,8 +785,8 @@ class grade_item extends grade_object {
     function get_standardised_final() {
         $standardised_finals = array();
 
-        $final_grades = $this->load_final(true);
-        
+        $final_grades = $this->load_final();
+
         if (!empty($final_grades)) {
             foreach ($final_grades as $userid => $final) {
                 $standardised_finals[$userid] = standardise_score($final->gradevalue, $this->grademin, $this->grademax, 0, 1);
@@ -852,23 +798,23 @@ class grade_item extends grade_object {
 
     /**
     * Returns the grade_category object this grade_item belongs to (if any).
-    * This category object may be the parent (referenced by categoryid) or the associated category 
+    * This category object may be the parent (referenced by categoryid) or the associated category
     * (referenced by iteminstance).
-    * 
+    *
     * @return mixed grade_category object if applicable, NULL otherwise
     */
     function get_category() {
         $category = null;
-        
+
         if (!empty($this->categoryid)) {
             $category = grade_category::fetch('id', $this->categoryid);
         } elseif (!empty($this->iteminstance) && $this->itemtype == 'category') {
             $category = grade_category::fetch('id', $this->iteminstance);
         }
-        
+
         return $category;
     }
-    
+
     /**
      * Calls upon the get_category method to retrieve the grade_category object
      * from the DB and assigns it to $this->category. It also returns the object.
@@ -882,50 +828,46 @@ class grade_item extends grade_object {
     /**
      * Returns this object's calculation.
      * @param boolean $fetch Whether to fetch the value from the DB or not (false == just use the object's value)
-     * @return mixed $calculation A string if found, false otherwise.
+     * @return mixed $calculation Object if found, false otherwise.
      */
     function get_calculation($fetch = false) {
-        if (!$fetch && get_class($this->calculation) == 'grade_calculation') {
-            return $this->calculation;
-        } 
-        $grade_calculation = grade_calculation::fetch('itemid', $this->id);
-            
-        if (empty($grade_calculation)) { // There is no calculation in DB
-            return false;
-        } elseif (empty($this->calculation) || !is_object($this->calculation)) { // The calculation isn't yet loaded
-            $this->calculation = $grade_calculation;
-            return $grade_calculation;
-        } elseif ($grade_calculation->calculation != $this->calculation->calculation) { // The object's calculation is not in sync with the DB (new value??)
-            $this->calculation = $grade_calculation;
-            return $grade_calculation;
-        } else { // The object's calculation is already in sync with the database
-            return $this->calculation;
+        if (is_null($this->calculation)) {
+            $fetch = true;
         }
+
+        if ($fetch) {
+            $this->calculation = grade_calculation::fetch('itemid', $this->id);
+        }
+
+        return $this->calculation;
     }
 
     /**
      * Sets this item's calculation (creates it) if not yet set, or
      * updates it if already set (in the DB). If no calculation is given,
-     * the method will attempt to retrieve one from the Database, based on
-     * the variables set in the current object.
-     * @param string $calculation
+     * the calculation is removed.
+     * @param string $formula
      * @return boolean
      */
-    function set_calculation($calculation = null) {
-        if (empty($calculation)) { // We are setting this item object's calculation variable from the DB
-            $grade_calculation = $this->get_calculation(true);
-            if (empty($grade_calculation)) {
-                debugging("No calculation to set for this grade_item.");
-                return false;
-            } else {
-                $this->calculation = $grade_calculation;
+    function set_calculation($formula) {
+        // remove cached calculation object
+        $this->calculation = null;
+
+        if (empty($formula)) { // We are removing this calculation
+            if (!empty($this->id)) {
+                if ($grade_calculation = $this->get_calculation()) {
+                    $grade_calculation->delete();
+                }
             }
+            $this->calculation = null;
+            $status = true;
+
         } else { // We are updating or creating the calculation entry in the DB
             $grade_calculation = $this->get_calculation();
-            
+
             if (empty($grade_calculation)) { // Creating
                 $grade_calculation = new grade_calculation();
-                $grade_calculation->calculation = $calculation;
+                $grade_calculation->calculation = $formula;
                 $grade_calculation->itemid = $this->id;
 
                 if ($grade_calculation->insert()) {
@@ -934,16 +876,19 @@ class grade_item extends grade_object {
                 } else {
                     debugging("Could not save the calculation in the database, for this grade_item.");
                     return false;
-                }                
+                }
             } else { // Updating
-                $grade_calculation->calculation = $calculation;
+                $grade_calculation->calculation = $formula;
                 $grade_calculation = new grade_calculation($grade_calculation);
                 $this->calculation = $grade_calculation;
-                return $grade_calculation->update();
+                $status = $grade_calculation->update();
             }
         }
+
+        $this->flag_for_update();
+        return $status;
     }
-    
+
     /**
      * 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
@@ -963,7 +908,7 @@ class grade_item extends grade_object {
         }
         return $grade_raw_array;
     }
-    
+
     /**
      * Returns the final values for this grade item (as imported by module or other source).
      * @param int $userid Optional: to retrieve a single final grade
@@ -974,19 +919,21 @@ class grade_item extends grade_object {
             $this->load_final();
         }
 
-        $grade_final_array = null;
-        if (!empty($userid)) {
-            $f = get_record('grade_grades_final', 'itemid', $this->id, 'userid', $userid);
-            $grade_final_array[$f->userid] = new grade_grades_final($f);
+        if (empty($userid)) {
+            return $this->grade_grades_final;
+
         } else {
-            $grade_final_array = $this->grade_grades_final;
+            if (array_key_exists($userid, $this->grade_grades_final)) {
+                return $this->grade_grades_final[$userid];
+            } else {
+                return new grade_grades_final(array('itemid'=>$this->itemid, 'gradevalue'=>NULL, 'userid'=>$userid));
+            }
         }
-        return $grade_final_array;
     }
 
     /**
-     * Returns the sortorder of this grade_item. This method is also available in 
-     * grade_category, for cases where the object type is not know. It will act as a virtual 
+     * Returns the sortorder of this grade_item. This method is also available in
+     * grade_category, for cases where the object type is not know. It will act as a virtual
      * variable for a grade_category.
      * @return int Sort order
      */
@@ -995,8 +942,8 @@ class grade_item extends grade_object {
     }
 
     /**
-     * Sets the sortorder of this grade_item. This method is also available in 
-     * grade_category, for cases where the object type is not know. It will act as a virtual 
+     * Sets the sortorder of this grade_item. This method is also available in
+     * grade_category, for cases where the object type is not know. It will act as a virtual
      * variable for a grade_category.
      * @param int $sortorder
      * @return void
@@ -1006,14 +953,14 @@ class grade_item extends grade_object {
     }
 
     /**
-     * Returns the most descriptive field for this object. This is a standard method used 
+     * Returns the most descriptive field for this object. This is a standard method used
      * when we do not know the exact type of an object.
      * @return string name
      */
     function get_name() {
         return $this->itemname;
-    } 
-    
+    }
+
     /**
      * Returns this grade_item's id. This is specified for cases where we do not
      * know an object's type, and want to get either an item's id or a category's item's id.
@@ -1039,10 +986,10 @@ class grade_item extends grade_object {
     function set_parent_id($parentid) {
         $this->categoryid = $parentid;
     }
-    
+
     /**
-     * Returns the locked state/date of this grade_item. This method is also available in 
-     * grade_category, for cases where the object type is not known. 
+     * Returns the locked state/date of this grade_item. This method is also available in
+     * grade_category, for cases where the object type is not known.
      * @return int 0, 1 or timestamp int(10)
      */
     function get_locked() {
@@ -1058,10 +1005,10 @@ class grade_item extends grade_object {
         $this->locked = $locked;
         return $this->update();
     }
-    
+
     /**
-     * Returns the hidden state/date of this grade_item. This method is also available in 
-     * grade_category, for cases where the object type is not known. 
+     * Returns the hidden state/date of this grade_item. This method is also available in
+     * grade_category, for cases where the object type is not known.
      * @return int 0, 1 or timestamp int(10)
      */
     function get_hidden() {
@@ -1069,16 +1016,16 @@ class grade_item extends grade_object {
     }
 
     /**
-     * Sets the grade_item's hidden variable and updates the grade_item. 
+     * Sets the grade_item's hidden variable and updates the grade_item.
      * @param int $hidden 0, 1 or a timestamp int(10) after which date the item will be hidden.
      * @return void
      */
     function set_hidden($hidden) {
         $this->hidden = $hidden;
-        return $this->update(); 
+        return $this->update();
     }
 
-    /** 
+    /**
      * If the old parent is set (after an update), this checks and returns whether it has any children. Important for
      * deleting childless categories.
      * @return boolean
@@ -1089,6 +1036,45 @@ class grade_item extends grade_object {
         } else {
             return false;
         }
-    } 
+    }
+
+    /**
+     * Finds out on which other items does this depend directly when doing calculation or category agregation
+     * @return array of grade_item ids this one depends on
+     */
+    function dependson() {
+
+        if ($this->get_calculation()) {
+            $this->upgrade_calculation_to_object();
+            return $this->calculation->dependson();
+
+        } else if ($this->itemtype == 'category') {
+            $grade_category = grade_category::fetch('id', $this->iteminstance);
+            $children = $grade_category->get_children(1, 'flat');
+
+            if (empty($children)) {
+                return array();
+            }
+
+            $result = array();
+
+            $childrentype = get_class(reset($children));
+            if ($childrentype == 'grade_category') {
+                foreach ($children as $id => $category) {
+                    $grade_item = $category->get_grade_item();
+                    $result[] = $grade_item->id;
+                }
+            } elseif ($childrentype == 'grade_item') {
+                foreach ($children as $id => $grade_item) {
+                    $result[] = $grade_item->id;
+                }
+            }
+
+            return $result;
+
+        } else {
+            return array();
+        }
+    }
 }
 ?>
index f91bd6c6d0e8af43a18db4b1ca6a8debc508368a..ecbeea04c48d4a0566a8123e25ca3f9b3c4e4535 100644 (file)
  * @package moodlecore
  */
 
-define('GRADE_AGGREGATE_MEAN', 0);
+define('GRADE_AGGREGATE_MEAN_ALL', 0);
 define('GRADE_AGGREGATE_MEDIAN', 1);
-define('GRADE_AGGREGATE_SUM', 2);
-define('GRADE_AGGREGATE_MODE', 3);
+define('GRADE_AGGREGATE_MEAN_GRADED', 2);
+define('GRADE_AGGREGATE_MIN', 3);
+define('GRADE_AGGREGATE_MAX', 4);
 
 define('GRADE_CHILDTYPE_ITEM', 0);
 define('GRADE_CHILDTYPE_CAT', 1);
@@ -338,6 +339,8 @@ function grade_get_items($courseid, $itemtype=NULL, $itemmodule=NULL, $iteminsta
 * @param string $aggregation
 * @return mixed New grade_category id if successful
 */
+/*
+// TODO: this should be obsoleted by grade_update() or removed completely - modules must not use any IDs or grade_item objects directly!
 function grade_create_category($courseid, $fullname, $items, $aggregation=GRADE_AGGREGATE_MEAN) {
     $grade_category = new grade_category(compact('courseid', 'fullname', 'items', 'aggregation'));
 
@@ -347,34 +350,133 @@ function grade_create_category($courseid, $fullname, $items, $aggregation=GRADE_
         return $grade_category->id;
     }
 }
+*/
 
 /**
- * Updates all grade_grades_final for each grade_item matching the given attributes.
- * The search is further restricted, so that only grade_items that have needs_update == TRUE
- * or that use calculation are retrieved.
+ * Updates all grade_grades_final in course.
  *
  * @param int $courseid
- * @param int $gradeitemid
- * @return int Number of grade_items updated
+ * @return boolean true if ok, array of errors if problems found
  */
-function grade_update_final_grades($courseid=NULL, $gradeitemid=NULL) {
+function grade_update_final_grades($courseid) {
+    $errors = array();
     $grade_item = new grade_item();
     $grade_item->courseid = $courseid;
-    $grade_item->id = $gradeitemid;
-    $grade_items = $grade_item->fetch_all_using_this();
+    if (!$grade_items = $grade_item->fetch_all_using_this()) {
+        return true;
+    }
 
-    $count = 0;
+    $needsupdate = false;
+    $calculated = false;
+    foreach ($grade_items as $gid=>$gitem) {
+        $grade_item =& $grade_items[$gid];
+        if ($grade_item->needsupdate) {
+            $needsupdate = true;
+        }
+        if ($grade_item->get_calculation()) {
+            $calculated = true;
+        }
+    }
 
-    foreach ($grade_items as $gi) {
-        $calculation = $gi->get_calculation();
-        if (!empty($calculation) || $gi->needsupdate) {
-            if ($gi->update_final_grade()) {
-                $count++;
+    // no update needed
+    if (!$needsupdate) {
+        return true;
+    }
+
+    // the easy way
+    if (!$calculated) {
+        foreach ($grade_items as $gid=>$gitem) {
+            $grade_item =& $grade_items[$gid];
+            if ($grade_item->needsupdate) {
+                $result = $grade_item->update_final_grade();
+                if ($result !== true) {
+                    $errors = array_merge($errors, $result);
+                }
             }
         }
+
+        if (count($errors) == 0) {
+            return true;
+        } else {
+            return $errors;
+        }
     }
 
-    return $count;
+    // now the hard way with calculated grade_items or categories
+    $finalitems = array();
+    $finalids = array();
+    while (count($grade_items) > 0) {
+        $count = 0;
+        foreach ($grade_items as $gid=>$gitem) {
+            $grade_item =& $grade_items[$gid];
+            if (!$grade_item->needsupdate and $grade_item->itemtype!='category' and !$grade_item->get_calculation()) {
+                $finalitems[$gid] = $grade_item;
+                $finalids[] = $gid;
+                unset($grade_items[$gid]);
+                continue;
+            }
+
+            $dependson = $grade_item->dependson();
+
+            //are we dealing with category with no calculated items?
+            // we can not trust the needsupdate flag because category might contain calculated items
+            if ($grade_item->itemtype=='category' and !$grade_item->needsupdate) {
+                $forceupdate = false;
+                foreach ($dependson as $childid) {
+                    if (in_array($childid, $finalids)) {
+                        $child = $finalitems[$childid];
+                    } else {
+                        $child = $grade_items[$childid];
+                    }
+                    if ($child->itemtype == 'category' or $child->get_calculation()) {
+                        $forceupdate = true;
+                    }
+                }
+
+                if ($forceupdate) {
+                    $grade_item->flag_for_update();
+                } else {
+                    $finalitems[$gid] = $grade_item;
+                    $finalids[] = $gid;
+                    unset($grade_items[$gid]);
+                    continue;
+                }
+            }
+
+            //do we have all data for this item?
+            $doupdate = true;
+            foreach ($dependson as $did) {
+                if (!in_array($did, $finalids)) {
+                    $doupdate = false;
+                }
+            }
+
+            //oki - let's update, calculate or aggregate :-)
+            if ($doupdate) {
+                $result = $grade_item->update_final_grade();
+                if ($result !== true) {
+                    $errors = array_merge($errors, $result);
+                } else {
+                    $finalitems[$gid] = $grade_item;
+                    $finalids[] = $gid;
+                    unset($grade_items[$gid]);
+                }
+            }
+        }
+
+        if ($count == 0) {
+            foreach($grade_items as $grade_item) {
+                $errors[] = 'Probably circular reference in grade_item id:'.$grade_item->id; // TODO: localize
+            }
+            break;
+        }
+    }
+
+    if (count($errors) == 0) {
+        return true;
+    } else {
+        return $errors;
+    }
 }
 
 /**
@@ -596,5 +698,61 @@ function standardise_score($gradevalue, $source_min, $source_max, $target_min, $
     return $standardised_value;
 }
 
+/**
+ * This function is used to migrade old date and settings from old gradebook into new grading system.
+ *
+ * TODO:
+ *   - category weight not used - we would have to create extra top course grade calculated category
+ *   - exta_credit item flag not used - does not fit all our aggregation types, could be used in SUM only
+ */
+function grade_oldgradebook_upgrade($courseid) {
+    global $CFG;
+
+    $categories = array();
+    if ($oldcats = get_records('grade_category', 'courseid', $courseid)) {
+        foreach ($oldcats as $oldcat) {
+            $newcat = new grade_category(array('courseid'=>$courseid, 'fullname'=>$oldcat->name));
+            $newcat->droplow     = $oldcat->drop_x_lowest;
+            $newcat->aggregation = GRADE_AGGREGATE_MEAN_GRADED;
+
+            if (empty($newcat->id)) {
+                $newcat->insert();
+            } else {
+                $newcat->update();
+            }
+
+            $categories[$oldcat->id] = $newcat;
+
+            $catitem = $newcat->get_grade_item();
+            $catitem->gradetype  = GRADE_TYPE_VALUE;
+            $catitem->plusfactor = $oldcat->bonus_points;
+            $catitem->hidden     = $oldcat->hidden;
+            $catitem->update();
+        }
+    }
+
+    // get all grade items with mod details
+    $sql = "SELECT gi.*, cm.idnumber as cmidnumber, m.name as modname
+              FROM {$CFG->prefix}grade_item gi, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
+             WHERE gi.courseid=$courseid AND m.id=gi.modid AND cm.instance=gi.cminstance
+          ORDER BY gi.sortorder ASC";
+
+    if ($olditems = get_records_sql($sql)) {
+        foreach ($olditems as $olditem) {
+            $newitem = new grade_item(array('courseid'=>$olditem->courseid, 'itemtype'=>'mod', 'itemmodule'=>$olditem->modname, 'iteminstance'=>$olditem->cminstance, 'itemnumber'=>0));
+            if (!empty($olditem->category)) {
+                // we do this low level stuff to get some speedup during upgrade
+                $newitem->set_parent_id($categories[$olditem->category]->id);
+            }
+            $newitem->gradetype = GRADE_TYPE_NONE;
+            $newitem->multfactor = $olditem->scale_grade;
+            if (empty($newitem->id)) {
+                $newitem->insert();
+            } else {
+                $newitem->update();
+            }
+        }
+    }
+}
 
 ?>
index edb76cfd584c5773b28f91560ba138982a47506e..e9dd1a4fe6b39be6fc041b68697f4e3b2dc9ee9d 100644 (file)
@@ -357,9 +357,11 @@ class grade_test extends UnitTestCase {
         $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();
+        $temp  = explode(',', $scale->scale);
+        $scale->max         = count($temp) -1;
         
         if ($scale->id = insert_record('scale', $scale)) {
-            $this->scale[] = $scale;
+            $this->scale[0] = $scale;
         } 
 
         $scale = new stdClass();
@@ -370,9 +372,11 @@ class grade_test extends UnitTestCase {
         $scale->scale       = 'Distinction, Very Good, Good, Pass, Fail';
         $scale->description = 'This scale is used to mark standard assignments.';
         $scale->timemodified = mktime();
+        $temp  = explode(',', $scale->scale);
+        $scale->max         = count($temp) -1;
         
         if ($scale->id = insert_record('scale', $scale)) {
-            $this->scale[] = $scale;
+            $this->scale[1] = $scale;
         } 
 
         $scale = new stdClass();
@@ -383,9 +387,11 @@ class grade_test extends UnitTestCase {
         $scale->scale       = 'Loner, Contentious, Disinterested, Participative, Follower, Leader';
         $scale->description = 'Describes the level of teamwork of a student.';
         $scale->timemodified = mktime();
+        $temp  = explode(',', $scale->scale);
+        $scale->max         = count($temp) -1;
         
         if ($scale->id = insert_record('scale', $scale)) {
-            $this->scale[] = $scale;
+            $this->scale[2] = $scale;
         } 
 
         $scale->name        = 'unittestscale4';
@@ -394,9 +400,11 @@ class grade_test extends UnitTestCase {
         $scale->scale       = 'Does not understand theory, Understands theory but fails practice, Manages through, Excels';
         $scale->description = 'Level of expertise at a technical task, with a theoretical framework.';
         $scale->timemodified = mktime();
+        $temp  = explode(',', $scale->scale);
+        $scale->max         = count($temp) -1;
         
         if ($scale->id = insert_record('scale', $scale)) {
-            $this->scale[] = $scale;
+            $this->scale[3] = $scale;
         }
 
         $scale->name        = 'unittestscale5';
@@ -405,9 +413,11 @@ class grade_test extends UnitTestCase {
         $scale->scale       = 'Insufficient, Acceptable, Excellent.';
         $scale->description = 'Description of skills.';
         $scale->timemodified = mktime();
+        $temp  = explode(',', $scale->scale);
+        $scale->max         = count($temp) -1;
         
         if ($scale->id = insert_record('scale', $scale)) {
-            $this->scale[] = $scale;
+            $this->scale[4] = $scale;
         }
     }
 
@@ -419,7 +429,7 @@ class grade_test extends UnitTestCase {
         
         $grade_category->fullname    = 'unittestcategory1';
         $grade_category->courseid    = $this->courseid;
-        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN_GRADED;
         $grade_category->keephigh    = 100;
         $grade_category->droplow     = 0;
         $grade_category->hidden      = 0;
@@ -428,14 +438,14 @@ class grade_test extends UnitTestCase {
         $grade_category->depth = 1;
         
         if ($grade_category->id = insert_record('grade_categories', $grade_category)) {
-            $this->grade_categories[] = $grade_category;
+            $this->grade_categories[0] = $grade_category;
         } 
         
         $grade_category = new stdClass();
         
         $grade_category->fullname    = 'unittestcategory2';
         $grade_category->courseid    = $this->courseid;
-        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN_GRADED;
         $grade_category->keephigh    = 100;
         $grade_category->droplow     = 0;
         $grade_category->hidden      = 0;
@@ -445,14 +455,14 @@ class grade_test extends UnitTestCase {
         $grade_category->depth = 2;
         
         if ($grade_category->id = insert_record('grade_categories', $grade_category)) {
-            $this->grade_categories[] = $grade_category;
+            $this->grade_categories[1] = $grade_category;
         } 
         
         $grade_category = new stdClass();
         
         $grade_category->fullname    = 'unittestcategory3';
         $grade_category->courseid    = $this->courseid;
-        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN_GRADED;
         $grade_category->keephigh    = 100;
         $grade_category->droplow     = 0;
         $grade_category->hidden      = 0;
@@ -462,7 +472,7 @@ class grade_test extends UnitTestCase {
         $grade_category->depth = 2;
         
         if ($grade_category->id = insert_record('grade_categories', $grade_category)) {
-            $this->grade_categories[] = $grade_category;
+            $this->grade_categories[2] = $grade_category;
         } 
         
         // A category with no parent, but grade_items as children
@@ -471,7 +481,7 @@ class grade_test extends UnitTestCase {
         
         $grade_category->fullname    = 'level1category';
         $grade_category->courseid    = $this->courseid;
-        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN_GRADED;
         $grade_category->keephigh    = 100;
         $grade_category->droplow     = 0;
         $grade_category->hidden      = 0;
@@ -480,7 +490,7 @@ class grade_test extends UnitTestCase {
         $grade_category->depth = 1;
         
         if ($grade_category->id = insert_record('grade_categories', $grade_category)) {
-            $this->grade_categories[] = $grade_category;
+            $this->grade_categories[3] = $grade_category;
         } 
     }
 
@@ -499,7 +509,7 @@ class grade_test extends UnitTestCase {
         $grade_item->iteminstance = 1;
         $grade_item->gradetype = GRADE_TYPE_VALUE;
         $grade_item->grademin = 30;
-        $grade_item->grademax = 140;
+        $grade_item->grademax = 110;
         $grade_item->itemnumber = 1;
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->timecreated = mktime();
@@ -507,7 +517,7 @@ class grade_test extends UnitTestCase {
         $grade_item->sortorder = 3;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[0] = $grade_item;
         }
         
         // id = 1
@@ -530,7 +540,7 @@ class grade_test extends UnitTestCase {
         $grade_item->sortorder = 4;
         
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[1] = $grade_item;
         }
 
         // id = 2
@@ -545,14 +555,14 @@ class grade_test extends UnitTestCase {
         $grade_item->gradetype = GRADE_TYPE_SCALE;
         $grade_item->scaleid = $this->scale[0]->id;
         $grade_item->grademin = 0;
-        $grade_item->grademax = 7;
+        $grade_item->grademax = $this->scale[0]->max;
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->timecreated = mktime();
         $grade_item->timemodified = mktime();
         $grade_item->sortorder = 6;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[2] = $grade_item;
         }
 
         // Load grade_items associated with the 3 categories
@@ -573,7 +583,7 @@ class grade_test extends UnitTestCase {
         $grade_item->sortorder = 1;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[3] = $grade_item;
         }
         
         // id = 4
@@ -593,7 +603,7 @@ class grade_test extends UnitTestCase {
         $grade_item->sortorder = 2;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[4] = $grade_item;
         }
 
         // id = 5
@@ -613,7 +623,7 @@ class grade_test extends UnitTestCase {
         $grade_item->sortorder = 5;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[5] = $grade_item;
         }
 
         // Orphan grade_item
@@ -634,7 +644,7 @@ class grade_test extends UnitTestCase {
         $grade_item->sortorder = 7;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[6] = $grade_item;
         }
 
         // 2 grade items under level1category
@@ -650,14 +660,14 @@ class grade_test extends UnitTestCase {
         $grade_item->gradetype = GRADE_TYPE_SCALE;
         $grade_item->scaleid = $this->scale[0]->id;
         $grade_item->grademin = 0;
-        $grade_item->grademax = 7;
+        $grade_item->grademax = $this->scale[0]->max;
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->timecreated = mktime();
         $grade_item->timemodified = mktime();
         $grade_item->sortorder = 9;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[7] = $grade_item;
         }
         
         // id = 8
@@ -678,7 +688,7 @@ class grade_test extends UnitTestCase {
         $grade_item->sortorder = 10;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[8] = $grade_item;
         }
         
         // Grade_item for level1category
@@ -692,15 +702,15 @@ class grade_test extends UnitTestCase {
         $grade_item->iteminstance = $this->grade_categories[3]->id;
         $grade_item->needsupdate = true;
         $grade_item->gradetype = GRADE_TYPE_VALUE;
-        $grade_item->grademin = 10;
-        $grade_item->grademax = 120;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
         $grade_item->iteminfo = 'Orphan Grade item used for unit testing';
         $grade_item->timecreated = mktime();
         $grade_item->timemodified = mktime();
         $grade_item->sortorder = 8;
 
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
-            $this->grade_items[] = $grade_item;
+            $this->grade_items[9] = $grade_item;
         }
 
     }
@@ -709,41 +719,17 @@ class grade_test extends UnitTestCase {
      * Load grade_calculation data into the database, and adds the corresponding objects to this class' variable.
      */
     function load_grade_calculations() {
-        // Calculation for grade_item 1
-        $grade_calculation = new stdClass();
-        $grade_calculation->itemid = $this->grade_items[0]->id;
-        $grade_calculation->calculation = '[unittestgradeitem1] * 1.4 - 3';
-        $grade_calculation->timecreated = mktime();
-        $grade_calculation->timemodified = mktime();
-        
-        if ($grade_calculation->id = insert_record('grade_calculations', $grade_calculation)) {
-            $this->grade_calculations[] = $grade_calculation;
-            $this->grade_items[0]->calculation = $grade_calculation;
-        } 
-        
         // Calculation for grade_item 2
         $grade_calculation = new stdClass();
         $grade_calculation->itemid = $this->grade_items[1]->id;
-        $grade_calculation->calculation = '[unittestgradeitem2] + 3';
+        $grade_calculation->calculation = '= gi'.$this->grade_items[0]->id.'* + 30 ';
         $grade_calculation->timecreated = mktime();
         $grade_calculation->timemodified = mktime();
         
         if ($grade_calculation->id = insert_record('grade_calculations', $grade_calculation)) {
-            $this->grade_calculations[] = $grade_calculation;
+            $this->grade_calculations[0] = $grade_calculation;
             $this->grade_items[1]->calculation = $grade_calculation;
         } 
-        
-        // Calculation for grade_item 3
-        $grade_calculation = new stdClass();
-        $grade_calculation->itemid = $this->grade_items[2]->id;
-        $grade_calculation->calculation = '[unittestgradeitem3] / 2 + 40';
-        $grade_calculation->timecreated = mktime();
-        $grade_calculation->timemodified = mktime();
-        
-        if ($grade_calculation->id = insert_record('grade_calculations', $grade_calculation)) {
-            $this->grade_calculations[] = $grade_calculation;
-            $this->grade_items[2]->calculation = $grade_calculation;
-        } 
     }
 
     /**
@@ -754,7 +740,7 @@ class grade_test extends UnitTestCase {
         $grade_raw = new stdClass();
         $grade_raw->itemid = $this->grade_items[0]->id;
         $grade_raw->userid = 1;
-        $grade_raw->gradevalue = 72;
+        $grade_raw->gradevalue = 15; // too small
         $grade_raw->timecreated = mktime();
         $grade_raw->timemodified = mktime();
 
@@ -765,7 +751,7 @@ class grade_test extends UnitTestCase {
         $grade_raw = new stdClass();
         $grade_raw->itemid = $this->grade_items[0]->id;
         $grade_raw->userid = 2;
-        $grade_raw->gradevalue = 78;
+        $grade_raw->gradevalue = 40;
         $grade_raw->timecreated = mktime();
         $grade_raw->timemodified = mktime();
 
@@ -776,7 +762,7 @@ class grade_test extends UnitTestCase {
         $grade_raw = new stdClass();
         $grade_raw->itemid = $this->grade_items[0]->id;
         $grade_raw->userid = 3;
-        $grade_raw->gradevalue = 68;
+        $grade_raw->gradevalue = 170; // too big
         $grade_raw->timecreated = mktime();
         $grade_raw->timemodified = mktime();
 
@@ -784,40 +770,8 @@ class grade_test extends UnitTestCase {
             $this->grade_grades_raw[] = $grade_raw;
         }
 
-        // Grades for grade_item 2
-
-        $grade_raw = new stdClass();
-        $grade_raw->itemid = $this->grade_items[1]->id;
-        $grade_raw->userid = 1;
-        $grade_raw->gradevalue = 66;
-        $grade_raw->timecreated = mktime();
-        $grade_raw->timemodified = mktime();
-
-        if ($grade_raw->id = insert_record('grade_grades_raw', $grade_raw)) {
-            $this->grade_grades_raw[] = $grade_raw;
-        }
-        
-        $grade_raw = new stdClass();
-        $grade_raw->itemid = $this->grade_items[1]->id;
-        $grade_raw->userid = 2;
-        $grade_raw->gradevalue = 84;
-        $grade_raw->timecreated = mktime();
-        $grade_raw->timemodified = mktime();
-
-        if ($grade_raw->id = insert_record('grade_grades_raw', $grade_raw)) {
-            $this->grade_grades_raw[] = $grade_raw;
-        }
-        
-        $grade_raw = new stdClass();
-        $grade_raw->itemid = $this->grade_items[1]->id;
-        $grade_raw->userid = 3;
-        $grade_raw->gradevalue = 91;
-        $grade_raw->timecreated = mktime();
-        $grade_raw->timemodified = mktime();
+        // No raw grades for grade_item 2 - it is calculated
 
-        if ($grade_raw->id = insert_record('grade_grades_raw', $grade_raw)) {
-            $this->grade_grades_raw[] = $grade_raw;
-        }
 
         // Grades for grade_item 3
 
@@ -894,45 +848,34 @@ class grade_test extends UnitTestCase {
 
         // Grades for grade_item 8
 
-        $grade_raw = new stdClass();
-        $grade_raw->itemid = $this->grade_items[7]->id;
-        $grade_raw->userid = 1;
-        $grade_raw->gradevalue = 97;
-        $grade_raw->timecreated = mktime();
-        $grade_raw->timemodified = mktime();
-
-        if ($grade_raw->id = insert_record('grade_grades_raw', $grade_raw)) {
-            $this->grade_grades_raw[] = $grade_raw;
-        }
-        
         $grade_raw = new stdClass();
         $grade_raw->itemid = $this->grade_items[7]->id;
         $grade_raw->userid = 2;
-        $grade_raw->gradevalue = 49;
+        $grade_raw->gradevalue = 3;
         $grade_raw->timecreated = mktime();
         $grade_raw->timemodified = mktime();
 
         if ($grade_raw->id = insert_record('grade_grades_raw', $grade_raw)) {
             $this->grade_grades_raw[] = $grade_raw;
         }
-        
+    
         $grade_raw = new stdClass();
         $grade_raw->itemid = $this->grade_items[7]->id;
         $grade_raw->userid = 3;
-        $grade_raw->gradevalue = 67;
+        $grade_raw->gradevalue = 6;
         $grade_raw->timecreated = mktime();
         $grade_raw->timemodified = mktime();
 
         if ($grade_raw->id = insert_record('grade_grades_raw', $grade_raw)) {
             $this->grade_grades_raw[] = $grade_raw;
         }
-        
+  
         // Grades for grade_item 9
 
         $grade_raw = new stdClass();
         $grade_raw->itemid = $this->grade_items[8]->id;
         $grade_raw->userid = 1;
-        $grade_raw->gradevalue = 49;
+        $grade_raw->gradevalue = 20;
         $grade_raw->timecreated = mktime();
         $grade_raw->timemodified = mktime();
 
@@ -943,7 +886,7 @@ class grade_test extends UnitTestCase {
         $grade_raw = new stdClass();
         $grade_raw->itemid = $this->grade_items[8]->id;
         $grade_raw->userid = 2;
-        $grade_raw->gradevalue = 93;
+        $grade_raw->gradevalue = 50;
         $grade_raw->timecreated = mktime();
         $grade_raw->timemodified = mktime();
 
@@ -954,7 +897,7 @@ class grade_test extends UnitTestCase {
         $grade_raw = new stdClass();
         $grade_raw->itemid = $this->grade_items[7]->id;
         $grade_raw->userid = 3;
-        $grade_raw->gradevalue = 76;
+        $grade_raw->gradevalue = 100;
         $grade_raw->timecreated = mktime();
         $grade_raw->timemodified = mktime();
 
@@ -972,7 +915,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[0]->id;
         $grade_final->userid = 1;
-        $grade_final->gradevalue = 97.8;
+        $grade_final->gradevalue = 30;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
 
@@ -983,7 +926,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[0]->id;
         $grade_final->userid = 2;
-        $grade_final->gradevalue = 106.2;
+        $grade_final->gradevalue = 40;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = true; 
@@ -995,7 +938,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[0]->id;
         $grade_final->userid = 3;
-        $grade_final->gradevalue = 92.2;
+        $grade_final->gradevalue = 110;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = false; 
@@ -1009,7 +952,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[1]->id;
         $grade_final->userid = 1;
-        $grade_final->gradevalue = 69;
+        $grade_final->gradevalue = 70;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = true; 
@@ -1021,7 +964,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[1]->id;
         $grade_final->userid = 2;
-        $grade_final->gradevalue = 87;
+        $grade_final->gradevalue = 100;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = true; 
@@ -1120,22 +1063,10 @@ class grade_test extends UnitTestCase {
 
         // Grades for grade_item 8
 
-        $grade_final = new stdClass();
-        $grade_final->itemid = $this->grade_items[7]->id;
-        $grade_final->userid = 1;
-        $grade_final->gradevalue = 69;
-        $grade_final->timecreated = mktime();
-        $grade_final->timemodified = mktime();
-        $grade_final->locked = true; 
-
-        if ($grade_final->id = insert_record('grade_grades_final', $grade_final)) {
-            $this->grade_grades_final[] = $grade_final;
-        } 
-        
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[7]->id;
         $grade_final->userid = 2;
-        $grade_final->gradevalue = 87;
+        $grade_final->gradevalue = 3;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = true; 
@@ -1147,7 +1078,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[7]->id;
         $grade_final->userid = 3;
-        $grade_final->gradevalue = 94;
+        $grade_final->gradevalue = 6;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = false; 
@@ -1161,7 +1092,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[8]->id;
         $grade_final->userid = 1;
-        $grade_final->gradevalue = 69;
+        $grade_final->gradevalue = 20;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = true; 
@@ -1173,7 +1104,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[8]->id;
         $grade_final->userid = 2;
-        $grade_final->gradevalue = 87;
+        $grade_final->gradevalue = 50;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = true; 
@@ -1185,7 +1116,7 @@ class grade_test extends UnitTestCase {
         $grade_final = new stdClass();
         $grade_final->itemid = $this->grade_items[8]->id;
         $grade_final->userid = 3;
-        $grade_final->gradevalue = 94;
+        $grade_final->gradevalue = 100;
         $grade_final->timecreated = mktime();
         $grade_final->timemodified = mktime();
         $grade_final->locked = false; 
index 647c5b0a1c86091482ea556c6a08e327882c2e18..37690ed6bb66a59404f22109b3e0083faa08268e 100755 (executable)
@@ -79,7 +79,7 @@ class grade_category_test extends grade_test {
         
         $grade_category->fullname    = 'unittestcategory4';
         $grade_category->courseid    = $this->courseid;
-        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN_GRADED;
         $grade_category->keephigh    = 100;
         $grade_category->droplow     = 10;
         $grade_category->hidden      = 0;
@@ -166,47 +166,31 @@ class grade_category_test extends grade_test {
     }
     
     function test_grade_category_generate_grades() {
-        global $CFG;
-        $CFG->usenullgrades = true;
-
-        $category = new grade_category($this->grade_categories[0]);
+        $category = new grade_category($this->grade_categories[3]);
         $this->assertTrue(method_exists($category, 'generate_grades'));
-        $category->generate_grades();
         $category->load_grade_item();
+
         $raw_grades = get_records('grade_grades_raw', 'itemid', $category->grade_item->id);
-        $final_grades = get_records('grade_grades_final', 'itemid', $category->grade_item->id);
+        $this->assertFalse($raw_grades);
 
+        $category->generate_grades();
+        $raw_grades = get_records('grade_grades_raw', 'itemid', $category->grade_item->id);
         $this->assertEqual(3, count($raw_grades));
-        $this->assertEqual(3, count($final_grades));
-        
+
+        $rawvalues = array();
         foreach ($raw_grades as $grade) {
             $this->assertWithinMargin($grade->gradevalue, $grade->grademin, $grade->grademax);
+            $rawvalues[] = (int)$grade->gradevalue;
         }
-        
-        foreach ($final_grades as $grade) {
-            $this->assertWithinMargin($grade->gradevalue, 0, 100);
-        }
+        sort($rawvalues);
+        // calculated mean results
+        $this->assertEqual($rawvalues, array(20,50,100));
     }
     
     function test_grade_category_aggregate_grades() {
         $category = new grade_category($this->grade_categories[0]);
-        $this->assertTrue(method_exists($category, 'aggregate_grades'));
-        
-        // Generate 3 random data sets
-        $grade_sets = array();
-  
-        for ($i = 0; $i < 3; $i++) {
-            for ($j = 0; $j < 200; $j++) {
-                $grade_sets[$i][] = $this->generate_random_raw_grade(new grade_item($this->grade_items[$i]), $j);
-            }
-        } 
-        
-        $aggregated_grades = $category->aggregate_grades($grade_sets);
-        $this->assertEqual(200, count($aggregated_grades)); 
-        $this->assertWithinMargin($aggregated_grades[rand(1, count($aggregated_grades) - 1)]->gradevalue, 0, 100);
-        $this->assertWithinMargin($aggregated_grades[rand(1, count($aggregated_grades) - 1)]->gradevalue, 0, 100);
-        $this->assertWithinMargin($aggregated_grades[rand(1, count($aggregated_grades) - 1)]->gradevalue, 0, 100);
-        $this->assertWithinMargin($aggregated_grades[rand(1, count($aggregated_grades) - 1)]->gradevalue, 0, 100);
+        $this->assertTrue(method_exists($category, 'aggregate_grades')); 
+        // tested above in test_grade_category_generate_grades()
     }
     
     function generate_random_raw_grade($item, $userid) {
@@ -274,13 +258,17 @@ class grade_category_test extends grade_test {
         $grades = array(5.374, 9.4743, 2.5474, 7.3754);
         
         $category->droplow = 2;
-        $result = $category->apply_limit_rules(fullclone($grades));
-        $this->assertEqual(array(7.3754, 9.4743), $result);
-        
+        $category->apply_limit_rules($grades);
+        sort($grades, SORT_NUMERIC);
+        $this->assertEqual(array(7.3754, 9.4743), $grades);
+
+        $category = new grade_category();
+        $grades = array(5.374, 9.4743, 2.5474, 7.3754);
+
         $category->keephigh = 1;
         $category->droplow = 0;
-        $result = $category->apply_limit_rules(fullclone($grades));
-        $this->assertEqual(array(9.4743), $result); 
+        $category->apply_limit_rules($grades);
+        $this->assertEqual(array(9.4743), $grades); 
     }
 } 
 ?>
index 4536766ce4806ad0804b48830473ad15261d8002..84b535a6197ce369b228f157887143120558c81b 100755 (executable)
@@ -250,18 +250,15 @@ class grade_item_test extends grade_test {
     function test_grade_item_get_final() {\r
         $grade_item = new grade_item($this->grade_items[0]);\r
         $this->assertTrue(method_exists($grade_item, 'get_final'));\r
-        \r
-        $final_grades = $grade_item->get_final($this->userid);\r
-        $final_grade = current($final_grades);\r
-        $this->assertEqual(1, count($final_grade));\r
-        $this->assertEqual($this->grade_grades_final[0]->gradevalue, $final_grade->gradevalue); \r
+        $final_grade = $grade_item->get_final($this->userid);\r
+        $this->assertEqual($this->grade_grades_final[0]->gradevalue, $final_grade->gradevalue);\r
     }\r
 \r
     function test_grade_item_get_calculation() {\r
-        $grade_item = new grade_item($this->grade_items[0]);\r
+        $grade_item = new grade_item($this->grade_items[1]);\r
         $this->assertTrue(method_exists($grade_item, 'get_calculation'));\r
-        \r
         $grade_calculation = $grade_item->get_calculation();\r
+\r
         $this->assertEqual($this->grade_calculations[0]->id, $grade_calculation->id);\r
     }\r
 \r
@@ -286,13 +283,12 @@ class grade_item_test extends grade_test {
     }\r
 \r
     /**\r
-     * Test update of all final grades, then only 1 grade (give a $userid)\r
+     * Test update of all final grades\r
      */\r
     function test_grade_item_update_final_grades() {\r
         $grade_item = new grade_item($this->grade_items[0]);\r
         $this->assertTrue(method_exists($grade_item, 'update_final_grade'));\r
-        $this->assertEqual(3, $grade_item->update_final_grade()); \r
-        $this->assertEqual(1, $grade_item->update_final_grade(1)); \r
+        $this->assertEqual(true, $grade_item->update_final_grade()); \r
     }\r
 \r
     /**\r
@@ -324,19 +320,12 @@ class grade_item_test extends grade_test {
     function test_grade_item_load_fake_final() {\r
         $grade_item = new grade_item($this->grade_items[0]);\r
         $this->assertTrue(method_exists($grade_item, 'load_final'));\r
-        global $CFG;\r
-        $CFG->usenullgrades = true;\r
 \r
         // Delete one of the final grades\r
         $final_grade = new grade_grades_final($this->grade_grades_final[0]);\r
         $final_grade->delete();\r
         unset($this->grade_grades_final[0]);\r
 \r
-        // Load the final grades\r
-        $final_grades = $grade_item->load_final(true);\r
-        $this->assertEqual(3, count($final_grades));\r
-        $this->assertEqual($grade_item->grademin, $final_grades[1]->gradevalue); \r
-\r
         // Load normal final grades\r
         $final_grades = $grade_item->load_final();\r
         $this->assertEqual(2, count($final_grades));\r
@@ -412,7 +401,7 @@ class grade_item_test extends grade_test {
         $grade_item->insert();\r
         $grade_item->load_scale();\r
         $this->assertEqual('Very Good', $grade_item->scale->scale_items[1]);\r
-        \r
+\r
         // Load raw grade and its scale\r
         $grade_raw = new grade_grades_raw(array('scaleid' => $this->scale[0]->id), false);\r
         $grade_raw->gradevalue = 4;\r
@@ -421,20 +410,11 @@ class grade_item_test extends grade_test {
         $grade_raw->insert();\r
         $grade_raw->load_scale();\r
         $this->assertEqual('Fairly neutral', $grade_raw->scale->scale_items[2]);\r
-        \r
+\r
         // Test grade_item::adjust_scale\r
-        $this->assertEqual(3, round($grade_item->adjust_grade($grade_raw, null, 'gradevalue')));\r
+        $this->assertEqual(3, $grade_item->adjust_grade($grade_raw));\r
         $grade_raw->gradevalue = 6;\r
-        $this->assertEqual(4, $grade_item->adjust_grade($grade_raw, null, 'gradevalue'));\r
-\r
-        // Check that the final grades have the correct values now\r
-        $grade_item->load_raw();\r
-        $grade_item->update_final_grade();\r
-        \r
-        $this->assertFalse(empty($grade_item->grade_grades_final));\r
-        $this->assertEqual($grade_item->id, $grade_item->grade_grades_final[1]->itemid);\r
-        $this->assertEqual(2.66667, round($grade_item->grade_grades_final[1]->gradevalue, 5));\r
-        $this->assertEqual(1, $grade_item->grade_grades_final[1]->userid);\r
+        $this->assertEqual(4, $grade_item->adjust_grade($grade_raw));\r
     }\r
 \r
     function test_grade_item_toggle_locking() {\r
@@ -473,51 +453,6 @@ class grade_item_test extends grade_test {
         $this->assertTrue($grade_item->grade_grades_final[3]->hidden);\r
     } \r
 \r
-    function test_grade_item_generate_final() {\r
-        $grade_item = new grade_item();\r
-        $grade_item->courseid = $this->courseid;\r
-        $grade_item->categoryid = $this->grade_categories[1]->id;\r
-        $grade_item->itemname = 'unittestgradeitem4';\r
-        $grade_item->itemtype = 'mod';\r
-        $grade_item->grademin = 0;\r
-        $grade_item->grademax = 100;\r
-        $grade_item->itemmodule = 'quiz';\r
-        $grade_item->iteminfo = 'Grade item used for unit testing';\r
-\r
-        $grade_item->insert();\r
-\r
-        $grade_grades_raw = new grade_grades_raw();\r
-        $grade_grades_raw->itemid = $grade_item->id;\r
-        $grade_grades_raw->userid = 1;\r
-        $grade_grades_raw->gradevalue = 88;\r
-        $grade_grades_raw->grademax = 110;\r
-        $grade_grades_raw->grademin = 18;\r
-        $grade_grades_raw->insert();\r
-\r
-        $grade_grades_raw = new grade_grades_raw();\r
-        $grade_grades_raw->itemid = $grade_item->id;\r
-        $grade_grades_raw->userid = 2;\r
-        $grade_grades_raw->gradevalue = 68;\r
-        $grade_grades_raw->grademax = 110;\r
-        $grade_grades_raw->grademin = 18;\r
-        $grade_grades_raw->insert();\r
-\r
-        $grade_grades_raw = new grade_grades_raw();\r
-        $grade_grades_raw->itemid = $grade_item->id;\r
-        $grade_grades_raw->userid = 3;\r
-        $grade_grades_raw->gradevalue = 81;\r
-        $grade_grades_raw->grademax = 110;\r
-        $grade_grades_raw->grademin = 18;\r
-        $grade_grades_raw->insert();\r
-\r
-        $grade_item->load_raw();\r
-        $this->assertEqual(3, count($grade_item->grade_grades_raw));\r
-\r
-        $grade_item->generate_final();\r
-        $grade_item->load_final();\r
-        $this->assertEqual(3, count($grade_item->grade_grades_final)); \r
-    }\r
-\r
     function test_float_keys() {\r
     }\r
 } \r
index c5f33433bd848deceb650ef70d382b6367730a7d..cd6bf2b13bcf190786e9c7fb3d78242ccd97761a 100755 (executable)
@@ -88,7 +88,7 @@ class grade_raw_test extends grade_test {
 
         $grade_grades_raw->insert();
 
-        $this->assertEqual(7, $grade_grades_raw->grademax);
+        $this->assertEqual(6, $grade_grades_raw->grademax);
         $this->assertEqual(0, $grade_grades_raw->grademin); 
     }
 
index 1d90a5f820dfa5e9bf2e82be4b0bca268de263d4..3e09b8584eb20ad6c2fdb0e8e241d5cf1c5b2123 100644 (file)
@@ -284,7 +284,7 @@ class grade_tree_test extends grade_test {
 
     function test_grade_tree_get_tree() {
         $tree = new grade_tree($this->courseid, true);
-        $this->assertEqual(48, count($tree->tree_array, COUNT_RECURSIVE));
+        $this->assertEqual(47, count($tree->tree_array, COUNT_RECURSIVE));
     }
     
     function test_grade_tree_remove_element() {
index 12f778045ca33e5885c1091e80de10fbd0ef8edc..9ff1245176602eff3714fa9e1a56048cccb82258 100644 (file)
@@ -53,6 +53,8 @@ class gradelib_test extends grade_test {
         }
     }
 
+/*
+// obsolted function, should be replaced by grade_update() or removed completely
     function test_grade_create_category() {
         if (get_class($this) == 'gradelib_test') { 
             $grade_category = new stdClass();
@@ -73,7 +75,7 @@ class gradelib_test extends grade_test {
             $this->grade_items[] = $db_grade_category->grade_item;
         }
     }
-
+*/
     function test_grade_is_locked() {
         if (get_class($this) == 'gradelib_test') { 
             $grade_item = $this->grade_items[0];
index 486bb4d9da02c5d245d6fea27877d0348a201813..071490dc57e050ea028cb3d25eacd9998f6b695b 100755 (executable)
@@ -65,6 +65,15 @@ class mathsslib_test extends UnitTestCase {
         $this->assertEqual($res, 30, 'maximum is: %s');
     }
 
+    /**
+     * Tests special chars
+     */
+    function test__specialchars() {
+        $formula = new calc_formula('=gi1 + gi2 + gi11', array('gi1'=>10,'gi2'=>20,'gi11'=>30));
+        $res = $formula->evaluate();
+        $this->assertEqual($res, 60, 'sum is: %s');
+    }
+
 }
 
 ?>
\ No newline at end of file