From: toyomoyo <toyomoyo>
Date: Fri, 26 Oct 2007 02:28:47 +0000 (+0000)
Subject: proper copy and format, sorry
X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=3c2e81ee493045fe1f6f862300bf868b25bec79a;p=moodle.git

proper copy and format, sorry
---

diff --git a/lib/grade/grade_item.php b/lib/grade/grade_item.php
index 8b08efc450..4140ad2191 100644
--- a/lib/grade/grade_item.php
+++ b/lib/grade/grade_item.php
@@ -1,1744 +1,1748 @@
-<?php // $Id$
-
-///////////////////////////////////////////////////////////////////////////
-//                                                                       //
-// NOTICE OF COPYRIGHT                                                   //
-//                                                                       //
-// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
-//          http://moodle.com                                            //
-//                                                                       //
-// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com       //
-//                                                                       //
-// This program is free software; you can redistribute it and/or modify  //
-// it under the terms of the GNU General Public License as published by  //
-// the Free Software Foundation; either version 2 of the License, or     //
-// (at your option) any later version.                                   //
-//                                                                       //
-// This program is distributed in the hope that it will be useful,       //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
-// GNU General Public License for more details:                          //
-//                                                                       //
-//          http://www.gnu.org/copyleft/gpl.html                         //
-//                                                                       //
-///////////////////////////////////////////////////////////////////////////
-
-require_once('grade_object.php');
-
-/**
- * Class representing a grade item. It is responsible for handling its DB representation,
- * modifying and returning its metadata.
- */
-class grade_item extends grade_object {
-    /**
-     * DB Table (used by grade_object).
-     * @var string $table
-     */
-    var $table = 'grade_items';
-
-    /**
-     * Array of required table fields, must start with 'id'.
-     * @var array $required_fields
-     */
-    var $required_fields = array('id', 'courseid', 'categoryid', 'itemname', 'itemtype', 'itemmodule', 'iteminstance',
-                                 'itemnumber', 'iteminfo', 'idnumber', 'calculation', 'gradetype', 'grademax', 'grademin',
-                                 'scaleid', 'outcomeid', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef',
-                                 'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime', 'needsupdate', 'timecreated',
-                                 'timemodified');
-
-    /**
-     * The course this grade_item belongs to.
-     * @var int $courseid
-     */
-    var $courseid;
-
-    /**
-     * The category this grade_item belongs to (optional).
-     * @var int $categoryid
-     */
-    var $categoryid;
-
-    /**
-     * The grade_category object referenced $this->iteminstance (itemtype must be == 'category' or == 'course' in that case).
-     * @var object $item_category
-     */
-    var $item_category;
-
-    /**
-     * The grade_category object referenced by $this->categoryid.
-     * @var object $parent_category
-     */
-    var $parent_category;
-
-
-    /**
-     * The name of this grade_item (pushed by the module).
-     * @var string $itemname
-     */
-    var $itemname;
-
-    /**
-     * e.g. 'category', 'course' and 'mod', 'blocks', 'import', etc...
-     * @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 $iteminfo;
-
-    /**
-     * Arbitrary idnumber provided by the module responsible.
-     * @var string $idnumber
-     */
-    var $idnumber;
-
-    /**
-     * Calculation string used for this item.
-     * @var string $calculation
-     */
-    var $calculation;
-
-    /**
-     * Indicates if we already tried to normalize the grade calculation formula.
-     * This flag helps to minimize db access when broken formulas used in calculation.
-     * @var boolean
-     */
-    var $calculation_normalized;
-    /**
-     * Math evaluation object
-     */
-    var $formula;
-
-    /**
-     * The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)
-     * @var int $gradetype
-     */
-    var $gradetype = GRADE_TYPE_VALUE;
-
-    /**
-     * Maximum allowable grade.
-     * @var float $grademax
-     */
-    var $grademax = 100;
-
-    /**
-     * Minimum allowable grade.
-     * @var float $grademin
-     */
-    var $grademin = 0;
-
-    /**
-     * 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 $outcomeid;
-
-    /**
-     * The grade_outcome this grade is associated with, if applicable.
-     * @var object $outcome
-     */
-    var $outcome;
-
-    /**
-     * grade required to pass. (grademin <= gradepass <= grademax)
-     * @var float $gradepass
-     */
-    var $gradepass = 0;
-
-    /**
-     * Multiply all grades by this number.
-     * @var float $multfactor
-     */
-    var $multfactor = 1.0;
-
-    /**
-     * Add this to all grades.
-     * @var float $plusfactor
-     */
-    var $plusfactor = 0;
-
-    /**
-     * Aggregation coeficient used for weighted averages
-     * @var float $aggregationcoef
-     */
-    var $aggregationcoef = 0;
-
-    /**
-     * Sorting order of the columns.
-     * @var int $sortorder
-     */
-    var $sortorder = 0;
-
-    /**
-     * Display type of the grades (Real, Percentage, Letter, or default).
-     * @var int $display
-     */
-    var $display = GRADE_DISPLAY_TYPE_DEFAULT;
-
-    /**
-     * The number of digits after the decimal point symbol. Applies only to REAL and PERCENTAGE grade display types.
-     * @var int $decimals
-     */
-    var $decimals = null;
-
-    /**
-     * 0 if visible, 1 always hidden or date not visible until
-     * @var int $hidden
-     */
-    var $hidden = 0;
-
-    /**
-     * Grade item lock flag. Empty if not locked, locked if any value present, usually date when item was locked. Locking prevents updating.
-     * @var int $locked
-     */
-    var $locked = 0;
-
-    /**
-     * Date after which the grade will be locked. Empty means no automatic locking.
-     * @var int $locktime
-     */
-    var $locktime = 0;
-
-    /**
-     * If set, the whole column will be recalculated, then this flag will be switched off.
-     * @var boolean $needsupdate
-     */
-    var $needsupdate = 1;
-
-    /**
-     * Cached dependson array
-     */
-    var $dependson_cache = null;
-
-    /**
-     * In addition to update() as defined in grade_object, handle the grade_outcome and grade_scale objects.
-     * Force regrading if necessary
-     * @param string $source from where was the object inserted (mod/forum, manual, etc.)
-     * @return boolean success
-     */
-    function update($source=null) {
-        // reset caches
-        $this->dependson_cache = null;
-
-        // Retrieve scale and infer grademax/min from it if needed
-        $this->load_scale();
-
-        // make sure there is not 0 in outcomeid
-        if (empty($this->outcomeid)) {
-            $this->outcomeid = null;
-        }
-
-        if ($this->qualifies_for_regrading()) {
-            $this->force_regrading();
-        }
-
-        return parent::update($source);
-    }
-
-    /**
-     * Compares the values held by this object with those of the matching record in DB, and returns
-     * whether or not these differences are sufficient to justify an update of all parent objects.
-     * This assumes that this object has an id number and a matching record in DB. If not, it will return false.
-     * @return boolean
-     */
-    function qualifies_for_regrading() {
-        if (empty($this->id)) {
-            return false;
-        }
-
-        $db_item = new grade_item(array('id' => $this->id));
-
-        $calculationdiff = $db_item->calculation != $this->calculation;
-        $categorydiff    = $db_item->categoryid  != $this->categoryid;
-        $gradetypediff   = $db_item->gradetype   != $this->gradetype;
-        $grademaxdiff    = $db_item->grademax    != $this->grademax;
-        $grademindiff    = $db_item->grademin    != $this->grademin;
-        $scaleiddiff     = $db_item->scaleid     != $this->scaleid;
-        $outcomeiddiff   = $db_item->outcomeid   != $this->outcomeid;
-        $multfactordiff  = $db_item->multfactor  != $this->multfactor;
-        $plusfactordiff  = $db_item->plusfactor  != $this->plusfactor;
-        $locktimediff    = $db_item->locktime    != $this->locktime;
-        $acoefdiff       = $db_item->aggregationcoef != $this->aggregationcoef;
-
-        $needsupdatediff = !$db_item->needsupdate &&  $this->needsupdate;    // force regrading only if setting the flag first time
-        $lockeddiff      = !empty($db_item->locked) && empty($this->locked); // force regrading only when unlocking
-
-        return ($calculationdiff || $categorydiff || $gradetypediff || $grademaxdiff || $grademindiff || $scaleiddiff
-             || $outcomeiddiff || $multfactordiff || $plusfactordiff || $needsupdatediff
-             || $lockeddiff || $acoefdiff || $locktimediff);
-    }
-
-    /**
-     * Finds and returns a grade_item instance based on params.
-     * @static
-     *
-     * @param array $params associative arrays varname=>value
-     * @return object grade_item instance or false if none found.
-     */
-    function fetch($params) {
-        return grade_object::fetch_helper('grade_items', 'grade_item', $params);
-    }
-
-    /**
-     * Finds and returns all grade_item instances based on params.
-     * @static
-     *
-     * @param array $params associative arrays varname=>value
-     * @return array array of grade_item insatnces or false if none found.
-     */
-    function fetch_all($params) {
-        return grade_object::fetch_all_helper('grade_items', 'grade_item', $params);
-    }
-
-    /**
-     * Delete all grades and force_regrading of parent category.
-     * @param string $source from where was the object deleted (mod/forum, manual, etc.)
-     * @return boolean success
-     */
-    function delete($source=null) {
-        if (!$this->is_course_item()) {
-            $this->force_regrading();
-        }
-
-        if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
-            foreach ($grades as $grade) {
-                $grade->delete($source);
-            }
-        }
-
-        return parent::delete($source);
-    }
-
-    /**
-     * In addition to perform parent::insert(), calls force_regrading() method too.
-     * @param string $source from where was the object inserted (mod/forum, manual, etc.)
-     * @return int PK ID if successful, false otherwise
-     */
-    function insert($source=null) {
-        global $CFG;
-
-        if (empty($this->courseid)) {
-            error('Can not insert grade item without course id!');
-        }
-
-        // load scale if needed
-        $this->load_scale();
-
-        // add parent category if needed
-        if (empty($this->categoryid) and !$this->is_course_item() and !$this->is_category_item()) {
-            $course_category = grade_category::fetch_course_category($this->courseid);
-            $this->categoryid = $course_category->id;
-
-        }
-
-        // always place the new items at the end, move them after insert if needed
-        $last_sortorder = get_field_select('grade_items', 'MAX(sortorder)', "courseid = {$this->courseid}");
-        if (!empty($last_sortorder)) {
-            $this->sortorder = $last_sortorder + 1;
-        } else {
-            $this->sortorder = 1;
-        }
-
-        // add proper item numbers to manual items
-        if ($this->itemtype == 'manual') {
-            if (empty($this->itemnumber)) {
-                $this->itemnumber = 0;
-            }
-        }
-
-        // make sure there is not 0 in outcomeid
-        if (empty($this->outcomeid)) {
-            $this->outcomeid = null;
-        }
-
-        if (parent::insert($source)) {
-            // force regrading of items if needed
-            $this->force_regrading();
-            return $this->id;
-
-        } else {
-            debugging("Could not insert this grade_item in the database!");
-            return false;
-        }
-    }
-
-    /**
-     * Set idnumber of grade item, updates also course_modules table
-     * @param string $idnumber (without magic quotes)
-     * @return boolean success
-     */
-    function add_idnumber($idnumber) {
-        if (!empty($this->idnumber)) {
-            return false;
-        }
-
-        if ($this->itemtype == 'mod' and !$this->is_outcome_item()) {
-            if (!$cm = get_coursemodule_from_instance($this->itemmodule, $this->iteminstance, $this->courseid)) {
-                return false;
-            }
-            if (!empty($cm->idnumber)) {
-                return false;
-            }
-            if (set_field('course_modules', 'idnumber', addslashes($idnumber), 'id', $cm->id)) {
-                $this->idnumber = $idnumber;
-                return $this->update();
-            }
-            return false;
-
-        } else {
-            $this->idnumber = $idnumber;
-            return $this->update();
-        }
-    }
-
-    /**
-     * 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
-     * $userid is given and the grade_item is unlocked.
-     *
-     * @param int $userid
-     * @return boolean Locked state
-     */
-    function is_locked($userid=NULL) {
-        if (!empty($this->locked)) {
-            return true;
-        }
-
-        if (!empty($userid)) {
-            if ($grade = grade_grade::fetch(array('itemid'=>$this->id, 'userid'=>$userid))) {
-                $grade->grade_item =& $this; // prevent db fetching of cached grade_item
-                return $grade->is_locked();
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Locks or unlocks this grade_item and (optionally) all its associated final grades.
-     * @param int $locked 0, 1 or a timestamp int(10) after which date the item will be locked.
-     * @param boolean $cascade lock/unlock child objects too
-     * @param boolean $refresh refresh grades when unlocking
-     * @return boolean true if grade_item all grades updated, false if at least one update fails
-     */
-    function set_locked($lockedstate, $cascade=false, $refresh=true) {
-        if ($lockedstate) {
-        /// setting lock
-            if ($this->needsupdate) {
-                return false; // can not lock grade without first having final grade
-            }
-
-            $this->locked = time();
-            $this->update();
-
-            if ($cascade) {
-                $grades = $this->get_final();
-                foreach($grades as $g) {
-                    $grade = new grade_grade($g, false);
-                    $grade->grade_item =& $this;
-                    $grade->set_locked(1, null, false);
-                }
-            }
-
-            return true;
-
-        } else {
-        /// removing lock
-            if (!empty($this->locked) and $this->locktime < time()) {
-                //we have to reset locktime or else it would lock up again
-                $this->locktime = 0;
-            }
-
-            $this->locked = 0;
-            $this->update();
-
-            if ($cascade) {
-                if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
-                    foreach($grades as $grade) {
-                        $grade->grade_item =& $this;
-                        $grade->set_locked(0, null, false);
-                    }
-                }
-            }
-
-            if ($refresh) {
-                //refresh when unlocking
-                $this->refresh_grades();
-            }
-
-            return true;
-        }
-    }
-
-    /**
-     * Lock the grade if needed - make sure this is called only when final grades are valid
-     */
-    function check_locktime() {
-        if (!empty($this->locked)) {
-            return; // already locked
-        }
-
-        if ($this->locktime and $this->locktime < time()) {
-            $this->locked = time();
-            $this->update('locktime');
-        }
-    }
-
-    /**
-     * Set the locktime for this grade item.
-     *
-     * @param int $locktime timestamp for lock to activate
-     * @return void
-     */
-    function set_locktime($locktime) {
-        $this->locktime = $locktime;
-        $this->update();
-    }
-
-    /**
-     * Set the locktime for this grade item.
-     *
-     * @return int $locktime timestamp for lock to activate
-     */
-    function get_locktime() {
-        return $this->locktime;
-    }
-
-    /**
-     * Returns the hidden state of this grade_item
-     * @return boolean hidden state
-     */
-    function is_hidden() {
-        return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()));
-    }
-
-    /**
-     * Check grade item hidden status.
-     * @return int 0 means visible, 1 hidden always, timestamp hidden until
-     */
-    function get_hidden() {
-        return $this->hidden;
-    }
-
-    /**
-     * Set the hidden status of grade_item and all grades, 0 mean visible, 1 always hidden, number means date to hide until.
-     * @param int $hidden new hidden status
-     * @param boolean $cascade apply to child objects too
-     * @return void
-     */
-    function set_hidden($hidden, $cascade=false) {
-        $this->hidden = $hidden;
-        $this->update();
-
-        if ($cascade) {
-            if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
-                foreach($grades as $grade) {
-                    $grade->grade_item =& $this;
-                    $grade->set_hidden($hidden, $cascade);
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the number of grades that are hidden.
-     * @param return int Number of hidden grades
-     */
-    function has_hidden_grades($groupsql="", $groupwheresql="") {
-        global $CFG;
-        return get_field_sql("SELECT COUNT(*) FROM {$CFG->prefix}grade_grades g LEFT JOIN "
-                            ."{$CFG->prefix}user u ON g.userid = u.id $groupsql WHERE itemid = $this->id AND hidden = 1 $groupwheresql");
-    }
-
-    /**
-     * Mark regrading as finished successfully.
-     */
-    function regrading_finished() {
-        $this->needsupdate = 0;
-        //do not use $this->update() because we do not want this logged in grade_item_history
-        set_field('grade_items', 'needsupdate', 0, 'id', $this->id);
-    }
-
-    /**
-     * Performs the necessary calculations on the grades_final referenced by this grade_item.
-     * Also resets the needsupdate flag once successfully performed.
-     *
-     * This function must be used ONLY from lib/gradeslib.php/grade_regrade_final_grades(),
-     * because the regrading must be done in correct order!!
-     *
-     * @return boolean true if ok, error string otherwise
-     */
-    function regrade_final_grades($userid=null) {
-        global $CFG;
-
-        // locked grade items already have correct final grades
-        if ($this->is_locked()) {
-            return true;
-        }
-
-        // calculation produces final value using formula from other final values
-        if ($this->is_calculated()) {
-            if ($this->compute($userid)) {
-                return true;
-            } else {
-                return "Could not calculate grades for grade item"; // TODO: improve and localize
-            }
-
-        // noncalculated outcomes already have final values - raw grades not used
-        } else if ($this->is_outcome_item()) {
-            return true;
-
-        // aggregate the category grade
-        } else if ($this->is_category_item() or $this->is_course_item()) {
-            // aggregate category grade item
-            $category = $this->get_item_category();
-            $category->grade_item =& $this;
-            if ($category->generate_grades($userid)) {
-                return true;
-            } else {
-                return "Could not aggregate final grades for category:".$this->id; // TODO: improve and localize
-            }
-
-        } else if ($this->is_manual_item()) {
-            // manual items track only final grades, no raw grades
-            return true;
-
-        } else if (!$this->is_raw_used()) {
-            // hmm - raw grades are not used- nothing to regrade
-            return true;
-        }
-
-        // normal grade item - just new final grades
-        $result = true;
-        $grade_inst = new grade_grade();
-        $fields = implode(',', $grade_inst->required_fields);
-        if ($userid) {
-            $rs = get_recordset_select('grade_grades', "itemid={$this->id} AND userid=$userid", '', $fields);
-        } else {
-            $rs = get_recordset('grade_grades', 'itemid', $this->id, '', $fields);
-        }
-        if ($rs) {
-            while ($grade_record = rs_fetch_next_record($rs)) {
-                $grade = new grade_grade($grade_record, false);
-
-                if (!empty($grade_record->locked) or !empty($grade_record->overridden)) {
-                    // this grade is locked - final grade must be ok
-                    continue;
-                }
-
-                $grade->finalgrade = $this->adjust_raw_grade($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
-
-                if ($grade_record->finalgrade !== $grade->finalgrade) {
-                    if (!$grade->update('system')) {
-                        $result = "Internal error updating final grade";
-                    }
-                }
-            }
-            rs_close($rs);
-        }
-
-        return $result;
-    }
-
-    /**
-     * 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 $rawgrade The raw grade value.
-     * @return mixed
-     */
-    function adjust_raw_grade($rawgrade, $rawmin, $rawmax) {
-        if (is_null($rawgrade)) {
-            return null;
-        }
-
-        if ($this->gradetype == GRADE_TYPE_VALUE) { // Dealing with numerical grade
-
-            if ($this->grademax < $this->grademin) {
-                return null;
-            }
-
-            if ($this->grademax == $this->grademin) {
-                return $this->grademax; // no range
-            }
-
-            // Standardise score to the new grade range
-            // NOTE: this is not compatible with current assignment grading
-            if ($rawmin != $this->grademin or $rawmax != $this->grademax) {
-                $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
-            }
-
-            // Apply other grade_item factors
-            $rawgrade *= $this->multfactor;
-            $rawgrade += $this->plusfactor;
-
-            return bounded_number($this->grademin, $rawgrade, $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 ($rawmin != $this->grademin or $rawmax != $this->grademax) {
-                $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
-            }
-
-            return (int)bounded_number(0, round($rawgrade+0.00001), $this->grademax);
-
-
-        } 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;;
-        }
-    }
-
-    /**
-     * Sets this grade_item's needsupdate to true. Also marks the course item as needing update.
-     * @return void
-     */
-    function force_regrading() {
-        $this->needsupdate = 1;
-        //mark this item and course item only - categories and calculated items are always regraded
-        $wheresql = "(itemtype='course' OR id={$this->id}) AND courseid={$this->courseid}";
-        set_field_select('grade_items', 'needsupdate', 1, $wheresql);
-    }
-
-    /**
-     * Instantiates a grade_scale object whose data is retrieved from the DB,
-     * if this item's scaleid variable is set.
-     * @return object grade_scale or null if no scale used
-     */
-    function load_scale() {
-        if ($this->gradetype != GRADE_TYPE_SCALE) {
-            $this->scaleid = null;
-        }
-
-        if (!empty($this->scaleid)) {
-            //do not load scale if already present
-            if (empty($this->scale->id) or $this->scale->id != $this->scaleid) {
-                $this->scale = grade_scale::fetch(array('id'=>$this->scaleid));
-                $this->scale->load_items();
-            }
-
-            // Until scales are uniformly set to min=0 max=count(scaleitems)-1 throughout Moodle, we
-            // stay with the current min=1 max=count(scaleitems)
-            $this->grademax = count($this->scale->scale_items);
-            $this->grademin = 1;
-
-        } else {
-            $this->scale = null;
-        }
-
-        return $this->scale;
-    }
-
-    /**
-     * 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(array('id'=>$this->outcomeid));
-        }
-        return $this->outcome;
-    }
-
-    /**
-    * Returns the grade_category object this grade_item belongs to (referenced by categoryid)
-    * or category attached to category item.
-    *
-    * @return mixed grade_category object if applicable, false if course item
-    */
-    function get_parent_category() {
-        if ($this->is_category_item() or $this->is_course_item()) {
-            return $this->get_item_category();
-
-        } else {
-            return grade_category::fetch(array('id'=>$this->categoryid));
-        }
-    }
-
-    /**
-     * Calls upon the get_parent_category method to retrieve the grade_category object
-     * from the DB and assigns it to $this->parent_category. It also returns the object.
-     * @return object Grade_category
-     */
-    function load_parent_category() {
-        if (empty($this->parent_category->id)) {
-            $this->parent_category = $this->get_parent_category();
-        }
-        return $this->parent_category;
-    }
-
-    /**
-    * Returns the grade_category for category item
-    *
-    * @return mixed grade_category object if applicable, false otherwise
-    */
-    function get_item_category() {
-        if (!$this->is_course_item() and !$this->is_category_item()) {
-            return false;
-        }
-        return grade_category::fetch(array('id'=>$this->iteminstance));
-    }
-
-    /**
-     * Calls upon the get_item_category method to retrieve the grade_category object
-     * from the DB and assigns it to $this->item_category. It also returns the object.
-     * @return object Grade_category
-     */
-    function load_item_category() {
-        if (empty($this->category->id)) {
-            $this->item_category = $this->get_item_category();
-        }
-        return $this->item_category;
-    }
-
-    /**
-     * Is the grade item associated with category?
-     * @return boolean
-     */
-    function is_category_item() {
-        return ($this->itemtype == 'category');
-    }
-
-    /**
-     * Is the grade item associated with course?
-     * @return boolean
-     */
-    function is_course_item() {
-        return ($this->itemtype == 'course');
-    }
-
-    /**
-     * Is this a manualy graded item?
-     * @return boolean
-     */
-    function is_manual_item() {
-        return ($this->itemtype == 'manual');
-    }
-
-    /**
-     * Is this an outcome item?
-     * @return boolean
-     */
-    function is_outcome_item() {
-        return !empty($this->outcomeid);
-    }
-
-    /**
-     * Is the grade item normal - associated with module, plugin or something else?
-     * @return boolean
-     */
-    function is_normal_item() {
-        return ($this->itemtype != 'course' and $this->itemtype != 'category' and $this->itemtype != 'manual');
-    }
-
-    /**
-     * Returns true if grade items uses raw grades
-     * @return boolean
-     */
-    function is_raw_used() {
-        return ($this->is_normal_item() and !$this->is_calculated() and !$this->is_outcome_item());
-    }
-
-    /**
-     * Returns grade item associated with the course
-     * @param int $courseid
-     * @return course item object
-     */
-    function fetch_course_item($courseid) {
-        if ($course_item = grade_item::fetch(array('courseid'=>$courseid, 'itemtype'=>'course'))) {
-            return $course_item;
-        }
-
-        // first get category - it creates the associated grade item
-        $course_category = grade_category::fetch_course_category($courseid);
-
-        return grade_item::fetch(array('courseid'=>$courseid, 'itemtype'=>'course'));
-    }
-
-    /**
-     * Is grading object editable?
-     * @return boolean
-     */
-    function is_editable() {
-        return true;
-    }
-
-    /**
-     * Checks if grade calculated. Returns this object's calculation.
-     * @return boolean true if grade item calculated.
-     */
-    function is_calculated() {
-        if (empty($this->calculation)) {
-            return false;
-        }
-
-        /*
-         * The main reason why we use the ##gixxx## instead of [[idnumber]] is speed of depends_on(),
-         * we would have to fetch all course grade items to find out the ids.
-         * Also if user changes the idnumber the formula does not need to be updated.
-         */
-
-        // first detect if we need to change calculation formula from [[idnumber]] to ##giXXX## (after backup, etc.)
-        if (!$this->calculation_normalized and preg_match('/##gi\d+##/', $this->calculation)) {
-            $this->set_calculation($this->calculation);
-        }
-
-        return !empty($this->calculation);
-    }
-
-    /**
-     * Returns calculation string if grade calculated.
-     * @return mixed string if calculation used, null if not
-     */
-    function get_calculation() {
-        if ($this->is_calculated()) {
-            return grade_item::denormalize_formula($this->calculation, $this->courseid);
-
-        } else {
-            return NULL;
-        }
-    }
-
-    /**
-     * 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 calculation is removed.
-     * @param string $formula string representation of formula used for calculation
-     * @return boolean success
-     */
-    function set_calculation($formula) {
-        $this->calculation = grade_item::normalize_formula($formula, $this->courseid);
-        $this->calculation_normalized = true;
-        return $this->update();
-    }
-
-    /**
-     * Denormalizes the calculation formula to [idnumber] form
-     * @static
-     * @param string $formula
-     * @return string denormalized string
-     */
-    function denormalize_formula($formula, $courseid) {
-        if (empty($formula)) {
-            return '';
-        }
-
-        // denormalize formula - convert ##giXX## to [[idnumber]]
-        if (preg_match_all('/##gi(\d+)##/', $formula, $matches)) {
-            foreach ($matches[1] as $id) {
-                if ($grade_item = grade_item::fetch(array('id'=>$id, 'courseid'=>$courseid))) {
-                    if (!empty($grade_item->idnumber)) {
-                        $formula = str_replace('##gi'.$grade_item->id.'##', '[['.$grade_item->idnumber.']]', $formula);
-                    }
-                }
-            }
-        }
-
-        return $formula;
-
-    }
-
-    /**
-     * Normalizes the calculation formula to [#giXX#] form
-     * @static
-     * @param string $formula
-     * @return string normalized string
-     */
-    function normalize_formula($formula, $courseid) {
-        $formula = trim($formula);
-
-        if (empty($formula)) {
-            return NULL;
-
-        }
-
-        // normalize formula - we want grade item ids ##giXXX## instead of [[idnumber]]
-        if ($grade_items = grade_item::fetch_all(array('courseid'=>$courseid))) {
-            foreach ($grade_items as $grade_item) {
-                $formula = str_replace('[['.$grade_item->idnumber.']]', '##gi'.$grade_item->id.'##', $formula);
-            }
-        }
-
-        return $formula;
-    }
-
-    /**
-     * 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
-     * @return mixed An array of all final_grades (stdClass objects) for this grade_item, or a single final_grade.
-     */
-    function get_final($userid=NULL) {
-        if ($userid) {
-            if ($user = get_record('grade_grades', 'itemid', $this->id, 'userid', $userid)) {
-                return $user;
-            }
-
-        } else {
-            if ($grades = get_records('grade_grades', 'itemid', $this->id)) {
-                //TODO: speed up with better SQL
-                $result = array();
-                foreach ($grades as $grade) {
-                    $result[$grade->userid] = $grade;
-                }
-                return $result;
-            } else {
-                return array();
-            }
-        }
-    }
-
-    /**
-     * Get (or create if not exist yet) grade for this user
-     * @param int $userid
-     * @return object grade_grade object instance
-     */
-    function get_grade($userid, $create=true) {
-        if (empty($this->id)) {
-            debugging('Can not use before insert');
-            return false;
-        }
-
-        $grade = new grade_grade(array('userid'=>$userid, 'itemid'=>$this->id));
-        if (empty($grade->id) and $create) {
-            $grade->insert();
-        }
-
-        return $grade;
-    }
-
-    /**
-     * Returns the sortorder of this grade_item. This method is also available in
-     * grade_category, for cases where the object type is not know.
-     * @return int Sort order
-     */
-    function get_sortorder() {
-        return $this->sortorder;
-    }
-
-    /**
-     * Returns the idnumber of this grade_item. This method is also available in
-     * grade_category, for cases where the object type is not know.
-     * @return string idnumber
-     */
-    function get_idnumber() {
-        return $this->idnumber;
-    }
-
-    /**
-     * Returns this grade_item. This method is also available in
-     * grade_category, for cases where the object type is not know.
-     * @return string idnumber
-     */
-    function get_grade_item() {
-        return $this;
-    }
-
-    /**
-     * Sets the sortorder of this grade_item. This method is also available in
-     * grade_category, for cases where the object type is not know.
-     * @param int $sortorder
-     * @return void
-     */
-    function set_sortorder($sortorder) {
-        $this->sortorder = $sortorder;
-        $this->update();
-    }
-
-    function move_after_sortorder($sortorder) {
-        global $CFG;
-
-        //make some room first
-        $sql = "UPDATE {$CFG->prefix}grade_items
-                   SET sortorder = sortorder + 1
-                 WHERE sortorder > $sortorder AND courseid = {$this->courseid}";
-        execute_sql($sql, false);
-
-        $this->set_sortorder($sortorder + 1);
-    }
-
-    /**
-     * 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() {
-        if (!empty($this->itemname)) {
-            // MDL-10557
-            return format_string($this->itemname);
-
-        } else if ($this->is_course_item()) {
-            return get_string('coursetotal', 'grades');
-
-        } else if ($this->is_category_item()) {
-            return get_string('categorytotal', 'grades');
-
-        } else {
-            return get_string('grade');
-        }
-    }
-
-    /**
-     * Sets this item's categoryid. A generic method shared by objects that have a parent id of some kind.
-     * @param int $parentid
-     * @return boolean success;
-     */
-    function set_parent($parentid) {
-        if ($this->is_course_item() or $this->is_category_item()) {
-            error('Can not set parent for category or course item!');
-        }
-
-        if ($this->categoryid == $parentid) {
-            return true;
-        }
-
-        // find parent and check course id
-        if (!$parent_category = grade_category::fetch(array('id'=>$parentid, 'courseid'=>$this->courseid))) {
-            return false;
-        }
-
-        $this->force_regrading();
-
-        // set new parent
-        $this->categoryid = $parent_category->id;
-        $this->parent_category =& $parent_category;
-
-        return $this->update();
-    }
-
-    /**
-     * Finds out on which other items does this depend directly when doing calculation or category agregation
-     * @param bool $reset_cache
-     * @return array of grade_item ids this one depends on
-     */
-    function depends_on($reset_cache=false) {
-        global $CFG;
-
-        if ($reset_cache) {
-            $this->dependson_cache = null;
-        } else if (isset($this->dependson_cache)) {
-            return $this->dependson_cache;
-        }
-
-        if ($this->is_locked()) {
-            // locked items do not need to be regraded
-            $this->dependson_cache = array();
-            return $this->dependson_cache;
-        }
-
-        if ($this->is_calculated()) {
-            if (preg_match_all('/##gi(\d+)##/', $this->calculation, $matches)) {
-                $this->dependson_cache = array_unique($matches[1]); // remove duplicates
-                return $this->dependson_cache;
-            } else {
-                $this->dependson_cache = array();
-                return $this->dependson_cache;
-            }
-
-        } else if ($grade_category = $this->load_item_category()) {
-            //only items with numeric or scale values can be aggregated
-            if ($this->gradetype != GRADE_TYPE_VALUE and $this->gradetype != GRADE_TYPE_SCALE) {
-                $this->dependson_cache = array();
-                return $this->dependson_cache;
-            }
-
-            $grade_category->apply_forced_settings();
-
-            if (empty($CFG->enableoutcomes) or $grade_category->aggregateoutcomes) {
-                $outcomes_sql = "";
-            } else {
-                $outcomes_sql = "AND gi.outcomeid IS NULL";
-            }
-
-            if ($grade_category->aggregatesubcats) {
-                // return all children excluding category items
-                $sql = "SELECT gi.id
-                          FROM {$CFG->prefix}grade_items gi
-                         WHERE (gi.gradetype = ".GRADE_TYPE_VALUE." OR gi.gradetype = ".GRADE_TYPE_SCALE.")
-                               $outcomes_sql
-                               AND gi.categoryid IN (
-                                  SELECT gc.id
-                                    FROM {$CFG->prefix}grade_categories gc
-                                   WHERE gc.path LIKE '%/{$grade_category->id}/%')";
-
-            } else {
-                $sql = "SELECT gi.id
-                          FROM {$CFG->prefix}grade_items gi
-                         WHERE gi.categoryid = {$grade_category->id}
-                               AND (gi.gradetype = ".GRADE_TYPE_VALUE." OR gi.gradetype = ".GRADE_TYPE_SCALE.")
-                               $outcomes_sql
-
-                        UNION
-
-                        SELECT gi.id
-                          FROM {$CFG->prefix}grade_items gi, {$CFG->prefix}grade_categories gc
-                         WHERE (gi.itemtype = 'category' OR gi.itemtype = 'course') AND gi.iteminstance=gc.id
-                               AND gc.parent = {$grade_category->id}
-                               AND (gi.gradetype = ".GRADE_TYPE_VALUE." OR gi.gradetype = ".GRADE_TYPE_SCALE.")
-                               $outcomes_sql";
-            }
-
-            if ($children = get_records_sql($sql)) {
-                $this->dependson_cache = array_keys($children);
-                return $this->dependson_cache;
-            } else {
-                $this->dependson_cache = array();
-                return $this->dependson_cache;
-            }
-
-        } else {
-            $this->dependson_cache = array();
-            return $this->dependson_cache;
-        }
-    }
-
-    /**
-     * Refetch grades from moudles, plugins.
-     * @param int $userid optional, one user only
-     */
-    function refresh_grades($userid=0) {
-        if ($this->itemtype == 'mod') {
-            if ($this->is_outcome_item()) {
-                //nothing to do
-                return;
-            }
-
-            if (!$activity = get_record($this->itemmodule, 'id', $this->iteminstance)) {
-                debugging('Can not find activity');
-                return;
-            }
-
-            if (! $cm = get_coursemodule_from_instance($this->itemmodule, $activity->id, $this->courseid)) {
-                debuggin('Can not find course module');
-                return;
-            }
-
-            $activity->modname    = $this->itemmodule;
-            $activity->cmidnumber = $cm->idnumber;
-
-            grade_update_mod_grades($activity);
-        }
-    }
-
-    /**
-     * Updates final grade value for given user, this is a only way to update final
-     * grades from gradebook and import because it logs the change in history table
-     * and deals with overridden flag. This flag is set to prevent later overriding
-     * from raw grades submitted from modules.
-     *
-     * @param int $userid the graded user
-     * @param mixed $finalgrade float value of final grade - false means do not change
-     * @param string $howmodified modification source
-     * @param string $note optional note
-     * @param mixed $feedback teachers feedback as string - false means do not change
-     * @param int $feedbackformat
-     * @return boolean success
-     */
-    function update_final_grade($userid, $finalgrade=false, $source=NULL, $note=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
-        global $USER, $CFG;
-
-        if (empty($usermodified)) {
-            $usermodified = $USER->id;
-        }
-
-        $result = true;
-
-        // no grading used or locked
-        if ($this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
-            return false;
-        }
-
-        $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid));
-        $grade->grade_item =& $this; // prevent db fetching of this grade_item
-
-        $grade->usermodified = $usermodified;
-
-        if ($grade->is_locked()) {
-            // do not update locked grades at all
-            return false;
-        }
-
-        $locktime = $grade->get_locktime();
-        if ($locktime and $locktime < time()) {
-            // do not update grades that should be already locked, force regrade instead
-            $this->force_regrading();
-            return false;
-        }
-
-        $oldgrade = new object();
-        $oldgrade->finalgrade     = $grade->finalgrade;
-        $oldgrade->overridden     = $grade->overridden;
-        $oldgrade->feedback       = $grade->feedback;
-        $oldgrade->feedbackformat = $grade->feedbackformat;
-
-        if ($finalgrade !== false or $feedback !== false) {
-            if (($this->is_outcome_item() or $this->is_manual_item()) and !$this->is_calculated()) {
-                // final grades updated only by user - no need for overriding
-                $grade->overridden = 0;
-
-            } else {
-                $grade->overridden = time();
-            }
-        }
-
-        if ($finalgrade !== false)  {
-            if (!is_null($finalgrade)) {
-                $finalgrade = bounded_number($this->grademin, $finalgrade, $this->grademax);
-            } else {
-                $finalgrade = $finalgrade;
-            }
-            $grade->finalgrade = $finalgrade;
-        }
-
-        // do we have comment from teacher?
-        if ($feedback !== false) {
-            $grade->feedback       = $feedback;
-            $grade->feedbackformat = $feedbackformat;
-        }
-
-        if (empty($grade->id)) {
-            $result = (boolean)$grade->insert($source);
-
-        } else if ($grade->finalgrade     !== $oldgrade->finalgrade
-                or $grade->feedback       !== $oldgrade->feedback
-                or $grade->feedbackformat !== $oldgrade->feedbackformat) {
-            $result = $grade->update($source);
-        }
-
-        if (!$result) {
-            // something went wrong - better force final grade recalculation
-            $this->force_regrading();
-
-        } else if ($this->is_course_item() and !$this->needsupdate) {
-            if (!grade_regrade_final_grades($this->courseid, $userid, $this)) {
-                $this->force_regrading();
-            }
-
-        } else if (!$this->needsupdate) {
-            $course_item = grade_item::fetch_course_item($this->courseid);
-            if (!$course_item->needsupdate) {
-                if (!grade_regrade_final_grades($this->courseid, $userid, $this)) {
-                    $this->force_regrading();
-                }
-            } else {
-                $this->force_regrading();
-            }
-        }
-
-        return $result;
-    }
-
-
-    /**
-     * Updates raw grade value for given user, this is a only way to update raw
-     * grades from external source (modules, etc.),
-     * because it logs the change in history table and deals with final grade recalculation.
-     *
-     * @param int $userid the graded user
-     * @param mixed $rawgrade float value of raw grade - false means do not change
-     * @param string $howmodified modification source
-     * @param string $note optional note
-     * @param mixed $feedback teachers feedback as string - false means do not change
-     * @param int $feedbackformat
-     * @return boolean success
-     */
-    function update_raw_grade($userid, $rawgrade=false, $source=NULL, $note=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
-        global $USER;
-
-        if (empty($usermodified)) {
-            $usermodified = $USER->id;
-        }
-
-        $result = true;
-
-        // calculated grades can not be updated; course and category can not be updated  because they are aggregated
-        if ($this->is_calculated() or $this->is_outcome_item() or !$this->is_normal_item()
-         or $this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
-            return false;
-        }
-
-        $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid));
-        $grade->grade_item =& $this; // prevent db fetching of this grade_item
-
-        $grade->usermodified = $usermodified;
-
-        if ($grade->is_locked()) {
-            // do not update locked grades at all
-            return false;
-        }
-
-        $locktime = $grade->get_locktime();
-        if ($locktime and $locktime < time()) {
-            // do not update grades that should be already locked and force regrade
-            $this->force_regrading();
-            return false;
-        }
-
-        $oldgrade = new object();
-        $oldgrade->finalgrade     = $grade->finalgrade;
-        $oldgrade->rawgrade       = $grade->rawgrade;
-        $oldgrade->rawgrademin    = $grade->rawgrademin;
-        $oldgrade->rawgrademax    = $grade->rawgrademax;
-        $oldgrade->rawscaleid     = $grade->rawscaleid;
-        $oldgrade->feedback       = $grade->feedback;
-        $oldgrade->feedbackformat = $grade->feedbackformat;
-
-        // fist copy current grademin/max and scale
-        $grade->rawgrademin = $this->grademin;
-        $grade->rawgrademax = $this->grademax;
-        $grade->rawscaleid  = $this->scaleid;
-
-        // change raw grade?
-        if ($rawgrade !== false) {
-            $grade->rawgrade = $rawgrade;
-        }
-
-        // do we have comment from teacher?
-        if ($feedback !== false) {
-            $grade->feedback       = $feedback;
-            $grade->feedbackformat = $feedbackformat;
-        }
-
-        if (empty($grade->id)) {
-            $result = (boolean)$grade->insert($source);
-
-        } else if ($grade->finalgrade     !== $oldgrade->finalgrade
-                or $grade->rawgrade       !== $oldgrade->rawgrade
-                or $grade->rawgrademin    !== $oldgrade->rawgrademin
-                or $grade->rawgrademax    !== $oldgrade->rawgrademax
-                or $grade->rawscaleid     !== $oldgrade->rawscaleid
-                or $grade->feedback       !== $oldgrade->feedback
-                or $grade->feedbackformat !== $oldgrade->feedbackformat) {
-
-            $result = $grade->update($source);
-        }
-
-        if (!$result) {
-            // something went wrong - better force final grade recalculation
-            $this->force_regrading();
-
-        } else if (!$this->needsupdate) {
-            $course_item = grade_item::fetch_course_item($this->courseid);
-            if (!$course_item->needsupdate) {
-                if (!grade_regrade_final_grades($this->courseid, $userid, $this)) {
-                    $this->force_regrading();
-                }
-            } else {
-                $this->force_regrading();
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * Calculates final grade values using the formula in calculation property.
-     * The parameters are taken from final grades of grade items in current course only.
-     * @return boolean false if error
-     */
-    function compute($userid=null) {
-        global $CFG;
-
-        if (!$this->is_calculated()) {
-            return false;
-        }
-
-        require_once($CFG->libdir.'/mathslib.php');
-
-        if ($this->is_locked()) {
-            return true; // no need to recalculate locked items
-        }
-
-        // get used items
-        $useditems = $this->depends_on();
-
-        // prepare formula and init maths library
-        $formula = preg_replace('/##(gi\d+)##/', '\1', $this->calculation);
-        $this->formula = new calc_formula($formula);
-
-        // where to look for final grades?
-        // this itemid is added so that we use only one query for source and final grades
-        $gis = implode(',', array_merge($useditems, array($this->id)));
-
-        if ($userid) {
-            $usersql = "AND g.userid=$userid";
-        } else {
-            $usersql = "";
-        }
-
-        $grade_inst = new grade_grade();
-        $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
-
-        $sql = "SELECT $fields
-                  FROM {$CFG->prefix}grade_grades g, {$CFG->prefix}grade_items gi
-                 WHERE gi.id = g.itemid AND gi.courseid={$this->courseid} AND gi.id IN ($gis) $usersql
-              ORDER BY g.userid";
-
-        $return = true;
-
-        // group the grades by userid and use formula on the group
-        if ($rs = get_recordset_sql($sql)) {
-            $prevuser = 0;
-            $grade_records   = array();
-            $oldgrade    = null;
-            while ($used = rs_fetch_next_record($rs)) {
-                if ($used->userid != $prevuser) {
-                    if (!$this->use_formula($prevuser, $grade_records, $useditems, $oldgrade)) {
-                        $return = false;
-                    }
-                    $prevuser = $used->userid;
-                    $grade_records   = array();
-                    $oldgrade    = null;
-                }
-                if ($used->itemid == $this->id) {
-                    $oldgrade = $used;
-                }
-                $grade_records['gi'.$used->itemid] = $used->finalgrade;
-            }
-            if (!$this->use_formula($prevuser, $grade_records, $useditems, $oldgrade)) {
-                $return = false;
-            }
-        }
-        rs_close($rs);
-
-        return $return;
-    }
-
-    /**
-     * internal function - does the final grade calculation
-     */
-    function use_formula($userid, $params, $useditems, $oldgrade) {
-        if (empty($userid)) {
-            return true;
-        }
-
-        // add missing final grade values
-        // not graded (null) is counted as 0 - the spreadsheet way
-        foreach($useditems as $gi) {
-            if (!array_key_exists('gi'.$gi, $params)) {
-                $params['gi'.$gi] = 0;
-            } else {
-                $params['gi'.$gi] = (float)$params['gi'.$gi];
-            }
-        }
-
-        // can not use own final grade during calculation
-        unset($params['gi'.$this->id]);
-
-        // insert final grade - will be needed later anyway
-        if ($oldgrade) {
-            $grade = new grade_grade($oldgrade, false); // fetching from db is not needed
-            $grade->grade_item =& $this;
-
-        } else {
-            $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid), false);
-            $grade->insert('system');
-            $grade->grade_item =& $this;
-
-            $oldgrade = new object();
-            $oldgrade->finalgrade  = $grade->finalgrade;
-            $oldgrade->rawgrade    = $grade->rawgrade;
-        }
-
-        // no need to recalculate locked or overridden grades
-        if ($grade->is_locked() or $grade->is_overridden()) {
-            return true;
-        }
-
-        // do the calculation
-        $this->formula->set_params($params);
-        $result = $this->formula->evaluate();
-
-        // no raw grade for calculated grades - only final
-        $grade->rawgrade = null;
-
-
-        if ($result === false) {
-            $grade->finalgrade = null;
-
-        } else {
-            // normalize
-            $result = bounded_number($this->grademin, $result, $this->grademax);
-            if ($this->gradetype == GRADE_TYPE_SCALE) {
-                $result = round($result+0.00001); // round scales upwards
-            }
-            $grade->finalgrade = $result;
-        }
-
-        // update in db if changed
-        if (   $grade->finalgrade  !== $oldgrade->finalgrade
-            or $grade->rawgrade    !== $oldgrade->rawgrade) {
-
-            $grade->update('system');
-        }
-
-        if ($result !== false) {
-            //lock grade if needed
-        }
-
-        if ($result === false) {
-            return false;
-        } else {
-            return true;
-        }
-
-    }
-
-    /**
-     * Validate the formula.
-     * @param string $formula
-     * @return boolean true if calculation possible, false otherwise
-     */
-    function validate_formula($formulastr) {
-        global $CFG;
-        require_once($CFG->libdir.'/mathslib.php');
-
-        $formulastr = grade_item::normalize_formula($formulastr, $this->courseid);
-
-        if (empty($formulastr)) {
-            return true;
-        }
-
-        if (strpos($formulastr, '=') !== 0) {
-            return get_string('errorcalculationnoequal', 'grades');
-        }
-
-        // get used items
-        if (preg_match_all('/##gi(\d+)##/', $formulastr, $matches)) {
-            $useditems = array_unique($matches[1]); // remove duplicates
-        } else {
-            $useditems = array();
-        }
-        
-        // MDL-11902
-        // unset the value if formula is trying to reference to itself
-        // but array keys does not match itemid
-        if (!empty($this->id)) {
-            $useditems = array_diff($useditems, array($this->id));
-            //unset($useditems[$this->id]);
-        }
-
-        // prepare formula and init maths library
-        $formula = preg_replace('/##(gi\d+)##/', '\1', $formulastr);
-        $formula = new calc_formula($formula);
-
-
-        if (empty($useditems)) {
-            $grade_items = array();
-
-        } else {
-            $gis = implode(',', $useditems);
-
-            $sql = "SELECT gi.*
-                      FROM {$CFG->prefix}grade_items gi
-                     WHERE gi.id IN ($gis) and gi.courseid={$this->courseid}"; // from the same course only!
-
-            if (!$grade_items = get_records_sql($sql)) {
-                $grade_items = array();
-            }
-        }
-
-        $params = array();
-        foreach ($useditems as $itemid) {
-            // make sure all grade items exist in this course
-            if (!array_key_exists($itemid, $grade_items)) {
-                return false;
-            }
-            // use max grade when testing formula, this should be ok in 99.9%
-            // division by 0 is one of possible problems
-            $params['gi'.$grade_items[$itemid]->id] = $grade_items[$itemid]->grademax;
-        }
-
-        // do the calculation
-        $formula->set_params($params);
-        $result = $formula->evaluate();
-
-        // false as result indicates some problem
-        if ($result === false) {
-            // TODO: add more error hints
-            return get_string('errorcalculationunknown', 'grades');
-        } else {
-            return true;
-        }
-    }
-
-    /**
-     * Returns the value of the display type. It can be set at 3 levels: grade_item, course setting and site. The lowest level overrides the higher ones.
-     * @return int Display type
-     */
-    function get_displaytype() {
-        global $CFG;
-
-        if ($this->display == GRADE_DISPLAY_TYPE_DEFAULT) {
-            return grade_get_setting($this->courseid, 'displaytype', $CFG->grade_displaytype);
-
-        } else {
-            return $this->display;
-        }
-    }
-
-    /**
-     * Returns the value of the decimals field. It can be set at 3 levels: grade_item, course setting and site. The lowest level overrides the higher ones.
-     * @return int Decimals (0 - 5)
-     */
-    function get_decimals() {
-        global $CFG;
-
-        if (is_null($this->decimals)) {
-            return grade_get_setting($this->courseid, 'decimalpoints', $CFG->grade_decimalpoints);
-
-        } else {
-            return $this->decimals;
-        }
-    }
-}
-?>
+<?php // $Id$
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com       //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+require_once('grade_object.php');
+
+/**
+ * Class representing a grade item. It is responsible for handling its DB representation,
+ * modifying and returning its metadata.
+ */
+class grade_item extends grade_object {
+    /**
+     * DB Table (used by grade_object).
+     * @var string $table
+     */
+    var $table = 'grade_items';
+
+    /**
+     * Array of required table fields, must start with 'id'.
+     * @var array $required_fields
+     */
+    var $required_fields = array('id', 'courseid', 'categoryid', 'itemname', 'itemtype', 'itemmodule', 'iteminstance',
+                                 'itemnumber', 'iteminfo', 'idnumber', 'calculation', 'gradetype', 'grademax', 'grademin',
+                                 'scaleid', 'outcomeid', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef',
+                                 'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime', 'needsupdate', 'timecreated',
+                                 'timemodified');
+
+    /**
+     * The course this grade_item belongs to.
+     * @var int $courseid
+     */
+    var $courseid;
+
+    /**
+     * The category this grade_item belongs to (optional).
+     * @var int $categoryid
+     */
+    var $categoryid;
+
+    /**
+     * The grade_category object referenced $this->iteminstance (itemtype must be == 'category' or == 'course' in that case).
+     * @var object $item_category
+     */
+    var $item_category;
+
+    /**
+     * The grade_category object referenced by $this->categoryid.
+     * @var object $parent_category
+     */
+    var $parent_category;
+
+
+    /**
+     * The name of this grade_item (pushed by the module).
+     * @var string $itemname
+     */
+    var $itemname;
+
+    /**
+     * e.g. 'category', 'course' and 'mod', 'blocks', 'import', etc...
+     * @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 $iteminfo;
+
+    /**
+     * Arbitrary idnumber provided by the module responsible.
+     * @var string $idnumber
+     */
+    var $idnumber;
+
+    /**
+     * Calculation string used for this item.
+     * @var string $calculation
+     */
+    var $calculation;
+
+    /**
+     * Indicates if we already tried to normalize the grade calculation formula.
+     * This flag helps to minimize db access when broken formulas used in calculation.
+     * @var boolean
+     */
+    var $calculation_normalized;
+    /**
+     * Math evaluation object
+     */
+    var $formula;
+
+    /**
+     * The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)
+     * @var int $gradetype
+     */
+    var $gradetype = GRADE_TYPE_VALUE;
+
+    /**
+     * Maximum allowable grade.
+     * @var float $grademax
+     */
+    var $grademax = 100;
+
+    /**
+     * Minimum allowable grade.
+     * @var float $grademin
+     */
+    var $grademin = 0;
+
+    /**
+     * 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 $outcomeid;
+
+    /**
+     * The grade_outcome this grade is associated with, if applicable.
+     * @var object $outcome
+     */
+    var $outcome;
+
+    /**
+     * grade required to pass. (grademin <= gradepass <= grademax)
+     * @var float $gradepass
+     */
+    var $gradepass = 0;
+
+    /**
+     * Multiply all grades by this number.
+     * @var float $multfactor
+     */
+    var $multfactor = 1.0;
+
+    /**
+     * Add this to all grades.
+     * @var float $plusfactor
+     */
+    var $plusfactor = 0;
+
+    /**
+     * Aggregation coeficient used for weighted averages
+     * @var float $aggregationcoef
+     */
+    var $aggregationcoef = 0;
+
+    /**
+     * Sorting order of the columns.
+     * @var int $sortorder
+     */
+    var $sortorder = 0;
+
+    /**
+     * Display type of the grades (Real, Percentage, Letter, or default).
+     * @var int $display
+     */
+    var $display = GRADE_DISPLAY_TYPE_DEFAULT;
+
+    /**
+     * The number of digits after the decimal point symbol. Applies only to REAL and PERCENTAGE grade display types.
+     * @var int $decimals
+     */
+    var $decimals = null;
+
+    /**
+     * 0 if visible, 1 always hidden or date not visible until
+     * @var int $hidden
+     */
+    var $hidden = 0;
+
+    /**
+     * Grade item lock flag. Empty if not locked, locked if any value present, usually date when item was locked. Locking prevents updating.
+     * @var int $locked
+     */
+    var $locked = 0;
+
+    /**
+     * Date after which the grade will be locked. Empty means no automatic locking.
+     * @var int $locktime
+     */
+    var $locktime = 0;
+
+    /**
+     * If set, the whole column will be recalculated, then this flag will be switched off.
+     * @var boolean $needsupdate
+     */
+    var $needsupdate = 1;
+
+    /**
+     * Cached dependson array
+     */
+    var $dependson_cache = null;
+
+    /**
+     * In addition to update() as defined in grade_object, handle the grade_outcome and grade_scale objects.
+     * Force regrading if necessary
+     * @param string $source from where was the object inserted (mod/forum, manual, etc.)
+     * @return boolean success
+     */
+    function update($source=null) {
+        // reset caches
+        $this->dependson_cache = null;
+
+        // Retrieve scale and infer grademax/min from it if needed
+        $this->load_scale();
+
+        // make sure there is not 0 in outcomeid
+        if (empty($this->outcomeid)) {
+            $this->outcomeid = null;
+        }
+
+        if ($this->qualifies_for_regrading()) {
+            $this->force_regrading();
+        }
+
+        return parent::update($source);
+    }
+
+    /**
+     * Compares the values held by this object with those of the matching record in DB, and returns
+     * whether or not these differences are sufficient to justify an update of all parent objects.
+     * This assumes that this object has an id number and a matching record in DB. If not, it will return false.
+     * @return boolean
+     */
+    function qualifies_for_regrading() {
+        if (empty($this->id)) {
+            return false;
+        }
+
+        $db_item = new grade_item(array('id' => $this->id));
+
+        $calculationdiff = $db_item->calculation != $this->calculation;
+        $categorydiff    = $db_item->categoryid  != $this->categoryid;
+        $gradetypediff   = $db_item->gradetype   != $this->gradetype;
+        $grademaxdiff    = $db_item->grademax    != $this->grademax;
+        $grademindiff    = $db_item->grademin    != $this->grademin;
+        $scaleiddiff     = $db_item->scaleid     != $this->scaleid;
+        $outcomeiddiff   = $db_item->outcomeid   != $this->outcomeid;
+        $multfactordiff  = $db_item->multfactor  != $this->multfactor;
+        $plusfactordiff  = $db_item->plusfactor  != $this->plusfactor;
+        $locktimediff    = $db_item->locktime    != $this->locktime;
+        $acoefdiff       = $db_item->aggregationcoef != $this->aggregationcoef;
+
+        $needsupdatediff = !$db_item->needsupdate &&  $this->needsupdate;    // force regrading only if setting the flag first time
+        $lockeddiff      = !empty($db_item->locked) && empty($this->locked); // force regrading only when unlocking
+
+        return ($calculationdiff || $categorydiff || $gradetypediff || $grademaxdiff || $grademindiff || $scaleiddiff
+             || $outcomeiddiff || $multfactordiff || $plusfactordiff || $needsupdatediff
+             || $lockeddiff || $acoefdiff || $locktimediff);
+    }
+
+    /**
+     * Finds and returns a grade_item instance based on params.
+     * @static
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object grade_item instance or false if none found.
+     */
+    function fetch($params) {
+        return grade_object::fetch_helper('grade_items', 'grade_item', $params);
+    }
+
+    /**
+     * Finds and returns all grade_item instances based on params.
+     * @static
+     *
+     * @param array $params associative arrays varname=>value
+     * @return array array of grade_item insatnces or false if none found.
+     */
+    function fetch_all($params) {
+        return grade_object::fetch_all_helper('grade_items', 'grade_item', $params);
+    }
+
+    /**
+     * Delete all grades and force_regrading of parent category.
+     * @param string $source from where was the object deleted (mod/forum, manual, etc.)
+     * @return boolean success
+     */
+    function delete($source=null) {
+        if (!$this->is_course_item()) {
+            $this->force_regrading();
+        }
+
+        if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
+            foreach ($grades as $grade) {
+                $grade->delete($source);
+            }
+        }
+
+        return parent::delete($source);
+    }
+
+    /**
+     * In addition to perform parent::insert(), calls force_regrading() method too.
+     * @param string $source from where was the object inserted (mod/forum, manual, etc.)
+     * @return int PK ID if successful, false otherwise
+     */
+    function insert($source=null) {
+        global $CFG;
+
+        if (empty($this->courseid)) {
+            error('Can not insert grade item without course id!');
+        }
+
+        // load scale if needed
+        $this->load_scale();
+
+        // add parent category if needed
+        if (empty($this->categoryid) and !$this->is_course_item() and !$this->is_category_item()) {
+            $course_category = grade_category::fetch_course_category($this->courseid);
+            $this->categoryid = $course_category->id;
+
+        }
+
+        // always place the new items at the end, move them after insert if needed
+        $last_sortorder = get_field_select('grade_items', 'MAX(sortorder)', "courseid = {$this->courseid}");
+        if (!empty($last_sortorder)) {
+            $this->sortorder = $last_sortorder + 1;
+        } else {
+            $this->sortorder = 1;
+        }
+
+        // add proper item numbers to manual items
+        if ($this->itemtype == 'manual') {
+            if (empty($this->itemnumber)) {
+                $this->itemnumber = 0;
+            }
+        }
+
+        // make sure there is not 0 in outcomeid
+        if (empty($this->outcomeid)) {
+            $this->outcomeid = null;
+        }
+
+        if (parent::insert($source)) {
+            // force regrading of items if needed
+            $this->force_regrading();
+            return $this->id;
+
+        } else {
+            debugging("Could not insert this grade_item in the database!");
+            return false;
+        }
+    }
+
+    /**
+     * Set idnumber of grade item, updates also course_modules table
+     * @param string $idnumber (without magic quotes)
+     * @return boolean success
+     */
+    function add_idnumber($idnumber) {
+        if (!empty($this->idnumber)) {
+            return false;
+        }
+
+        if ($this->itemtype == 'mod' and !$this->is_outcome_item()) {
+            if (!$cm = get_coursemodule_from_instance($this->itemmodule, $this->iteminstance, $this->courseid)) {
+                return false;
+            }
+            if (!empty($cm->idnumber)) {
+                return false;
+            }
+            if (set_field('course_modules', 'idnumber', addslashes($idnumber), 'id', $cm->id)) {
+                $this->idnumber = $idnumber;
+                return $this->update();
+            }
+            return false;
+
+        } else {
+            $this->idnumber = $idnumber;
+            return $this->update();
+        }
+    }
+
+    /**
+     * 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
+     * $userid is given and the grade_item is unlocked.
+     *
+     * @param int $userid
+     * @return boolean Locked state
+     */
+    function is_locked($userid=NULL) {
+        if (!empty($this->locked)) {
+            return true;
+        }
+
+        if (!empty($userid)) {
+            if ($grade = grade_grade::fetch(array('itemid'=>$this->id, 'userid'=>$userid))) {
+                $grade->grade_item =& $this; // prevent db fetching of cached grade_item
+                return $grade->is_locked();
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Locks or unlocks this grade_item and (optionally) all its associated final grades.
+     * @param int $locked 0, 1 or a timestamp int(10) after which date the item will be locked.
+     * @param boolean $cascade lock/unlock child objects too
+     * @param boolean $refresh refresh grades when unlocking
+     * @return boolean true if grade_item all grades updated, false if at least one update fails
+     */
+    function set_locked($lockedstate, $cascade=false, $refresh=true) {
+        if ($lockedstate) {
+        /// setting lock
+            if ($this->needsupdate) {
+                return false; // can not lock grade without first having final grade
+            }
+
+            $this->locked = time();
+            $this->update();
+
+            if ($cascade) {
+                $grades = $this->get_final();
+                foreach($grades as $g) {
+                    $grade = new grade_grade($g, false);
+                    $grade->grade_item =& $this;
+                    $grade->set_locked(1, null, false);
+                }
+            }
+
+            return true;
+
+        } else {
+        /// removing lock
+            if (!empty($this->locked) and $this->locktime < time()) {
+                //we have to reset locktime or else it would lock up again
+                $this->locktime = 0;
+            }
+
+            $this->locked = 0;
+            $this->update();
+
+            if ($cascade) {
+                if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
+                    foreach($grades as $grade) {
+                        $grade->grade_item =& $this;
+                        $grade->set_locked(0, null, false);
+                    }
+                }
+            }
+
+            if ($refresh) {
+                //refresh when unlocking
+                $this->refresh_grades();
+            }
+
+            return true;
+        }
+    }
+
+    /**
+     * Lock the grade if needed - make sure this is called only when final grades are valid
+     */
+    function check_locktime() {
+        if (!empty($this->locked)) {
+            return; // already locked
+        }
+
+        if ($this->locktime and $this->locktime < time()) {
+            $this->locked = time();
+            $this->update('locktime');
+        }
+    }
+
+    /**
+     * Set the locktime for this grade item.
+     *
+     * @param int $locktime timestamp for lock to activate
+     * @return void
+     */
+    function set_locktime($locktime) {
+        $this->locktime = $locktime;
+        $this->update();
+    }
+
+    /**
+     * Set the locktime for this grade item.
+     *
+     * @return int $locktime timestamp for lock to activate
+     */
+    function get_locktime() {
+        return $this->locktime;
+    }
+
+    /**
+     * Returns the hidden state of this grade_item
+     * @return boolean hidden state
+     */
+    function is_hidden() {
+        return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()));
+    }
+
+    /**
+     * Check grade item hidden status.
+     * @return int 0 means visible, 1 hidden always, timestamp hidden until
+     */
+    function get_hidden() {
+        return $this->hidden;
+    }
+
+    /**
+     * Set the hidden status of grade_item and all grades, 0 mean visible, 1 always hidden, number means date to hide until.
+     * @param int $hidden new hidden status
+     * @param boolean $cascade apply to child objects too
+     * @return void
+     */
+    function set_hidden($hidden, $cascade=false) {
+        $this->hidden = $hidden;
+        $this->update();
+
+        if ($cascade) {
+            if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
+                foreach($grades as $grade) {
+                    $grade->grade_item =& $this;
+                    $grade->set_hidden($hidden, $cascade);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the number of grades that are hidden.
+     * @param return int Number of hidden grades
+     */
+    function has_hidden_grades($groupsql="", $groupwheresql="") {
+        global $CFG;
+        return get_field_sql("SELECT COUNT(*) FROM {$CFG->prefix}grade_grades g LEFT JOIN "
+                            ."{$CFG->prefix}user u ON g.userid = u.id $groupsql WHERE itemid = $this->id AND hidden = 1 $groupwheresql");
+    }
+
+    /**
+     * Mark regrading as finished successfully.
+     */
+    function regrading_finished() {
+        $this->needsupdate = 0;
+        //do not use $this->update() because we do not want this logged in grade_item_history
+        set_field('grade_items', 'needsupdate', 0, 'id', $this->id);
+    }
+
+    /**
+     * Performs the necessary calculations on the grades_final referenced by this grade_item.
+     * Also resets the needsupdate flag once successfully performed.
+     *
+     * This function must be used ONLY from lib/gradeslib.php/grade_regrade_final_grades(),
+     * because the regrading must be done in correct order!!
+     *
+     * @return boolean true if ok, error string otherwise
+     */
+    function regrade_final_grades($userid=null) {
+        global $CFG;
+
+        // locked grade items already have correct final grades
+        if ($this->is_locked()) {
+            return true;
+        }
+
+        // calculation produces final value using formula from other final values
+        if ($this->is_calculated()) {
+            if ($this->compute($userid)) {
+                return true;
+            } else {
+                return "Could not calculate grades for grade item"; // TODO: improve and localize
+            }
+
+        // noncalculated outcomes already have final values - raw grades not used
+        } else if ($this->is_outcome_item()) {
+            return true;
+
+        // aggregate the category grade
+        } else if ($this->is_category_item() or $this->is_course_item()) {
+            // aggregate category grade item
+            $category = $this->get_item_category();
+            $category->grade_item =& $this;
+            if ($category->generate_grades($userid)) {
+                return true;
+            } else {
+                return "Could not aggregate final grades for category:".$this->id; // TODO: improve and localize
+            }
+
+        } else if ($this->is_manual_item()) {
+            // manual items track only final grades, no raw grades
+            return true;
+
+        } else if (!$this->is_raw_used()) {
+            // hmm - raw grades are not used- nothing to regrade
+            return true;
+        }
+
+        // normal grade item - just new final grades
+        $result = true;
+        $grade_inst = new grade_grade();
+        $fields = implode(',', $grade_inst->required_fields);
+        if ($userid) {
+            $rs = get_recordset_select('grade_grades', "itemid={$this->id} AND userid=$userid", '', $fields);
+        } else {
+            $rs = get_recordset('grade_grades', 'itemid', $this->id, '', $fields);
+        }
+        if ($rs) {
+            while ($grade_record = rs_fetch_next_record($rs)) {
+                $grade = new grade_grade($grade_record, false);
+
+                if (!empty($grade_record->locked) or !empty($grade_record->overridden)) {
+                    // this grade is locked - final grade must be ok
+                    continue;
+                }
+
+                $grade->finalgrade = $this->adjust_raw_grade($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
+
+                if ($grade_record->finalgrade !== $grade->finalgrade) {
+                    if (!$grade->update('system')) {
+                        $result = "Internal error updating final grade";
+                    }
+                }
+            }
+            rs_close($rs);
+        }
+
+        return $result;
+    }
+
+    /**
+     * 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 $rawgrade The raw grade value.
+     * @return mixed
+     */
+    function adjust_raw_grade($rawgrade, $rawmin, $rawmax) {
+        if (is_null($rawgrade)) {
+            return null;
+        }
+
+        if ($this->gradetype == GRADE_TYPE_VALUE) { // Dealing with numerical grade
+
+            if ($this->grademax < $this->grademin) {
+                return null;
+            }
+
+            if ($this->grademax == $this->grademin) {
+                return $this->grademax; // no range
+            }
+
+            // Standardise score to the new grade range
+            // NOTE: this is not compatible with current assignment grading
+            if ($rawmin != $this->grademin or $rawmax != $this->grademax) {
+                $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
+            }
+
+            // Apply other grade_item factors
+            $rawgrade *= $this->multfactor;
+            $rawgrade += $this->plusfactor;
+
+            return bounded_number($this->grademin, $rawgrade, $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 ($rawmin != $this->grademin or $rawmax != $this->grademax) {
+                $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
+            }
+
+            return (int)bounded_number(0, round($rawgrade+0.00001), $this->grademax);
+
+
+        } 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;;
+        }
+    }
+
+    /**
+     * Sets this grade_item's needsupdate to true. Also marks the course item as needing update.
+     * @return void
+     */
+    function force_regrading() {
+        $this->needsupdate = 1;
+        //mark this item and course item only - categories and calculated items are always regraded
+        $wheresql = "(itemtype='course' OR id={$this->id}) AND courseid={$this->courseid}";
+        set_field_select('grade_items', 'needsupdate', 1, $wheresql);
+    }
+
+    /**
+     * Instantiates a grade_scale object whose data is retrieved from the DB,
+     * if this item's scaleid variable is set.
+     * @return object grade_scale or null if no scale used
+     */
+    function load_scale() {
+        if ($this->gradetype != GRADE_TYPE_SCALE) {
+            $this->scaleid = null;
+        }
+
+        if (!empty($this->scaleid)) {
+            //do not load scale if already present
+            if (empty($this->scale->id) or $this->scale->id != $this->scaleid) {
+                $this->scale = grade_scale::fetch(array('id'=>$this->scaleid));
+                $this->scale->load_items();
+            }
+
+            // Until scales are uniformly set to min=0 max=count(scaleitems)-1 throughout Moodle, we
+            // stay with the current min=1 max=count(scaleitems)
+            $this->grademax = count($this->scale->scale_items);
+            $this->grademin = 1;
+
+        } else {
+            $this->scale = null;
+        }
+
+        return $this->scale;
+    }
+
+    /**
+     * 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(array('id'=>$this->outcomeid));
+        }
+        return $this->outcome;
+    }
+
+    /**
+    * Returns the grade_category object this grade_item belongs to (referenced by categoryid)
+    * or category attached to category item.
+    *
+    * @return mixed grade_category object if applicable, false if course item
+    */
+    function get_parent_category() {
+        if ($this->is_category_item() or $this->is_course_item()) {
+            return $this->get_item_category();
+
+        } else {
+            return grade_category::fetch(array('id'=>$this->categoryid));
+        }
+    }
+
+    /**
+     * Calls upon the get_parent_category method to retrieve the grade_category object
+     * from the DB and assigns it to $this->parent_category. It also returns the object.
+     * @return object Grade_category
+     */
+    function load_parent_category() {
+        if (empty($this->parent_category->id)) {
+            $this->parent_category = $this->get_parent_category();
+        }
+        return $this->parent_category;
+    }
+
+    /**
+    * Returns the grade_category for category item
+    *
+    * @return mixed grade_category object if applicable, false otherwise
+    */
+    function get_item_category() {
+        if (!$this->is_course_item() and !$this->is_category_item()) {
+            return false;
+        }
+        return grade_category::fetch(array('id'=>$this->iteminstance));
+    }
+
+    /**
+     * Calls upon the get_item_category method to retrieve the grade_category object
+     * from the DB and assigns it to $this->item_category. It also returns the object.
+     * @return object Grade_category
+     */
+    function load_item_category() {
+        if (empty($this->category->id)) {
+            $this->item_category = $this->get_item_category();
+        }
+        return $this->item_category;
+    }
+
+    /**
+     * Is the grade item associated with category?
+     * @return boolean
+     */
+    function is_category_item() {
+        return ($this->itemtype == 'category');
+    }
+
+    /**
+     * Is the grade item associated with course?
+     * @return boolean
+     */
+    function is_course_item() {
+        return ($this->itemtype == 'course');
+    }
+
+    /**
+     * Is this a manualy graded item?
+     * @return boolean
+     */
+    function is_manual_item() {
+        return ($this->itemtype == 'manual');
+    }
+
+    /**
+     * Is this an outcome item?
+     * @return boolean
+     */
+    function is_outcome_item() {
+        return !empty($this->outcomeid);
+    }
+
+    /**
+     * Is the grade item normal - associated with module, plugin or something else?
+     * @return boolean
+     */
+    function is_normal_item() {
+        return ($this->itemtype != 'course' and $this->itemtype != 'category' and $this->itemtype != 'manual');
+    }
+
+    /**
+     * Returns true if grade items uses raw grades
+     * @return boolean
+     */
+    function is_raw_used() {
+        return ($this->is_normal_item() and !$this->is_calculated() and !$this->is_outcome_item());
+    }
+
+    /**
+     * Returns grade item associated with the course
+     * @param int $courseid
+     * @return course item object
+     */
+    function fetch_course_item($courseid) {
+        if ($course_item = grade_item::fetch(array('courseid'=>$courseid, 'itemtype'=>'course'))) {
+            return $course_item;
+        }
+
+        // first get category - it creates the associated grade item
+        $course_category = grade_category::fetch_course_category($courseid);
+
+        return grade_item::fetch(array('courseid'=>$courseid, 'itemtype'=>'course'));
+    }
+
+    /**
+     * Is grading object editable?
+     * @return boolean
+     */
+    function is_editable() {
+        return true;
+    }
+
+    /**
+     * Checks if grade calculated. Returns this object's calculation.
+     * @return boolean true if grade item calculated.
+     */
+    function is_calculated() {
+        if (empty($this->calculation)) {
+            return false;
+        }
+
+        /*
+         * The main reason why we use the ##gixxx## instead of [[idnumber]] is speed of depends_on(),
+         * we would have to fetch all course grade items to find out the ids.
+         * Also if user changes the idnumber the formula does not need to be updated.
+         */
+
+        // first detect if we need to change calculation formula from [[idnumber]] to ##giXXX## (after backup, etc.)
+        if (!$this->calculation_normalized and preg_match('/##gi\d+##/', $this->calculation)) {
+            $this->set_calculation($this->calculation);
+        }
+
+        return !empty($this->calculation);
+    }
+
+    /**
+     * Returns calculation string if grade calculated.
+     * @return mixed string if calculation used, null if not
+     */
+    function get_calculation() {
+        if ($this->is_calculated()) {
+            return grade_item::denormalize_formula($this->calculation, $this->courseid);
+
+        } else {
+            return NULL;
+        }
+    }
+
+    /**
+     * 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 calculation is removed.
+     * @param string $formula string representation of formula used for calculation
+     * @return boolean success
+     */
+    function set_calculation($formula) {
+        $this->calculation = grade_item::normalize_formula($formula, $this->courseid);
+        $this->calculation_normalized = true;
+        return $this->update();
+    }
+
+    /**
+     * Denormalizes the calculation formula to [idnumber] form
+     * @static
+     * @param string $formula
+     * @return string denormalized string
+     */
+    function denormalize_formula($formula, $courseid) {
+        if (empty($formula)) {
+            return '';
+        }
+
+        // denormalize formula - convert ##giXX## to [[idnumber]]
+        if (preg_match_all('/##gi(\d+)##/', $formula, $matches)) {
+            foreach ($matches[1] as $id) {
+                if ($grade_item = grade_item::fetch(array('id'=>$id, 'courseid'=>$courseid))) {
+                    if (!empty($grade_item->idnumber)) {
+                        $formula = str_replace('##gi'.$grade_item->id.'##', '[['.$grade_item->idnumber.']]', $formula);
+                    }
+                }
+            }
+        }
+
+        return $formula;
+
+    }
+
+    /**
+     * Normalizes the calculation formula to [#giXX#] form
+     * @static
+     * @param string $formula
+     * @return string normalized string
+     */
+    function normalize_formula($formula, $courseid) {
+        $formula = trim($formula);
+
+        if (empty($formula)) {
+            return NULL;
+
+        }
+
+        // normalize formula - we want grade item ids ##giXXX## instead of [[idnumber]]
+        if ($grade_items = grade_item::fetch_all(array('courseid'=>$courseid))) {
+            foreach ($grade_items as $grade_item) {
+                $formula = str_replace('[['.$grade_item->idnumber.']]', '##gi'.$grade_item->id.'##', $formula);
+            }
+        }
+
+        return $formula;
+    }
+
+    /**
+     * 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
+     * @return mixed An array of all final_grades (stdClass objects) for this grade_item, or a single final_grade.
+     */
+    function get_final($userid=NULL) {
+        if ($userid) {
+            if ($user = get_record('grade_grades', 'itemid', $this->id, 'userid', $userid)) {
+                return $user;
+            }
+
+        } else {
+            if ($grades = get_records('grade_grades', 'itemid', $this->id)) {
+                //TODO: speed up with better SQL
+                $result = array();
+                foreach ($grades as $grade) {
+                    $result[$grade->userid] = $grade;
+                }
+                return $result;
+            } else {
+                return array();
+            }
+        }
+    }
+
+    /**
+     * Get (or create if not exist yet) grade for this user
+     * @param int $userid
+     * @return object grade_grade object instance
+     */
+    function get_grade($userid, $create=true) {
+        if (empty($this->id)) {
+            debugging('Can not use before insert');
+            return false;
+        }
+
+        $grade = new grade_grade(array('userid'=>$userid, 'itemid'=>$this->id));
+        if (empty($grade->id) and $create) {
+            $grade->insert();
+        }
+
+        return $grade;
+    }
+
+    /**
+     * Returns the sortorder of this grade_item. This method is also available in
+     * grade_category, for cases where the object type is not know.
+     * @return int Sort order
+     */
+    function get_sortorder() {
+        return $this->sortorder;
+    }
+
+    /**
+     * Returns the idnumber of this grade_item. This method is also available in
+     * grade_category, for cases where the object type is not know.
+     * @return string idnumber
+     */
+    function get_idnumber() {
+        return $this->idnumber;
+    }
+
+    /**
+     * Returns this grade_item. This method is also available in
+     * grade_category, for cases where the object type is not know.
+     * @return string idnumber
+     */
+    function get_grade_item() {
+        return $this;
+    }
+
+    /**
+     * Sets the sortorder of this grade_item. This method is also available in
+     * grade_category, for cases where the object type is not know.
+     * @param int $sortorder
+     * @return void
+     */
+    function set_sortorder($sortorder) {
+        $this->sortorder = $sortorder;
+        $this->update();
+    }
+
+    function move_after_sortorder($sortorder) {
+        global $CFG;
+
+        //make some room first
+        $sql = "UPDATE {$CFG->prefix}grade_items
+                   SET sortorder = sortorder + 1
+                 WHERE sortorder > $sortorder AND courseid = {$this->courseid}";
+        execute_sql($sql, false);
+
+        $this->set_sortorder($sortorder + 1);
+    }
+
+    /**
+     * 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() {
+        if (!empty($this->itemname)) {
+            // MDL-10557
+            return format_string($this->itemname);
+
+        } else if ($this->is_course_item()) {
+            return get_string('coursetotal', 'grades');
+
+        } else if ($this->is_category_item()) {
+            return get_string('categorytotal', 'grades');
+
+        } else {
+            return get_string('grade');
+        }
+    }
+
+    /**
+     * Sets this item's categoryid. A generic method shared by objects that have a parent id of some kind.
+     * @param int $parentid
+     * @return boolean success;
+     */
+    function set_parent($parentid) {
+        if ($this->is_course_item() or $this->is_category_item()) {
+            error('Can not set parent for category or course item!');
+        }
+
+        if ($this->categoryid == $parentid) {
+            return true;
+        }
+
+        // find parent and check course id
+        if (!$parent_category = grade_category::fetch(array('id'=>$parentid, 'courseid'=>$this->courseid))) {
+            return false;
+        }
+
+        $this->force_regrading();
+
+        // set new parent
+        $this->categoryid = $parent_category->id;
+        $this->parent_category =& $parent_category;
+
+        return $this->update();
+    }
+
+    /**
+     * Finds out on which other items does this depend directly when doing calculation or category agregation
+     * @param bool $reset_cache
+     * @return array of grade_item ids this one depends on
+     */
+    function depends_on($reset_cache=false) {
+        global $CFG;
+
+        if ($reset_cache) {
+            $this->dependson_cache = null;
+        } else if (isset($this->dependson_cache)) {
+            return $this->dependson_cache;
+        }
+
+        if ($this->is_locked()) {
+            // locked items do not need to be regraded
+            $this->dependson_cache = array();
+            return $this->dependson_cache;
+        }
+
+        if ($this->is_calculated()) {
+            if (preg_match_all('/##gi(\d+)##/', $this->calculation, $matches)) {
+                $this->dependson_cache = array_unique($matches[1]); // remove duplicates
+                return $this->dependson_cache;
+            } else {
+                $this->dependson_cache = array();
+                return $this->dependson_cache;
+            }
+
+        } else if ($grade_category = $this->load_item_category()) {
+            //only items with numeric or scale values can be aggregated
+            if ($this->gradetype != GRADE_TYPE_VALUE and $this->gradetype != GRADE_TYPE_SCALE) {
+                $this->dependson_cache = array();
+                return $this->dependson_cache;
+            }
+
+            // If global aggregateoutcomes is set, override category value
+            if ($CFG->grade_aggregateoutcomes != -1) {
+                $grade_category->aggregateoutcomes = $CFG->grade_aggregateoutcomes;
+            }
+
+            // If global aggregatesubcats is set, override category value
+            if ($CFG->grade_aggregatesubcats != -1) {
+                $grade_category->aggregatesubcats = $CFG->grade_aggregatesubcats;
+            }
+
+            if (empty($CFG->enableoutcomes) or $grade_category->aggregateoutcomes) {
+                $outcomes_sql = "";
+            } else {
+                $outcomes_sql = "AND gi.outcomeid IS NULL";
+            }
+
+            if ($grade_category->aggregatesubcats) {
+                // return all children excluding category items
+                $sql = "SELECT gi.id
+                          FROM {$CFG->prefix}grade_items gi
+                         WHERE (gi.gradetype = ".GRADE_TYPE_VALUE." OR gi.gradetype = ".GRADE_TYPE_SCALE.")
+                               $outcomes_sql
+                               AND gi.categoryid IN (
+                                  SELECT gc.id
+                                    FROM {$CFG->prefix}grade_categories gc
+                                   WHERE gc.path LIKE '%/{$grade_category->id}/%')";
+
+            } else {
+                $sql = "SELECT gi.id
+                          FROM {$CFG->prefix}grade_items gi
+                         WHERE gi.categoryid = {$grade_category->id}
+                               AND (gi.gradetype = ".GRADE_TYPE_VALUE." OR gi.gradetype = ".GRADE_TYPE_SCALE.")
+                               $outcomes_sql
+
+                        UNION
+
+                        SELECT gi.id
+                          FROM {$CFG->prefix}grade_items gi, {$CFG->prefix}grade_categories gc
+                         WHERE (gi.itemtype = 'category' OR gi.itemtype = 'course') AND gi.iteminstance=gc.id
+                               AND gc.parent = {$grade_category->id}
+                               AND (gi.gradetype = ".GRADE_TYPE_VALUE." OR gi.gradetype = ".GRADE_TYPE_SCALE.")
+                               $outcomes_sql";
+            }
+
+            if ($children = get_records_sql($sql)) {
+                $this->dependson_cache = array_keys($children);
+                return $this->dependson_cache;
+            } else {
+                $this->dependson_cache = array();
+                return $this->dependson_cache;
+            }
+
+        } else {
+            $this->dependson_cache = array();
+            return $this->dependson_cache;
+        }
+    }
+
+    /**
+     * Refetch grades from moudles, plugins.
+     * @param int $userid optional, one user only
+     */
+    function refresh_grades($userid=0) {
+        if ($this->itemtype == 'mod') {
+            if ($this->is_outcome_item()) {
+                //nothing to do
+                return;
+            }
+
+            if (!$activity = get_record($this->itemmodule, 'id', $this->iteminstance)) {
+                debugging('Can not find activity');
+                return;
+            }
+
+            if (! $cm = get_coursemodule_from_instance($this->itemmodule, $activity->id, $this->courseid)) {
+                debuggin('Can not find course module');
+                return;
+            }
+
+            $activity->modname    = $this->itemmodule;
+            $activity->cmidnumber = $cm->idnumber;
+
+            grade_update_mod_grades($activity);
+        }
+    }
+
+    /**
+     * Updates final grade value for given user, this is a only way to update final
+     * grades from gradebook and import because it logs the change in history table
+     * and deals with overridden flag. This flag is set to prevent later overriding
+     * from raw grades submitted from modules.
+     *
+     * @param int $userid the graded user
+     * @param mixed $finalgrade float value of final grade - false means do not change
+     * @param string $howmodified modification source
+     * @param string $note optional note
+     * @param mixed $feedback teachers feedback as string - false means do not change
+     * @param int $feedbackformat
+     * @return boolean success
+     */
+    function update_final_grade($userid, $finalgrade=false, $source=NULL, $note=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
+        global $USER, $CFG;
+
+        if (empty($usermodified)) {
+            $usermodified = $USER->id;
+        }
+
+        $result = true;
+
+        // no grading used or locked
+        if ($this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
+            return false;
+        }
+
+        $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid));
+        $grade->grade_item =& $this; // prevent db fetching of this grade_item
+
+        $grade->usermodified = $usermodified;
+
+        if ($grade->is_locked()) {
+            // do not update locked grades at all
+            return false;
+        }
+
+        $locktime = $grade->get_locktime();
+        if ($locktime and $locktime < time()) {
+            // do not update grades that should be already locked, force regrade instead
+            $this->force_regrading();
+            return false;
+        }
+
+        $oldgrade = new object();
+        $oldgrade->finalgrade     = $grade->finalgrade;
+        $oldgrade->overridden     = $grade->overridden;
+        $oldgrade->feedback       = $grade->feedback;
+        $oldgrade->feedbackformat = $grade->feedbackformat;
+
+        if ($finalgrade !== false or $feedback !== false) {
+            if (($this->is_outcome_item() or $this->is_manual_item()) and !$this->is_calculated()) {
+                // final grades updated only by user - no need for overriding
+                $grade->overridden = 0;
+
+            } else {
+                $grade->overridden = time();
+            }
+        }
+
+        if ($finalgrade !== false)  {
+            if (!is_null($finalgrade)) {
+                $finalgrade = bounded_number($this->grademin, $finalgrade, $this->grademax);
+            } else {
+                $finalgrade = $finalgrade;
+            }
+            $grade->finalgrade = $finalgrade;
+        }
+
+        // do we have comment from teacher?
+        if ($feedback !== false) {
+            $grade->feedback       = $feedback;
+            $grade->feedbackformat = $feedbackformat;
+        }
+
+        if (empty($grade->id)) {
+            $result = (boolean)$grade->insert($source);
+
+        } else if ($grade->finalgrade     !== $oldgrade->finalgrade
+                or $grade->feedback       !== $oldgrade->feedback
+                or $grade->feedbackformat !== $oldgrade->feedbackformat) {
+            $result = $grade->update($source);
+        }
+
+        if (!$result) {
+            // something went wrong - better force final grade recalculation
+            $this->force_regrading();
+
+        } else if ($this->is_course_item() and !$this->needsupdate) {
+            if (!grade_regrade_final_grades($this->courseid, $userid, $this)) {
+                $this->force_regrading();
+            }
+
+        } else if (!$this->needsupdate) {
+            $course_item = grade_item::fetch_course_item($this->courseid);
+            if (!$course_item->needsupdate) {
+                if (!grade_regrade_final_grades($this->courseid, $userid, $this)) {
+                    $this->force_regrading();
+                }
+            } else {
+                $this->force_regrading();
+            }
+        }
+
+        return $result;
+    }
+
+
+    /**
+     * Updates raw grade value for given user, this is a only way to update raw
+     * grades from external source (modules, etc.),
+     * because it logs the change in history table and deals with final grade recalculation.
+     *
+     * @param int $userid the graded user
+     * @param mixed $rawgrade float value of raw grade - false means do not change
+     * @param string $howmodified modification source
+     * @param string $note optional note
+     * @param mixed $feedback teachers feedback as string - false means do not change
+     * @param int $feedbackformat
+     * @return boolean success
+     */
+    function update_raw_grade($userid, $rawgrade=false, $source=NULL, $note=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
+        global $USER;
+
+        if (empty($usermodified)) {
+            $usermodified = $USER->id;
+        }
+
+        $result = true;
+
+        // calculated grades can not be updated; course and category can not be updated  because they are aggregated
+        if ($this->is_calculated() or $this->is_outcome_item() or !$this->is_normal_item()
+         or $this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
+            return false;
+        }
+
+        $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid));
+        $grade->grade_item =& $this; // prevent db fetching of this grade_item
+
+        $grade->usermodified = $usermodified;
+
+        if ($grade->is_locked()) {
+            // do not update locked grades at all
+            return false;
+        }
+
+        $locktime = $grade->get_locktime();
+        if ($locktime and $locktime < time()) {
+            // do not update grades that should be already locked and force regrade
+            $this->force_regrading();
+            return false;
+        }
+
+        $oldgrade = new object();
+        $oldgrade->finalgrade     = $grade->finalgrade;
+        $oldgrade->rawgrade       = $grade->rawgrade;
+        $oldgrade->rawgrademin    = $grade->rawgrademin;
+        $oldgrade->rawgrademax    = $grade->rawgrademax;
+        $oldgrade->rawscaleid     = $grade->rawscaleid;
+        $oldgrade->feedback       = $grade->feedback;
+        $oldgrade->feedbackformat = $grade->feedbackformat;
+
+        // fist copy current grademin/max and scale
+        $grade->rawgrademin = $this->grademin;
+        $grade->rawgrademax = $this->grademax;
+        $grade->rawscaleid  = $this->scaleid;
+
+        // change raw grade?
+        if ($rawgrade !== false) {
+            $grade->rawgrade = $rawgrade;
+        }
+
+        // do we have comment from teacher?
+        if ($feedback !== false) {
+            $grade->feedback       = $feedback;
+            $grade->feedbackformat = $feedbackformat;
+        }
+
+        if (empty($grade->id)) {
+            $result = (boolean)$grade->insert($source);
+
+        } else if ($grade->finalgrade     !== $oldgrade->finalgrade
+                or $grade->rawgrade       !== $oldgrade->rawgrade
+                or $grade->rawgrademin    !== $oldgrade->rawgrademin
+                or $grade->rawgrademax    !== $oldgrade->rawgrademax
+                or $grade->rawscaleid     !== $oldgrade->rawscaleid
+                or $grade->feedback       !== $oldgrade->feedback
+                or $grade->feedbackformat !== $oldgrade->feedbackformat) {
+
+            $result = $grade->update($source);
+        }
+
+        if (!$result) {
+            // something went wrong - better force final grade recalculation
+            $this->force_regrading();
+
+        } else if (!$this->needsupdate) {
+            $course_item = grade_item::fetch_course_item($this->courseid);
+            if (!$course_item->needsupdate) {
+                if (!grade_regrade_final_grades($this->courseid, $userid, $this)) {
+                    $this->force_regrading();
+                }
+            } else {
+                $this->force_regrading();
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Calculates final grade values using the formula in calculation property.
+     * The parameters are taken from final grades of grade items in current course only.
+     * @return boolean false if error
+     */
+    function compute($userid=null) {
+        global $CFG;
+
+        if (!$this->is_calculated()) {
+            return false;
+        }
+
+        require_once($CFG->libdir.'/mathslib.php');
+
+        if ($this->is_locked()) {
+            return true; // no need to recalculate locked items
+        }
+
+        // get used items
+        $useditems = $this->depends_on();
+
+        // prepare formula and init maths library
+        $formula = preg_replace('/##(gi\d+)##/', '\1', $this->calculation);
+        $this->formula = new calc_formula($formula);
+
+        // where to look for final grades?
+        // this itemid is added so that we use only one query for source and final grades
+        $gis = implode(',', array_merge($useditems, array($this->id)));
+
+        if ($userid) {
+            $usersql = "AND g.userid=$userid";
+        } else {
+            $usersql = "";
+        }
+
+        $grade_inst = new grade_grade();
+        $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
+
+        $sql = "SELECT $fields
+                  FROM {$CFG->prefix}grade_grades g, {$CFG->prefix}grade_items gi
+                 WHERE gi.id = g.itemid AND gi.courseid={$this->courseid} AND gi.id IN ($gis) $usersql
+              ORDER BY g.userid";
+
+        $return = true;
+
+        // group the grades by userid and use formula on the group
+        if ($rs = get_recordset_sql($sql)) {
+            $prevuser = 0;
+            $grade_records   = array();
+            $oldgrade    = null;
+            while ($used = rs_fetch_next_record($rs)) {
+                if ($used->userid != $prevuser) {
+                    if (!$this->use_formula($prevuser, $grade_records, $useditems, $oldgrade)) {
+                        $return = false;
+                    }
+                    $prevuser = $used->userid;
+                    $grade_records   = array();
+                    $oldgrade    = null;
+                }
+                if ($used->itemid == $this->id) {
+                    $oldgrade = $used;
+                }
+                $grade_records['gi'.$used->itemid] = $used->finalgrade;
+            }
+            if (!$this->use_formula($prevuser, $grade_records, $useditems, $oldgrade)) {
+                $return = false;
+            }
+        }
+        rs_close($rs);
+
+        return $return;
+    }
+
+    /**
+     * internal function - does the final grade calculation
+     */
+    function use_formula($userid, $params, $useditems, $oldgrade) {
+        if (empty($userid)) {
+            return true;
+        }
+
+        // add missing final grade values
+        // not graded (null) is counted as 0 - the spreadsheet way
+        foreach($useditems as $gi) {
+            if (!array_key_exists('gi'.$gi, $params)) {
+                $params['gi'.$gi] = 0;
+            } else {
+                $params['gi'.$gi] = (float)$params['gi'.$gi];
+            }
+        }
+
+        // can not use own final grade during calculation
+        unset($params['gi'.$this->id]);
+
+        // insert final grade - will be needed later anyway
+        if ($oldgrade) {
+            $grade = new grade_grade($oldgrade, false); // fetching from db is not needed
+            $grade->grade_item =& $this;
+
+        } else {
+            $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid), false);
+            $grade->insert('system');
+            $grade->grade_item =& $this;
+
+            $oldgrade = new object();
+            $oldgrade->finalgrade  = $grade->finalgrade;
+            $oldgrade->rawgrade    = $grade->rawgrade;
+        }
+
+        // no need to recalculate locked or overridden grades
+        if ($grade->is_locked() or $grade->is_overridden()) {
+            return true;
+        }
+
+        // do the calculation
+        $this->formula->set_params($params);
+        $result = $this->formula->evaluate();
+
+        // no raw grade for calculated grades - only final
+        $grade->rawgrade = null;
+
+
+        if ($result === false) {
+            $grade->finalgrade = null;
+
+        } else {
+            // normalize
+            $result = bounded_number($this->grademin, $result, $this->grademax);
+            if ($this->gradetype == GRADE_TYPE_SCALE) {
+                $result = round($result+0.00001); // round scales upwards
+            }
+            $grade->finalgrade = $result;
+        }
+
+        // update in db if changed
+        if (   $grade->finalgrade  !== $oldgrade->finalgrade
+            or $grade->rawgrade    !== $oldgrade->rawgrade) {
+
+            $grade->update('system');
+        }
+
+        if ($result !== false) {
+            //lock grade if needed
+        }
+
+        if ($result === false) {
+            return false;
+        } else {
+            return true;
+        }
+
+    }
+
+    /**
+     * Validate the formula.
+     * @param string $formula
+     * @return boolean true if calculation possible, false otherwise
+     */
+    function validate_formula($formulastr) {
+        global $CFG;
+        require_once($CFG->libdir.'/mathslib.php');
+
+        $formulastr = grade_item::normalize_formula($formulastr, $this->courseid);
+
+        if (empty($formulastr)) {
+            return true;
+        }
+
+        if (strpos($formulastr, '=') !== 0) {
+            return get_string('errorcalculationnoequal', 'grades');
+        }
+
+        // get used items
+        if (preg_match_all('/##gi(\d+)##/', $formulastr, $matches)) {
+            $useditems = array_unique($matches[1]); // remove duplicates
+        } else {
+            $useditems = array();
+        }
+
+        if (!empty($this->id)) {
+            unset($useditems[$this->id]);
+        }
+
+        // prepare formula and init maths library
+        $formula = preg_replace('/##(gi\d+)##/', '\1', $formulastr);
+        $formula = new calc_formula($formula);
+
+
+        if (empty($useditems)) {
+            $grade_items = array();
+
+        } else {
+            $gis = implode(',', $useditems);
+
+            $sql = "SELECT gi.*
+                      FROM {$CFG->prefix}grade_items gi
+                     WHERE gi.id IN ($gis) and gi.courseid={$this->courseid}"; // from the same course only!
+
+            if (!$grade_items = get_records_sql($sql)) {
+                $grade_items = array();
+            }
+        }
+
+        $params = array();
+        foreach ($useditems as $itemid) {
+            // make sure all grade items exist in this course
+            if (!array_key_exists($itemid, $grade_items)) {
+                return false;
+            }
+            // use max grade when testing formula, this should be ok in 99.9%
+            // division by 0 is one of possible problems
+            $params['gi'.$grade_items[$itemid]->id] = $grade_items[$itemid]->grademax;
+        }
+
+        // do the calculation
+        $formula->set_params($params);
+        $result = $formula->evaluate();
+
+        // false as result indicates some problem
+        if ($result === false) {
+            // TODO: add more error hints
+            return get_string('errorcalculationunknown', 'grades');
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Returns the value of the display type. It can be set at 3 levels: grade_item, course setting and site. The lowest level overrides the higher ones.
+     * @return int Display type
+     */
+    function get_displaytype() {
+        global $CFG;
+
+        if ($this->display == GRADE_DISPLAY_TYPE_DEFAULT) {
+            return grade_get_setting($this->courseid, 'displaytype', $CFG->grade_displaytype);
+
+        } else {
+            return $this->display;
+        }
+    }
+
+    /**
+     * Returns the value of the decimals field. It can be set at 3 levels: grade_item, course setting and site. The lowest level overrides the higher ones.
+     * @return int Decimals (0 - 5)
+     */
+    function get_decimals() {
+        global $CFG;
+
+        if (is_null($this->decimals)) {
+            return grade_get_setting($this->courseid, 'decimalpoints', $CFG->grade_decimalpoints);
+
+        } else {
+            return $this->decimals;
+        }
+    }
+}
+?>