$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
if ($gradeitems) {
foreach ($gradeitems as $gradeitem) {
- $gradeitem -> generate_final();
// load as an array of grade_final objects
if ($itemgrades = $gradeitem -> load_final()) {
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
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
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
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
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
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
} 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
} 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
* 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
* @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]);
}
/**
* @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);
* @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);
}
* 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);
$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;
}
/**
* @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
* 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;
}
/**
* @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++) {
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;
}
/**
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;
$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;
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;
}
* @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
* @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
* @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
* @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
* @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
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.
*/
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();
}
}
$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;
* @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) {
$grade_item = new grade_item($grade_item);
return $grade_item;
}
- } else {
+ } else {
return false;
}
}
}
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.
function insert() {
global $CFG;
+ // all new grade_items must be recalculated
+ $this->needsupdate = true;
+
if (!isset($this->gradetype)) {
$this->gradetype = GRADE_TYPE_VALUE;
}
// 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;
$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)', '');
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)) {
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
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;
}
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) {
}
/**
- * 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;
}
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) {
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;
+ }
}
/**
}
/**
- * 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.
$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);
/**
* 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);
}
/**
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);
/**
* 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.
/**
* 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()) {
} 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
}
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
$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
*/
}
/**
- * 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
}
/**
- * 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.
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() {
$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() {
}
/**
- * 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
} 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();
+ }
+ }
}
?>
* @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);
* @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'));
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;
+ }
}
/**
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();
+ }
+ }
+ }
+}
?>
$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();
$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();
$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';
$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';
$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;
}
}
$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;
$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;
$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;
$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
$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;
$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;
}
}
$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();
$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
$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
$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
$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
$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
$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
$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
$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
$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
$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;
}
}
* 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;
- }
}
/**
$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();
$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();
$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();
$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
// 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();
$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();
$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();
$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();
$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;
$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;
$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;
$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;
// 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;
$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;
$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;
$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;
$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;
$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;
}
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) {
$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);
}
}
?>
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
}\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
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
$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
$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
$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
$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);
}
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() {
}
}
+/*
+// 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();
$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];
$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