Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1984 lines (1670 sloc) 67.891 kb
<?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, rounds the float numbers using php function,
* the reason is we need to compare the db value with computed number to skip regrading if possible.
* @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();
}
$this->timemodified = time();
$this->grademin = grade_floatval($this->grademin);
$this->grademax = grade_floatval($this->grademax);
$this->multfactor = grade_floatval($this->multfactor);
$this->plusfactor = grade_floatval($this->plusfactor);
$this->aggregationcoef = grade_floatval($this->aggregationcoef);
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;
$scaleiddiff = $db_item->scaleid != $this->scaleid;
$outcomeiddiff = $db_item->outcomeid != $this->outcomeid;
$locktimediff = $db_item->locktime != $this->locktime;
$grademindiff = grade_floats_different($db_item->grademin, $this->grademin);
$grademaxdiff = grade_floats_different($db_item->grademax, $this->grademax);
$multfactordiff = grade_floats_different($db_item->multfactor, $this->multfactor);
$plusfactordiff = grade_floats_different($db_item->plusfactor, $this->plusfactor);
$acoefdiff = grade_floats_different($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) {
$this->delete_all_grades($source);
return parent::delete($source);
}
/**
* Delete all grades
* @param string $source from where was the object deleted (mod/forum, manual, etc.)
* @return boolean success
*/
function delete_all_grades($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 true;
}
/**
* 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;
}
$this->timecreated = $this->timemodified = time();
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 hidden status. Uses data from both grade item and grade.
* @return boolean true if hiddenuntil, false if not
*/
function is_hiddenuntil() {
return $this->hidden > 1;
}
/**
* 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);
}
}
}
//if marking item visible make sure category is visible MDL-21367
if( !$hidden ) {
$category_array = grade_category::fetch_all(array('id'=>$this->categoryid));
if ($category_array && array_key_exists($this->categoryid, $category_array)) {
$category = $category_array[$this->categoryid];
//call set_hidden on the category regardless of whether it is hidden as its parent might be hidden
//if($category->is_hidden()) {
$category->set_hidden($hidden, false);
//}
}
}
}
/**
* 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_floats_different($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 float $rawgrade The raw grade value.
* @param float $rawmin original rawmin
* @param float $rawmax original rawmax
* @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 ($this->itemmodule != 'assignment' and ($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 $this->bounded_grade($rawgrade);
} 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 ($this->itemmodule != 'assignment' and ($rawmin != $this->grademin or $rawmax != $this->grademax)) {
$rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
}
return $this->bounded_grade($rawgrade);
} 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 {
debugging("Unknown 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));
if (!$this->scale) {
debugging('Incorrect scale id: '.$this->scaleid);
$this->scale = null;
return null;
}
$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->item_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 external - associated with module, plugin or something else?
* @return boolean
*/
function is_external_item() {
return ($this->itemtype == 'mod');
}
/**
* Is the grade item overridable
* @return boolean
*/
function is_overridable_item() {
return !$this->is_outcome_item() and ($this->is_external_item() or $this->is_calculated() or $this->is_course_item() or $this->is_category_item());
}
/**
* Is the grade item feedback overridable
* @return boolean
*/
function is_overridable_item_feedback() {
return !$this->is_outcome_item() and $this->is_external_item();
}
/**
* Returns true if grade items uses raw grades
* @return boolean
*/
function is_raw_used() {
return ($this->is_external_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 $course_category->get_grade_item();
}
/**
* 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 strpos($this->calculation, '[[') !== false) {
$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) {
if ($this->sortorder == $sortorder) {
return;
}
$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.
* @param boolean $fulltotal: if the item is a category total, returns $categoryname."total" instead of "Category total" or "Course total"
* @return string name
*/
function get_name($fulltotal=false) {
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()) {
if ($fulltotal) {
$category = $this->load_parent_category();
$a = new stdClass();
$a->category = $category->get_name();
return get_string('categorytotalfull', 'grades', $a);
} else {
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;
}
// MDL-19407 If moving from a non-SWM category to a SWM category, convert aggregationcoef to 0
$currentparent = $this->load_parent_category();
if ($currentparent->aggregation != GRADE_AGGREGATE_WEIGHTED_MEAN2 && $parent_category->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) {
$this->aggregationcoef = 0;
}
$this->force_regrading();
// set new parent
$this->categoryid = $parent_category->id;
$this->parent_category =& $parent_category;
return $this->update();
}
/**
* Makes sure value is a valid grade value.
* @param float $gradevalue
* @return mixed float or int fixed grade value
*/
function bounded_grade($gradevalue) {
global $CFG;
if (is_null($gradevalue)) {
return null;
}
if ($this->gradetype == GRADE_TYPE_SCALE) {
// no >100% grades hack for scale grades!
// 1.5 is rounded to 2 ;-)
return (int)bounded_number($this->grademin, round($gradevalue+0.00001), $this->grademax);
}
$grademax = $this->grademax;
// NOTE: if you change this value you must manually reset the needsupdate flag in all grade items
$maxcoef = isset($CFG->gradeoverhundredprocentmax) ? $CFG->gradeoverhundredprocentmax : 10; // 1000% max by default
if (!empty($CFG->unlimitedgrades)) {
// NOTE: if you change this value you must manually reset the needsupdate flag in all grade items
$grademax = $grademax * $maxcoef;
} else if ($this->is_category_item() or $this->is_course_item()) {
$category = $this->load_item_category();
if ($category->aggregation >= 100) {
// grade >100% hack
$grademax = $grademax * $maxcoef;
}
}
return (float)bounded_number($this->grademin, $gradevalue, $grademax);
}
/**
* 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 (empty($CFG->grade_includescalesinaggregation)) {
$gtypes = "gi.gradetype = ".GRADE_TYPE_VALUE;
} else {
$gtypes = "(gi.gradetype = ".GRADE_TYPE_VALUE." OR gi.gradetype = ".GRADE_TYPE_SCALE.")";
}
if ($grade_category->aggregatesubcats) {
// return all children excluding category items
$sql = "SELECT gi.id
FROM {$CFG->prefix}grade_items gi
WHERE $gtypes
$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 $gtypes
$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 $gtypes
$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 modules, 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 $this->itemmodule activity with id $this->iteminstance");
return;
}
if (!$cm = get_coursemodule_from_instance($this->itemmodule, $activity->id, $this->courseid)) {
debugging('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, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
global $USER, $CFG;
$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
if (empty($usermodified)) {
$grade->usermodified = $USER->id;
} else {
$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;
// changed grade?
if ($finalgrade !== false) {
if ($this->is_overridable_item()) {
$grade->overridden = time();
}
$grade->finalgrade = $this->bounded_grade($finalgrade);
}
// do we have comment from teacher?
if ($feedback !== false) {
if ($this->is_overridable_item_feedback()) {
// external items (modules, plugins) may have own feedback
$grade->overridden = time();
}
$grade->feedback = $feedback;
$grade->feedbackformat = $feedbackformat;
}
if (empty($grade->id)) {
$grade->timecreated = null; // hack alert - date submitted - no submission yet
$grade->timemodified = time(); // hack alert - date graded
$result = (boolean)$grade->insert($source);
} else if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
or $grade->feedback !== $oldgrade->feedback
or $grade->feedbackformat != $oldgrade->feedbackformat
or ($oldgrade->overridden == 0 and $grade->overridden > 0)) {
$grade->timemodified = time(); // hack alert - date graded
$result = $grade->update($source);
} else {
// no grade change
return $result;
}
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) !== true) {
$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) !== true) {
$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
* @param int $usermodified - user which did the grading
* @param int $dategraded
* @param int $datesubmitted
* @param object $grade object - usefull for bulk upgrades
* @return boolean success
*/
function update_raw_grade($userid, $rawgrade=false, $source=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null, $dategraded=null, $datesubmitted=null, $grade=null) {
global $USER;
$result = true;
// calculated grades can not be updated; course and category can not be updated because they are aggregated
if (!$this->is_raw_used() or $this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
return false;
}
if (is_null($grade)) {
//fetch from db
$grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid));
}
$grade->grade_item =& $this; // prevent db fetching of this grade_item
if (empty($usermodified)) {
$grade->usermodified = $USER->id;
} else {
$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;
// use new min and max
$grade->rawgrade = $grade->rawgrade;
$grade->rawgrademin = $this->grademin;
$grade->rawgrademax = $this->grademax;
$grade->rawscaleid = $this->scaleid;
// change raw grade?
if ($rawgrade !== false) {
$grade->rawgrade = $rawgrade;
}
// empty feedback means no feedback at all
if ($feedback === '') {
$feedback = null;
}
// do we have comment from teacher?
if ($feedback !== false and !$grade->is_overridden()) {
$grade->feedback = $feedback;
$grade->feedbackformat = $feedbackformat;
}
// update final grade if possible
if (!$grade->is_locked() and !$grade->is_overridden()) {
$grade->finalgrade = $this->adjust_raw_grade($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
}
// TODO: hack alert - create new fields for these in 2.0
$oldgrade->timecreated = $grade->timecreated;
$oldgrade->timemodified = $grade->timemodified;
$grade->timecreated = $datesubmitted;
if ($grade->is_overridden()) {
// keep original graded date - update_final_grade() sets this for overridden grades
} else if (is_null($grade->rawgrade) and is_null($grade->feedback)) {
// no grade and feedback means no grading yet
$grade->timemodified = null;
} else if (!empty($dategraded)) {
// fine - module sends info when graded (yay!)
$grade->timemodified = $dategraded;
} else if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
or $grade->feedback !== $oldgrade->feedback) {
// guess - if either grade or feedback changed set new graded date
$grade->timemodified = time();
} else {
//keep original graded date
}
// end of hack alert
if (empty($grade->id)) {
$result = (boolean)$grade->insert($source);
} else if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
or grade_floats_different($grade->rawgrade, $oldgrade->rawgrade)
or grade_floats_different($grade->rawgrademin, $oldgrade->rawgrademin)
or grade_floats_different($grade->rawgrademax, $oldgrade->rawgrademax)
or $grade->rawscaleid != $oldgrade->rawscaleid
or $grade->feedback !== $oldgrade->feedback
or $grade->feedbackformat != $oldgrade->feedbackformat
or $grade->timecreated != $oldgrade->timecreated // part of hack above
or $grade->timemodified != $oldgrade->timemodified // part of hack above
) {
$result = $grade->update($source);
} else {
return $result;
}
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) !== true) {
$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
}
// precreate grades - we need them to exist
$sql = "SELECT DISTINCT go.userid
FROM {$CFG->prefix}grade_grades go
JOIN {$CFG->prefix}grade_items gi
ON (gi.id = go.itemid AND gi.courseid={$this->courseid})
LEFT OUTER JOIN {$CFG->prefix}grade_grades g
ON (g.userid = go.userid AND g.itemid = $this->id)
WHERE gi.id <> $this->id AND g.id IS NULL";
if ($missing = get_records_sql($sql)) {
foreach ($missing as $m) {
$grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$m->userid), false);
$grade->grade_item =& $this;
$grade->insert('system');
}
}
// get used items
$useditems = $this->depends_on();
// prepare formula and init maths library
$formula = preg_replace('/##(gi\d+)##/', '\1', $this->calculation);
if (strpos($formula, '[[') !== false) {
// missing item
return false;
}
$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) {
$oldfinalgrade = $oldgrade->finalgrade;
$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->grade_item =& $this;
$grade->insert('system');
$oldfinalgrade = null;
}
// 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();
if ($result === false) {
$grade->finalgrade = null;
} else {
// normalize
$grade->finalgrade = $this->bounded_grade($result);
}
// update in db if changed
if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
$grade->timemodified = time();
$grade->update('compute');
}
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;
}
}
/**
* Returns a string representing the range of grademin - grademax for this grade item.
* @param int $rangesdisplaytype
* @param int $rangesdecimalpoints
* @return string
*/
function get_formatted_range($rangesdisplaytype=null, $rangesdecimalpoints=null) {
global $USER;
// Determine which display type to use for this average
if (isset($USER->gradeediting) && $USER->gradeediting[$this->courseid]) {
$displaytype = GRADE_DISPLAY_TYPE_REAL;
} else if ($rangesdisplaytype == GRADE_REPORT_PREFERENCE_INHERIT) { // no ==0 here, please resave report and user prefs
$displaytype = $this->get_displaytype();
} else {
$displaytype = $rangesdisplaytype;
}
// Override grade_item setting if a display preference (not default) was set for the averages
if ($rangesdecimalpoints == GRADE_REPORT_PREFERENCE_INHERIT) {
$decimalpoints = $this->get_decimals();
} else {
$decimalpoints = $rangesdecimalpoints;
}
if ($displaytype == GRADE_DISPLAY_TYPE_PERCENTAGE) {
$grademin = "0 %";
$grademax = "100 %";
} else {
$grademin = grade_format_gradevalue($this->grademin, $this, true, $displaytype, $decimalpoints);
$grademax = grade_format_gradevalue($this->grademax, $this, true, $displaytype, $decimalpoints);
}
return $grademin.'&ndash;'. $grademax;
}
/**
* Queries parent categories recursively to find the aggregationcoef type that applies to this
* grade item.
*/
function get_coefstring() {
$parent_category = $this->load_parent_category();
if ($this->is_category_item()) {
$parent_category = $parent_category->load_parent_category();
}
if ($parent_category->is_aggregationcoef_used()) {
return $parent_category->get_coefstring();
} else {
return false;
}
}
}
?>
Jump to Line
Something went wrong with that request. Please try again.