[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/grade/ -> grade_item.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Definition of a class to represent a grade item
  19   *
  20   * @package   core_grades
  21   * @category  grade
  22   * @copyright 2006 Nicolas Connault
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  require_once ('grade_object.php');
  28  
  29  /**
  30   * Class representing a grade item.
  31   *
  32   * It is responsible for handling its DB representation, modifying and returning its metadata.
  33   *
  34   * @package   core_grades
  35   * @category  grade
  36   * @copyright 2006 Nicolas Connault
  37   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class grade_item extends grade_object {
  40      /**
  41       * DB Table (used by grade_object).
  42       * @var string $table
  43       */
  44      public $table = 'grade_items';
  45  
  46      /**
  47       * Array of required table fields, must start with 'id'.
  48       * @var array $required_fields
  49       */
  50      public $required_fields = array('id', 'courseid', 'categoryid', 'itemname', 'itemtype', 'itemmodule', 'iteminstance',
  51                                   'itemnumber', 'iteminfo', 'idnumber', 'calculation', 'gradetype', 'grademax', 'grademin',
  52                                   'scaleid', 'outcomeid', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef',
  53                                   'aggregationcoef2', 'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
  54                                   'needsupdate', 'weightoverride', 'timecreated', 'timemodified');
  55  
  56      /**
  57       * The course this grade_item belongs to.
  58       * @var int $courseid
  59       */
  60      public $courseid;
  61  
  62      /**
  63       * The category this grade_item belongs to (optional).
  64       * @var int $categoryid
  65       */
  66      public $categoryid;
  67  
  68      /**
  69       * The grade_category object referenced $this->iteminstance if itemtype == 'category' or == 'course'.
  70       * @var grade_category $item_category
  71       */
  72      public $item_category;
  73  
  74      /**
  75       * The grade_category object referenced by $this->categoryid.
  76       * @var grade_category $parent_category
  77       */
  78      public $parent_category;
  79  
  80  
  81      /**
  82       * The name of this grade_item (pushed by the module).
  83       * @var string $itemname
  84       */
  85      public $itemname;
  86  
  87      /**
  88       * e.g. 'category', 'course' and 'mod', 'blocks', 'import', etc...
  89       * @var string $itemtype
  90       */
  91      public $itemtype;
  92  
  93      /**
  94       * The module pushing this grade (e.g. 'forum', 'quiz', 'assignment' etc).
  95       * @var string $itemmodule
  96       */
  97      public $itemmodule;
  98  
  99      /**
 100       * ID of the item module
 101       * @var int $iteminstance
 102       */
 103      public $iteminstance;
 104  
 105      /**
 106       * Number of the item in a series of multiple grades pushed by an activity.
 107       * @var int $itemnumber
 108       */
 109      public $itemnumber;
 110  
 111      /**
 112       * Info and notes about this item.
 113       * @var string $iteminfo
 114       */
 115      public $iteminfo;
 116  
 117      /**
 118       * Arbitrary idnumber provided by the module responsible.
 119       * @var string $idnumber
 120       */
 121      public $idnumber;
 122  
 123      /**
 124       * Calculation string used for this item.
 125       * @var string $calculation
 126       */
 127      public $calculation;
 128  
 129      /**
 130       * Indicates if we already tried to normalize the grade calculation formula.
 131       * This flag helps to minimize db access when broken formulas used in calculation.
 132       * @var bool
 133       */
 134      public $calculation_normalized;
 135      /**
 136       * Math evaluation object
 137       * @var calc_formula A formula object
 138       */
 139      public $formula;
 140  
 141      /**
 142       * The type of grade (0 = none, 1 = value, 2 = scale, 3 = text)
 143       * @var int $gradetype
 144       */
 145      public $gradetype = GRADE_TYPE_VALUE;
 146  
 147      /**
 148       * Maximum allowable grade.
 149       * @var float $grademax
 150       */
 151      public $grademax = 100;
 152  
 153      /**
 154       * Minimum allowable grade.
 155       * @var float $grademin
 156       */
 157      public $grademin = 0;
 158  
 159      /**
 160       * id of the scale, if this grade is based on a scale.
 161       * @var int $scaleid
 162       */
 163      public $scaleid;
 164  
 165      /**
 166       * The grade_scale object referenced by $this->scaleid.
 167       * @var grade_scale $scale
 168       */
 169      public $scale;
 170  
 171      /**
 172       * The id of the optional grade_outcome associated with this grade_item.
 173       * @var int $outcomeid
 174       */
 175      public $outcomeid;
 176  
 177      /**
 178       * The grade_outcome this grade is associated with, if applicable.
 179       * @var grade_outcome $outcome
 180       */
 181      public $outcome;
 182  
 183      /**
 184       * grade required to pass. (grademin <= gradepass <= grademax)
 185       * @var float $gradepass
 186       */
 187      public $gradepass = 0;
 188  
 189      /**
 190       * Multiply all grades by this number.
 191       * @var float $multfactor
 192       */
 193      public $multfactor = 1.0;
 194  
 195      /**
 196       * Add this to all grades.
 197       * @var float $plusfactor
 198       */
 199      public $plusfactor = 0;
 200  
 201      /**
 202       * Aggregation coeficient used for weighted averages or extra credit
 203       * @var float $aggregationcoef
 204       */
 205      public $aggregationcoef = 0;
 206  
 207      /**
 208       * Aggregation coeficient used for weighted averages only
 209       * @var float $aggregationcoef2
 210       */
 211      public $aggregationcoef2 = 0;
 212  
 213      /**
 214       * Sorting order of the columns.
 215       * @var int $sortorder
 216       */
 217      public $sortorder = 0;
 218  
 219      /**
 220       * Display type of the grades (Real, Percentage, Letter, or default).
 221       * @var int $display
 222       */
 223      public $display = GRADE_DISPLAY_TYPE_DEFAULT;
 224  
 225      /**
 226       * The number of digits after the decimal point symbol. Applies only to REAL and PERCENTAGE grade display types.
 227       * @var int $decimals
 228       */
 229      public $decimals = null;
 230  
 231      /**
 232       * Grade item lock flag. Empty if not locked, locked if any value present, usually date when item was locked. Locking prevents updating.
 233       * @var int $locked
 234       */
 235      public $locked = 0;
 236  
 237      /**
 238       * Date after which the grade will be locked. Empty means no automatic locking.
 239       * @var int $locktime
 240       */
 241      public $locktime = 0;
 242  
 243      /**
 244       * If set, the whole column will be recalculated, then this flag will be switched off.
 245       * @var bool $needsupdate
 246       */
 247      public $needsupdate = 1;
 248  
 249      /**
 250       * If set, the grade item's weight has been overridden by a user and should not be automatically adjusted.
 251       */
 252      public $weightoverride = 0;
 253  
 254      /**
 255       * Cached dependson array
 256       * @var array An array of cached grade item dependencies.
 257       */
 258      public $dependson_cache = null;
 259  
 260      /**
 261       * In addition to update() as defined in grade_object, handle the grade_outcome and grade_scale objects.
 262       * Force regrading if necessary, rounds the float numbers using php function,
 263       * the reason is we need to compare the db value with computed number to skip regrading if possible.
 264       *
 265       * @param string $source from where was the object inserted (mod/forum, manual, etc.)
 266       * @return bool success
 267       */
 268      public function update($source=null) {
 269          // reset caches
 270          $this->dependson_cache = null;
 271  
 272          // Retrieve scale and infer grademax/min from it if needed
 273          $this->load_scale();
 274  
 275          // make sure there is not 0 in outcomeid
 276          if (empty($this->outcomeid)) {
 277              $this->outcomeid = null;
 278          }
 279  
 280          if ($this->qualifies_for_regrading()) {
 281              $this->force_regrading();
 282          }
 283  
 284          $this->timemodified = time();
 285  
 286          $this->grademin        = grade_floatval($this->grademin);
 287          $this->grademax        = grade_floatval($this->grademax);
 288          $this->multfactor      = grade_floatval($this->multfactor);
 289          $this->plusfactor      = grade_floatval($this->plusfactor);
 290          $this->aggregationcoef = grade_floatval($this->aggregationcoef);
 291          $this->aggregationcoef2 = grade_floatval($this->aggregationcoef2);
 292  
 293          return parent::update($source);
 294      }
 295  
 296      /**
 297       * Compares the values held by this object with those of the matching record in DB, and returns
 298       * whether or not these differences are sufficient to justify an update of all parent objects.
 299       * This assumes that this object has an id number and a matching record in DB. If not, it will return false.
 300       *
 301       * @return bool
 302       */
 303      public function qualifies_for_regrading() {
 304          if (empty($this->id)) {
 305              return false;
 306          }
 307  
 308          $db_item = new grade_item(array('id' => $this->id));
 309  
 310          $calculationdiff = $db_item->calculation != $this->calculation;
 311          $categorydiff    = $db_item->categoryid  != $this->categoryid;
 312          $gradetypediff   = $db_item->gradetype   != $this->gradetype;
 313          $scaleiddiff     = $db_item->scaleid     != $this->scaleid;
 314          $outcomeiddiff   = $db_item->outcomeid   != $this->outcomeid;
 315          $locktimediff    = $db_item->locktime    != $this->locktime;
 316          $grademindiff    = grade_floats_different($db_item->grademin,        $this->grademin);
 317          $grademaxdiff    = grade_floats_different($db_item->grademax,        $this->grademax);
 318          $multfactordiff  = grade_floats_different($db_item->multfactor,      $this->multfactor);
 319          $plusfactordiff  = grade_floats_different($db_item->plusfactor,      $this->plusfactor);
 320          $acoefdiff       = grade_floats_different($db_item->aggregationcoef, $this->aggregationcoef);
 321          $acoefdiff2      = grade_floats_different($db_item->aggregationcoef2, $this->aggregationcoef2);
 322          $weightoverride  = grade_floats_different($db_item->weightoverride, $this->weightoverride);
 323  
 324          $needsupdatediff = !$db_item->needsupdate &&  $this->needsupdate;    // force regrading only if setting the flag first time
 325          $lockeddiff      = !empty($db_item->locked) && empty($this->locked); // force regrading only when unlocking
 326  
 327          return ($calculationdiff || $categorydiff || $gradetypediff || $grademaxdiff || $grademindiff || $scaleiddiff
 328               || $outcomeiddiff || $multfactordiff || $plusfactordiff || $needsupdatediff
 329               || $lockeddiff || $acoefdiff || $acoefdiff2 || $weightoverride || $locktimediff);
 330      }
 331  
 332      /**
 333       * Finds and returns a grade_item instance based on params.
 334       *
 335       * @static
 336       * @param array $params associative arrays varname=>value
 337       * @return grade_item|bool Returns a grade_item instance or false if none found
 338       */
 339      public static function fetch($params) {
 340          return grade_object::fetch_helper('grade_items', 'grade_item', $params);
 341      }
 342  
 343      /**
 344       * Finds and returns all grade_item instances based on params.
 345       *
 346       * @static
 347       * @param array $params associative arrays varname=>value
 348       * @return array array of grade_item instances or false if none found.
 349       */
 350      public static function fetch_all($params) {
 351          return grade_object::fetch_all_helper('grade_items', 'grade_item', $params);
 352      }
 353  
 354      /**
 355       * Delete all grades and force_regrading of parent category.
 356       *
 357       * @param string $source from where was the object deleted (mod/forum, manual, etc.)
 358       * @return bool success
 359       */
 360      public function delete($source=null) {
 361          $this->delete_all_grades($source);
 362          return parent::delete($source);
 363      }
 364  
 365      /**
 366       * Delete all grades
 367       *
 368       * @param string $source from where was the object deleted (mod/forum, manual, etc.)
 369       * @return bool
 370       */
 371      public function delete_all_grades($source=null) {
 372          if (!$this->is_course_item()) {
 373              $this->force_regrading();
 374          }
 375  
 376          if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
 377              foreach ($grades as $grade) {
 378                  $grade->delete($source);
 379              }
 380          }
 381  
 382          return true;
 383      }
 384  
 385      /**
 386       * In addition to perform parent::insert(), calls force_regrading() method too.
 387       *
 388       * @param string $source From where was the object inserted (mod/forum, manual, etc.)
 389       * @return int PK ID if successful, false otherwise
 390       */
 391      public function insert($source=null) {
 392          global $CFG, $DB;
 393  
 394          if (empty($this->courseid)) {
 395              print_error('cannotinsertgrade');
 396          }
 397  
 398          // load scale if needed
 399          $this->load_scale();
 400  
 401          // add parent category if needed
 402          if (empty($this->categoryid) and !$this->is_course_item() and !$this->is_category_item()) {
 403              $course_category = grade_category::fetch_course_category($this->courseid);
 404              $this->categoryid = $course_category->id;
 405  
 406          }
 407  
 408          // always place the new items at the end, move them after insert if needed
 409          $last_sortorder = $DB->get_field_select('grade_items', 'MAX(sortorder)', "courseid = ?", array($this->courseid));
 410          if (!empty($last_sortorder)) {
 411              $this->sortorder = $last_sortorder + 1;
 412          } else {
 413              $this->sortorder = 1;
 414          }
 415  
 416          // add proper item numbers to manual items
 417          if ($this->itemtype == 'manual') {
 418              if (empty($this->itemnumber)) {
 419                  $this->itemnumber = 0;
 420              }
 421          }
 422  
 423          // make sure there is not 0 in outcomeid
 424          if (empty($this->outcomeid)) {
 425              $this->outcomeid = null;
 426          }
 427  
 428          $this->timecreated = $this->timemodified = time();
 429  
 430          if (parent::insert($source)) {
 431              // force regrading of items if needed
 432              $this->force_regrading();
 433              return $this->id;
 434  
 435          } else {
 436              debugging("Could not insert this grade_item in the database!");
 437              return false;
 438          }
 439      }
 440  
 441      /**
 442       * Set idnumber of grade item, updates also course_modules table
 443       *
 444       * @param string $idnumber (without magic quotes)
 445       * @return bool success
 446       */
 447      public function add_idnumber($idnumber) {
 448          global $DB;
 449          if (!empty($this->idnumber)) {
 450              return false;
 451          }
 452  
 453          if ($this->itemtype == 'mod' and !$this->is_outcome_item()) {
 454              if ($this->itemnumber == 0) {
 455                  // for activity modules, itemnumber 0 is synced with the course_modules
 456                  if (!$cm = get_coursemodule_from_instance($this->itemmodule, $this->iteminstance, $this->courseid)) {
 457                      return false;
 458                  }
 459                  if (!empty($cm->idnumber)) {
 460                      return false;
 461                  }
 462                  $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id));
 463                  $this->idnumber = $idnumber;
 464                  return $this->update();
 465              } else {
 466                  $this->idnumber = $idnumber;
 467                  return $this->update();
 468              }
 469  
 470          } else {
 471              $this->idnumber = $idnumber;
 472              return $this->update();
 473          }
 474      }
 475  
 476      /**
 477       * Returns the locked state of this grade_item (if the grade_item is locked OR no specific
 478       * $userid is given) or the locked state of a specific grade within this item if a specific
 479       * $userid is given and the grade_item is unlocked.
 480       *
 481       * @param int $userid The user's ID
 482       * @return bool Locked state
 483       */
 484      public function is_locked($userid=NULL) {
 485          if (!empty($this->locked)) {
 486              return true;
 487          }
 488  
 489          if (!empty($userid)) {
 490              if ($grade = grade_grade::fetch(array('itemid'=>$this->id, 'userid'=>$userid))) {
 491                  $grade->grade_item =& $this; // prevent db fetching of cached grade_item
 492                  return $grade->is_locked();
 493              }
 494          }
 495  
 496          return false;
 497      }
 498  
 499      /**
 500       * Locks or unlocks this grade_item and (optionally) all its associated final grades.
 501       *
 502       * @param int $lockedstate 0, 1 or a timestamp int(10) after which date the item will be locked.
 503       * @param bool $cascade Lock/unlock child objects too
 504       * @param bool $refresh Refresh grades when unlocking
 505       * @return bool True if grade_item all grades updated, false if at least one update fails
 506       */
 507      public function set_locked($lockedstate, $cascade=false, $refresh=true) {
 508          if ($lockedstate) {
 509          /// setting lock
 510              if ($this->needsupdate) {
 511                  return false; // can not lock grade without first having final grade
 512              }
 513  
 514              $this->locked = time();
 515              $this->update();
 516  
 517              if ($cascade) {
 518                  $grades = $this->get_final();
 519                  foreach($grades as $g) {
 520                      $grade = new grade_grade($g, false);
 521                      $grade->grade_item =& $this;
 522                      $grade->set_locked(1, null, false);
 523                  }
 524              }
 525  
 526              return true;
 527  
 528          } else {
 529          /// removing lock
 530              if (!empty($this->locked) and $this->locktime < time()) {
 531                  //we have to reset locktime or else it would lock up again
 532                  $this->locktime = 0;
 533              }
 534  
 535              $this->locked = 0;
 536              $this->update();
 537  
 538              if ($cascade) {
 539                  if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
 540                      foreach($grades as $grade) {
 541                          $grade->grade_item =& $this;
 542                          $grade->set_locked(0, null, false);
 543                      }
 544                  }
 545              }
 546  
 547              if ($refresh) {
 548                  //refresh when unlocking
 549                  $this->refresh_grades();
 550              }
 551  
 552              return true;
 553          }
 554      }
 555  
 556      /**
 557       * Lock the grade if needed. Make sure this is called only when final grades are valid
 558       */
 559      public function check_locktime() {
 560          if (!empty($this->locked)) {
 561              return; // already locked
 562          }
 563  
 564          if ($this->locktime and $this->locktime < time()) {
 565              $this->locked = time();
 566              $this->update('locktime');
 567          }
 568      }
 569  
 570      /**
 571       * Set the locktime for this grade item.
 572       *
 573       * @param int $locktime timestamp for lock to activate
 574       * @return void
 575       */
 576      public function set_locktime($locktime) {
 577          $this->locktime = $locktime;
 578          $this->update();
 579      }
 580  
 581      /**
 582       * Set the locktime for this grade item.
 583       *
 584       * @return int $locktime timestamp for lock to activate
 585       */
 586      public function get_locktime() {
 587          return $this->locktime;
 588      }
 589  
 590      /**
 591       * Set the hidden status of grade_item and all grades.
 592       *
 593       * 0 mean always visible, 1 means always hidden and a number > 1 is a timestamp to hide until
 594       *
 595       * @param int $hidden new hidden status
 596       * @param bool $cascade apply to child objects too
 597       */
 598      public function set_hidden($hidden, $cascade=false) {
 599          parent::set_hidden($hidden, $cascade);
 600  
 601          if ($cascade) {
 602              if ($grades = grade_grade::fetch_all(array('itemid'=>$this->id))) {
 603                  foreach($grades as $grade) {
 604                      $grade->grade_item =& $this;
 605                      $grade->set_hidden($hidden, $cascade);
 606                  }
 607              }
 608          }
 609  
 610          //if marking item visible make sure category is visible MDL-21367
 611          if( !$hidden ) {
 612              $category_array = grade_category::fetch_all(array('id'=>$this->categoryid));
 613              if ($category_array && array_key_exists($this->categoryid, $category_array)) {
 614                  $category = $category_array[$this->categoryid];
 615                  //call set_hidden on the category regardless of whether it is hidden as its parent might be hidden
 616                  //if($category->is_hidden()) {
 617                      $category->set_hidden($hidden, false);
 618                  //}
 619              }
 620          }
 621      }
 622  
 623      /**
 624       * Returns the number of grades that are hidden
 625       *
 626       * @param string $groupsql SQL to limit the query by group
 627       * @param array $params SQL params for $groupsql
 628       * @param string $groupwheresql Where conditions for $groupsql
 629       * @return int The number of hidden grades
 630       */
 631      public function has_hidden_grades($groupsql="", array $params=null, $groupwheresql="") {
 632          global $DB;
 633          $params = (array)$params;
 634          $params['itemid'] = $this->id;
 635  
 636          return $DB->get_field_sql("SELECT COUNT(*) FROM {grade_grades} g LEFT JOIN "
 637                              ."{user} u ON g.userid = u.id $groupsql WHERE itemid = :itemid AND hidden = 1 $groupwheresql", $params);
 638      }
 639  
 640      /**
 641       * Mark regrading as finished successfully.
 642       */
 643      public function regrading_finished() {
 644          global $DB;
 645          $this->needsupdate = 0;
 646          //do not use $this->update() because we do not want this logged in grade_item_history
 647          $DB->set_field('grade_items', 'needsupdate', 0, array('id' => $this->id));
 648      }
 649  
 650      /**
 651       * Performs the necessary calculations on the grades_final referenced by this grade_item.
 652       * Also resets the needsupdate flag once successfully performed.
 653       *
 654       * This function must be used ONLY from lib/gradeslib.php/grade_regrade_final_grades(),
 655       * because the regrading must be done in correct order!!
 656       *
 657       * @param int $userid Supply a user ID to limit the regrading to a single user
 658       * @return bool true if ok, error string otherwise
 659       */
 660      public function regrade_final_grades($userid=null) {
 661          global $CFG, $DB;
 662  
 663          // locked grade items already have correct final grades
 664          if ($this->is_locked()) {
 665              return true;
 666          }
 667  
 668          // calculation produces final value using formula from other final values
 669          if ($this->is_calculated()) {
 670              if ($this->compute($userid)) {
 671                  return true;
 672              } else {
 673                  return "Could not calculate grades for grade item"; // TODO: improve and localize
 674              }
 675  
 676          // noncalculated outcomes already have final values - raw grades not used
 677          } else if ($this->is_outcome_item()) {
 678              return true;
 679  
 680          // aggregate the category grade
 681          } else if ($this->is_category_item() or $this->is_course_item()) {
 682              // aggregate category grade item
 683              $category = $this->get_item_category();
 684              $category->grade_item =& $this;
 685              if ($category->generate_grades($userid)) {
 686                  return true;
 687              } else {
 688                  return "Could not aggregate final grades for category:".$this->id; // TODO: improve and localize
 689              }
 690  
 691          } else if ($this->is_manual_item()) {
 692              // manual items track only final grades, no raw grades
 693              return true;
 694  
 695          } else if (!$this->is_raw_used()) {
 696              // hmm - raw grades are not used- nothing to regrade
 697              return true;
 698          }
 699  
 700          // normal grade item - just new final grades
 701          $result = true;
 702          $grade_inst = new grade_grade();
 703          $fields = implode(',', $grade_inst->required_fields);
 704          if ($userid) {
 705              $params = array($this->id, $userid);
 706              $rs = $DB->get_recordset_select('grade_grades', "itemid=? AND userid=?", $params, '', $fields);
 707          } else {
 708              $rs = $DB->get_recordset('grade_grades', array('itemid' => $this->id), '', $fields);
 709          }
 710          if ($rs) {
 711              foreach ($rs as $grade_record) {
 712                  $grade = new grade_grade($grade_record, false);
 713  
 714                  if (!empty($grade_record->locked) or !empty($grade_record->overridden)) {
 715                      // this grade is locked - final grade must be ok
 716                      continue;
 717                  }
 718  
 719                  $grade->finalgrade = $this->adjust_raw_grade($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
 720  
 721                  if (grade_floats_different($grade_record->finalgrade, $grade->finalgrade)) {
 722                      $success = $grade->update('system');
 723  
 724                      // If successful trigger a user_graded event.
 725                      if ($success) {
 726                          $grade->load_grade_item();
 727                          \core\event\user_graded::create_from_grade($grade)->trigger();
 728                      } else {
 729                          $result = "Internal error updating final grade";
 730                      }
 731                  }
 732              }
 733              $rs->close();
 734          }
 735  
 736          return $result;
 737      }
 738  
 739      /**
 740       * Given a float grade value or integer grade scale, applies a number of adjustment based on
 741       * grade_item variables and returns the result.
 742       *
 743       * @param float $rawgrade The raw grade value
 744       * @param float $rawmin original rawmin
 745       * @param float $rawmax original rawmax
 746       * @return mixed
 747       */
 748      public function adjust_raw_grade($rawgrade, $rawmin, $rawmax) {
 749          if (is_null($rawgrade)) {
 750              return null;
 751          }
 752  
 753          if ($this->gradetype == GRADE_TYPE_VALUE) { // Dealing with numerical grade
 754  
 755              if ($this->grademax < $this->grademin) {
 756                  return null;
 757              }
 758  
 759              if ($this->grademax == $this->grademin) {
 760                  return $this->grademax; // no range
 761              }
 762  
 763              // Standardise score to the new grade range
 764              // NOTE: this is not compatible with current assignment grading
 765              $isassignmentmodule = ($this->itemmodule == 'assignment') || ($this->itemmodule == 'assign');
 766              if (!$isassignmentmodule && ($rawmin != $this->grademin or $rawmax != $this->grademax)) {
 767                  $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
 768              }
 769  
 770              // Apply other grade_item factors
 771              $rawgrade *= $this->multfactor;
 772              $rawgrade += $this->plusfactor;
 773  
 774              return $this->bounded_grade($rawgrade);
 775  
 776          } else if ($this->gradetype == GRADE_TYPE_SCALE) { // Dealing with a scale value
 777              if (empty($this->scale)) {
 778                  $this->load_scale();
 779              }
 780  
 781              if ($this->grademax < 0) {
 782                  return null; // scale not present - no grade
 783              }
 784  
 785              if ($this->grademax == 0) {
 786                  return $this->grademax; // only one option
 787              }
 788  
 789              // Convert scale if needed
 790              // NOTE: this is not compatible with current assignment grading
 791              if ($this->itemmodule != 'assignment' and ($rawmin != $this->grademin or $rawmax != $this->grademax)) {
 792                  $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
 793              }
 794  
 795              return $this->bounded_grade($rawgrade);
 796  
 797  
 798          } else if ($this->gradetype == GRADE_TYPE_TEXT or $this->gradetype == GRADE_TYPE_NONE) { // no value
 799              // somebody changed the grading type when grades already existed
 800              return null;
 801  
 802          } else {
 803              debugging("Unknown grade type");
 804              return null;
 805          }
 806      }
 807  
 808      /**
 809       * Sets this grade_item's needsupdate to true. Also marks the course item as needing update.
 810       *
 811       * @return void
 812       */
 813      public function force_regrading() {
 814          global $DB;
 815          $this->needsupdate = 1;
 816          //mark this item and course item only - categories and calculated items are always regraded
 817          $wheresql = "(itemtype='course' OR id=?) AND courseid=?";
 818          $params   = array($this->id, $this->courseid);
 819          $DB->set_field_select('grade_items', 'needsupdate', 1, $wheresql, $params);
 820      }
 821  
 822      /**
 823       * Instantiates a grade_scale object from the DB if this item's scaleid variable is set
 824       *
 825       * @return grade_scale Returns a grade_scale object or null if no scale used
 826       */
 827      public function load_scale() {
 828          if ($this->gradetype != GRADE_TYPE_SCALE) {
 829              $this->scaleid = null;
 830          }
 831  
 832          if (!empty($this->scaleid)) {
 833              //do not load scale if already present
 834              if (empty($this->scale->id) or $this->scale->id != $this->scaleid) {
 835                  $this->scale = grade_scale::fetch(array('id'=>$this->scaleid));
 836                  if (!$this->scale) {
 837                      debugging('Incorrect scale id: '.$this->scaleid);
 838                      $this->scale = null;
 839                      return null;
 840                  }
 841                  $this->scale->load_items();
 842              }
 843  
 844              // Until scales are uniformly set to min=0 max=count(scaleitems)-1 throughout Moodle, we
 845              // stay with the current min=1 max=count(scaleitems)
 846              $this->grademax = count($this->scale->scale_items);
 847              $this->grademin = 1;
 848  
 849          } else {
 850              $this->scale = null;
 851          }
 852  
 853          return $this->scale;
 854      }
 855  
 856      /**
 857       * Instantiates a grade_outcome object from the DB if this item's outcomeid variable is set
 858       *
 859       * @return grade_outcome This grade item's associated grade_outcome or null
 860       */
 861      public function load_outcome() {
 862          if (!empty($this->outcomeid)) {
 863              $this->outcome = grade_outcome::fetch(array('id'=>$this->outcomeid));
 864          }
 865          return $this->outcome;
 866      }
 867  
 868      /**
 869       * Returns the grade_category object this grade_item belongs to (referenced by categoryid)
 870       * or category attached to category item.
 871       *
 872       * @return grade_category|bool Returns a grade_category object if applicable or false if this is a course item
 873       */
 874      public function get_parent_category() {
 875          if ($this->is_category_item() or $this->is_course_item()) {
 876              return $this->get_item_category();
 877  
 878          } else {
 879              return grade_category::fetch(array('id'=>$this->categoryid));
 880          }
 881      }
 882  
 883      /**
 884       * Calls upon the get_parent_category method to retrieve the grade_category object
 885       * from the DB and assigns it to $this->parent_category. It also returns the object.
 886       *
 887       * @return grade_category This grade item's parent grade_category.
 888       */
 889      public function load_parent_category() {
 890          if (empty($this->parent_category->id)) {
 891              $this->parent_category = $this->get_parent_category();
 892          }
 893          return $this->parent_category;
 894      }
 895  
 896      /**
 897       * Returns the grade_category for a grade category grade item
 898       *
 899       * @return grade_category|bool Returns a grade_category instance if applicable or false otherwise
 900       */
 901      public function get_item_category() {
 902          if (!$this->is_course_item() and !$this->is_category_item()) {
 903              return false;
 904          }
 905          return grade_category::fetch(array('id'=>$this->iteminstance));
 906      }
 907  
 908      /**
 909       * Calls upon the get_item_category method to retrieve the grade_category object
 910       * from the DB and assigns it to $this->item_category. It also returns the object.
 911       *
 912       * @return grade_category
 913       */
 914      public function load_item_category() {
 915          if (empty($this->item_category->id)) {
 916              $this->item_category = $this->get_item_category();
 917          }
 918          return $this->item_category;
 919      }
 920  
 921      /**
 922       * Is the grade item associated with category?
 923       *
 924       * @return bool
 925       */
 926      public function is_category_item() {
 927          return ($this->itemtype == 'category');
 928      }
 929  
 930      /**
 931       * Is the grade item associated with course?
 932       *
 933       * @return bool
 934       */
 935      public function is_course_item() {
 936          return ($this->itemtype == 'course');
 937      }
 938  
 939      /**
 940       * Is this a manually graded item?
 941       *
 942       * @return bool
 943       */
 944      public function is_manual_item() {
 945          return ($this->itemtype == 'manual');
 946      }
 947  
 948      /**
 949       * Is this an outcome item?
 950       *
 951       * @return bool
 952       */
 953      public function is_outcome_item() {
 954          return !empty($this->outcomeid);
 955      }
 956  
 957      /**
 958       * Is the grade item external - associated with module, plugin or something else?
 959       *
 960       * @return bool
 961       */
 962      public function is_external_item() {
 963          return ($this->itemtype == 'mod');
 964      }
 965  
 966      /**
 967       * Is the grade item overridable
 968       *
 969       * @return bool
 970       */
 971      public function is_overridable_item() {
 972          if ($this->is_course_item() or $this->is_category_item()) {
 973              $overridable = (bool) get_config('moodle', 'grade_overridecat');
 974          } else {
 975              $overridable = false;
 976          }
 977  
 978          return !$this->is_outcome_item() and ($this->is_external_item() or $this->is_calculated() or $overridable);
 979      }
 980  
 981      /**
 982       * Is the grade item feedback overridable
 983       *
 984       * @return bool
 985       */
 986      public function is_overridable_item_feedback() {
 987          return !$this->is_outcome_item() and $this->is_external_item();
 988      }
 989  
 990      /**
 991       * Returns true if grade items uses raw grades
 992       *
 993       * @return bool
 994       */
 995      public function is_raw_used() {
 996          return ($this->is_external_item() and !$this->is_calculated() and !$this->is_outcome_item());
 997      }
 998  
 999      /**
1000       * Returns the grade item associated with the course
1001       *
1002       * @param int $courseid
1003       * @return grade_item Course level grade item object
1004       */
1005      public static function fetch_course_item($courseid) {
1006          if ($course_item = grade_item::fetch(array('courseid'=>$courseid, 'itemtype'=>'course'))) {
1007              return $course_item;
1008          }
1009  
1010          // first get category - it creates the associated grade item
1011          $course_category = grade_category::fetch_course_category($courseid);
1012          return $course_category->get_grade_item();
1013      }
1014  
1015      /**
1016       * Is grading object editable?
1017       *
1018       * @return bool
1019       */
1020      public function is_editable() {
1021          return true;
1022      }
1023  
1024      /**
1025       * Checks if grade calculated. Returns this object's calculation.
1026       *
1027       * @return bool true if grade item calculated.
1028       */
1029      public function is_calculated() {
1030          if (empty($this->calculation)) {
1031              return false;
1032          }
1033  
1034          /*
1035           * The main reason why we use the ##gixxx## instead of [[idnumber]] is speed of depends_on(),
1036           * we would have to fetch all course grade items to find out the ids.
1037           * Also if user changes the idnumber the formula does not need to be updated.
1038           */
1039  
1040          // first detect if we need to change calculation formula from [[idnumber]] to ##giXXX## (after backup, etc.)
1041          if (!$this->calculation_normalized and strpos($this->calculation, '[[') !== false) {
1042              $this->set_calculation($this->calculation);
1043          }
1044  
1045          return !empty($this->calculation);
1046      }
1047  
1048      /**
1049       * Returns calculation string if grade calculated.
1050       *
1051       * @return string Returns the grade item's calculation if calculation is used, null if not
1052       */
1053      public function get_calculation() {
1054          if ($this->is_calculated()) {
1055              return grade_item::denormalize_formula($this->calculation, $this->courseid);
1056  
1057          } else {
1058              return NULL;
1059          }
1060      }
1061  
1062      /**
1063       * Sets this item's calculation (creates it) if not yet set, or
1064       * updates it if already set (in the DB). If no calculation is given,
1065       * the calculation is removed.
1066       *
1067       * @param string $formula string representation of formula used for calculation
1068       * @return bool success
1069       */
1070      public function set_calculation($formula) {
1071          $this->calculation = grade_item::normalize_formula($formula, $this->courseid);
1072          $this->calculation_normalized = true;
1073          return $this->update();
1074      }
1075  
1076      /**
1077       * Denormalizes the calculation formula to [idnumber] form
1078       *
1079       * @param string $formula A string representation of the formula
1080       * @param int $courseid The course ID
1081       * @return string The denormalized formula as a string
1082       */
1083      public static function denormalize_formula($formula, $courseid) {
1084          if (empty($formula)) {
1085              return '';
1086          }
1087  
1088          // denormalize formula - convert ##giXX## to [[idnumber]]
1089          if (preg_match_all('/##gi(\d+)##/', $formula, $matches)) {
1090              foreach ($matches[1] as $id) {
1091                  if ($grade_item = grade_item::fetch(array('id'=>$id, 'courseid'=>$courseid))) {
1092                      if (!empty($grade_item->idnumber)) {
1093                          $formula = str_replace('##gi'.$grade_item->id.'##', '[['.$grade_item->idnumber.']]', $formula);
1094                      }
1095                  }
1096              }
1097          }
1098  
1099          return $formula;
1100  
1101      }
1102  
1103      /**
1104       * Normalizes the calculation formula to [#giXX#] form
1105       *
1106       * @param string $formula The formula
1107       * @param int $courseid The course ID
1108       * @return string The normalized formula as a string
1109       */
1110      public static function normalize_formula($formula, $courseid) {
1111          $formula = trim($formula);
1112  
1113          if (empty($formula)) {
1114              return NULL;
1115  
1116          }
1117  
1118          // normalize formula - we want grade item ids ##giXXX## instead of [[idnumber]]
1119          if ($grade_items = grade_item::fetch_all(array('courseid'=>$courseid))) {
1120              foreach ($grade_items as $grade_item) {
1121                  $formula = str_replace('[['.$grade_item->idnumber.']]', '##gi'.$grade_item->id.'##', $formula);
1122              }
1123          }
1124  
1125          return $formula;
1126      }
1127  
1128      /**
1129       * Returns the final values for this grade item (as imported by module or other source).
1130       *
1131       * @param int $userid Optional: to retrieve a single user's final grade
1132       * @return array|grade_grade An array of all grade_grade instances for this grade_item, or a single grade_grade instance.
1133       */
1134      public function get_final($userid=NULL) {
1135          global $DB;
1136          if ($userid) {
1137              if ($user = $DB->get_record('grade_grades', array('itemid' => $this->id, 'userid' => $userid))) {
1138                  return $user;
1139              }
1140  
1141          } else {
1142              if ($grades = $DB->get_records('grade_grades', array('itemid' => $this->id))) {
1143                  //TODO: speed up with better SQL (MDL-31380)
1144                  $result = array();
1145                  foreach ($grades as $grade) {
1146                      $result[$grade->userid] = $grade;
1147                  }
1148                  return $result;
1149              } else {
1150                  return array();
1151              }
1152          }
1153      }
1154  
1155      /**
1156       * Get (or create if not exist yet) grade for this user
1157       *
1158       * @param int $userid The user ID
1159       * @param bool $create If true and the user has no grade for this grade item a new grade_grade instance will be inserted
1160       * @return grade_grade The grade_grade instance for the user for this grade item
1161       */
1162      public function get_grade($userid, $create=true) {
1163          if (empty($this->id)) {
1164              debugging('Can not use before insert');
1165              return false;
1166          }
1167  
1168          $grade = new grade_grade(array('userid'=>$userid, 'itemid'=>$this->id));
1169          if (empty($grade->id) and $create) {
1170              $grade->insert();
1171          }
1172  
1173          return $grade;
1174      }
1175  
1176      /**
1177       * Returns the sortorder of this grade_item. This method is also available in
1178       * grade_category, for cases where the object type is not know.
1179       *
1180       * @return int Sort order
1181       */
1182      public function get_sortorder() {
1183          return $this->sortorder;
1184      }
1185  
1186      /**
1187       * Returns the idnumber of this grade_item. This method is also available in
1188       * grade_category, for cases where the object type is not know.
1189       *
1190       * @return string The grade item idnumber
1191       */
1192      public function get_idnumber() {
1193          return $this->idnumber;
1194      }
1195  
1196      /**
1197       * Returns this grade_item. This method is also available in
1198       * grade_category, for cases where the object type is not know.
1199       *
1200       * @return grade_item
1201       */
1202      public function get_grade_item() {
1203          return $this;
1204      }
1205  
1206      /**
1207       * Sets the sortorder of this grade_item. This method is also available in
1208       * grade_category, for cases where the object type is not know.
1209       *
1210       * @param int $sortorder
1211       */
1212      public function set_sortorder($sortorder) {
1213          if ($this->sortorder == $sortorder) {
1214              return;
1215          }
1216          $this->sortorder = $sortorder;
1217          $this->update();
1218      }
1219  
1220      /**
1221       * Update this grade item's sortorder so that it will appear after $sortorder
1222       *
1223       * @param int $sortorder The sort order to place this grade item after
1224       */
1225      public function move_after_sortorder($sortorder) {
1226          global $CFG, $DB;
1227  
1228          //make some room first
1229          $params = array($sortorder, $this->courseid);
1230          $sql = "UPDATE {grade_items}
1231                     SET sortorder = sortorder + 1
1232                   WHERE sortorder > ? AND courseid = ?";
1233          $DB->execute($sql, $params);
1234  
1235          $this->set_sortorder($sortorder + 1);
1236      }
1237  
1238      /**
1239       * Detect duplicate grade item's sortorder and re-sort them.
1240       * Note: Duplicate sortorder will be introduced while duplicating activities or
1241       * merging two courses.
1242       *
1243       * @param int $courseid id of the course for which grade_items sortorder need to be fixed.
1244       */
1245      public static function fix_duplicate_sortorder($courseid) {
1246          global $DB;
1247  
1248          $transaction = $DB->start_delegated_transaction();
1249  
1250          $sql = "SELECT DISTINCT g1.id, g1.courseid, g1.sortorder
1251                      FROM {grade_items} g1
1252                      JOIN {grade_items} g2 ON g1.courseid = g2.courseid
1253                  WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id AND g1.courseid = :courseid
1254                  ORDER BY g1.sortorder DESC, g1.id DESC";
1255  
1256          // Get all duplicates in course highest sort order, and higest id first so that we can make space at the
1257          // bottom higher end of the sort orders and work down by id.
1258          $rs = $DB->get_recordset_sql($sql, array('courseid' => $courseid));
1259  
1260          foreach($rs as $duplicate) {
1261              $DB->execute("UPDATE {grade_items}
1262                              SET sortorder = sortorder + 1
1263                            WHERE courseid = :courseid AND
1264                            (sortorder > :sortorder OR (sortorder = :sortorder2 AND id > :id))",
1265                  array('courseid' => $duplicate->courseid,
1266                      'sortorder' => $duplicate->sortorder,
1267                      'sortorder2' => $duplicate->sortorder,
1268                      'id' => $duplicate->id));
1269          }
1270          $rs->close();
1271          $transaction->allow_commit();
1272      }
1273  
1274      /**
1275       * Returns the most descriptive field for this object.
1276       *
1277       * Determines what type of grade item it is then returns the appropriate string
1278       *
1279       * @param bool $fulltotal If the item is a category total, returns $categoryname."total" instead of "Category total" or "Course total"
1280       * @return string name
1281       */
1282      public function get_name($fulltotal=false) {
1283          if (!empty($this->itemname)) {
1284              // MDL-10557
1285              return format_string($this->itemname);
1286  
1287          } else if ($this->is_course_item()) {
1288              return get_string('coursetotal', 'grades');
1289  
1290          } else if ($this->is_category_item()) {
1291              if ($fulltotal) {
1292                  $category = $this->load_parent_category();
1293                  $a = new stdClass();
1294                  $a->category = $category->get_name();
1295                  return get_string('categorytotalfull', 'grades', $a);
1296              } else {
1297              return get_string('categorytotal', 'grades');
1298              }
1299  
1300          } else {
1301              return get_string('grade');
1302          }
1303      }
1304  
1305      /**
1306       * A grade item can return a more detailed description which will be added to the header of the column/row in some reports.
1307       *
1308       * @return string description
1309       */
1310      public function get_description() {
1311          if ($this->is_course_item() || $this->is_category_item()) {
1312              $categoryitem = $this->load_item_category();
1313              return $categoryitem->get_description();
1314          }
1315          return '';
1316      }
1317  
1318      /**
1319       * Sets this item's categoryid. A generic method shared by objects that have a parent id of some kind.
1320       *
1321       * @param int $parentid The ID of the new parent
1322       * @return bool True if success
1323       */
1324      public function set_parent($parentid) {
1325          if ($this->is_course_item() or $this->is_category_item()) {
1326              print_error('cannotsetparentforcatoritem');
1327          }
1328  
1329          if ($this->categoryid == $parentid) {
1330              return true;
1331          }
1332  
1333          // find parent and check course id
1334          if (!$parent_category = grade_category::fetch(array('id'=>$parentid, 'courseid'=>$this->courseid))) {
1335              return false;
1336          }
1337  
1338          // MDL-19407 If moving from a non-SWM category to a SWM category, convert aggregationcoef to 0
1339          $currentparent = $this->load_parent_category();
1340  
1341          if ($currentparent->aggregation != GRADE_AGGREGATE_WEIGHTED_MEAN2 && $parent_category->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) {
1342              $this->aggregationcoef = 0;
1343          }
1344  
1345          $this->force_regrading();
1346  
1347          // set new parent
1348          $this->categoryid = $parent_category->id;
1349          $this->parent_category =& $parent_category;
1350  
1351          return $this->update();
1352      }
1353  
1354      /**
1355       * Makes sure value is a valid grade value.
1356       *
1357       * @param float $gradevalue
1358       * @return mixed float or int fixed grade value
1359       */
1360      public function bounded_grade($gradevalue) {
1361          global $CFG;
1362  
1363          if (is_null($gradevalue)) {
1364              return null;
1365          }
1366  
1367          if ($this->gradetype == GRADE_TYPE_SCALE) {
1368              // no >100% grades hack for scale grades!
1369              // 1.5 is rounded to 2 ;-)
1370              return (int)bounded_number($this->grademin, round($gradevalue+0.00001), $this->grademax);
1371          }
1372  
1373          $grademax = $this->grademax;
1374  
1375          // NOTE: if you change this value you must manually reset the needsupdate flag in all grade items
1376          $maxcoef = isset($CFG->gradeoverhundredprocentmax) ? $CFG->gradeoverhundredprocentmax : 10; // 1000% max by default
1377  
1378          if (!empty($CFG->unlimitedgrades)) {
1379              // NOTE: if you change this value you must manually reset the needsupdate flag in all grade items
1380              $grademax = $grademax * $maxcoef;
1381          } else if ($this->is_category_item() or $this->is_course_item()) {
1382              $category = $this->load_item_category();
1383              if ($category->aggregation >= 100) {
1384                  // grade >100% hack
1385                  $grademax = $grademax * $maxcoef;
1386              }
1387          }
1388  
1389          return (float)bounded_number($this->grademin, $gradevalue, $grademax);
1390      }
1391  
1392      /**
1393       * Finds out on which other items does this depend directly when doing calculation or category aggregation
1394       *
1395       * @param bool $reset_cache
1396       * @return array of grade_item IDs this one depends on
1397       */
1398      public function depends_on($reset_cache=false) {
1399          global $CFG, $DB;
1400  
1401          if ($reset_cache) {
1402              $this->dependson_cache = null;
1403          } else if (isset($this->dependson_cache)) {
1404              return $this->dependson_cache;
1405          }
1406  
1407          if ($this->is_locked()) {
1408              // locked items do not need to be regraded
1409              $this->dependson_cache = array();
1410              return $this->dependson_cache;
1411          }
1412  
1413          if ($this->is_calculated()) {
1414              if (preg_match_all('/##gi(\d+)##/', $this->calculation, $matches)) {
1415                  $this->dependson_cache = array_unique($matches[1]); // remove duplicates
1416                  return $this->dependson_cache;
1417              } else {
1418                  $this->dependson_cache = array();
1419                  return $this->dependson_cache;
1420              }
1421  
1422          } else if ($grade_category = $this->load_item_category()) {
1423              $params = array();
1424  
1425              //only items with numeric or scale values can be aggregated
1426              if ($this->gradetype != GRADE_TYPE_VALUE and $this->gradetype != GRADE_TYPE_SCALE) {
1427                  $this->dependson_cache = array();
1428                  return $this->dependson_cache;
1429              }
1430  
1431              $grade_category->apply_forced_settings();
1432  
1433              if (empty($CFG->enableoutcomes) or $grade_category->aggregateoutcomes) {
1434                  $outcomes_sql = "";
1435              } else {
1436                  $outcomes_sql = "AND gi.outcomeid IS NULL";
1437              }
1438  
1439              if (empty($CFG->grade_includescalesinaggregation)) {
1440                  $gtypes = "gi.gradetype = ?";
1441                  $params[] = GRADE_TYPE_VALUE;
1442              } else {
1443                  $gtypes = "(gi.gradetype = ? OR gi.gradetype = ?)";
1444                  $params[] = GRADE_TYPE_VALUE;
1445                  $params[] = GRADE_TYPE_SCALE;
1446              }
1447  
1448              $params[] = $grade_category->id;
1449              $params[] = $this->courseid;
1450              $params[] = $grade_category->id;
1451              $params[] = $this->courseid;
1452              if (empty($CFG->grade_includescalesinaggregation)) {
1453                  $params[] = GRADE_TYPE_VALUE;
1454              } else {
1455                  $params[] = GRADE_TYPE_VALUE;
1456                  $params[] = GRADE_TYPE_SCALE;
1457              }
1458              $sql = "SELECT gi.id
1459                        FROM {grade_items} gi
1460                       WHERE $gtypes
1461                             AND gi.categoryid = ?
1462                             AND gi.courseid = ?
1463                             $outcomes_sql
1464                      UNION
1465  
1466                      SELECT gi.id
1467                        FROM {grade_items} gi, {grade_categories} gc
1468                       WHERE (gi.itemtype = 'category' OR gi.itemtype = 'course') AND gi.iteminstance=gc.id
1469                             AND gc.parent = ?
1470                             AND gi.courseid = ?
1471                             AND $gtypes
1472                             $outcomes_sql";
1473  
1474              if ($children = $DB->get_records_sql($sql, $params)) {
1475                  $this->dependson_cache = array_keys($children);
1476                  return $this->dependson_cache;
1477              } else {
1478                  $this->dependson_cache = array();
1479                  return $this->dependson_cache;
1480              }
1481  
1482          } else {
1483              $this->dependson_cache = array();
1484              return $this->dependson_cache;
1485          }
1486      }
1487  
1488      /**
1489       * Refetch grades from modules, plugins.
1490       *
1491       * @param int $userid optional, limit the refetch to a single user
1492       * @return bool Returns true on success or if there is nothing to do
1493       */
1494      public function refresh_grades($userid=0) {
1495          global $DB;
1496          if ($this->itemtype == 'mod') {
1497              if ($this->is_outcome_item()) {
1498                  //nothing to do
1499                  return true;
1500              }
1501  
1502              if (!$activity = $DB->get_record($this->itemmodule, array('id' => $this->iteminstance))) {
1503                  debugging("Can not find $this->itemmodule activity with id $this->iteminstance");
1504                  return false;
1505              }
1506  
1507              if (!$cm = get_coursemodule_from_instance($this->itemmodule, $activity->id, $this->courseid)) {
1508                  debugging('Can not find course module');
1509                  return false;
1510              }
1511  
1512              $activity->modname    = $this->itemmodule;
1513              $activity->cmidnumber = $cm->idnumber;
1514  
1515              return grade_update_mod_grades($activity, $userid);
1516          }
1517  
1518          return true;
1519      }
1520  
1521      /**
1522       * Updates final grade value for given user, this is a only way to update final
1523       * grades from gradebook and import because it logs the change in history table
1524       * and deals with overridden flag. This flag is set to prevent later overriding
1525       * from raw grades submitted from modules.
1526       *
1527       * @param int $userid The graded user
1528       * @param float|false $finalgrade The float value of final grade, false means do not change
1529       * @param string $source The modification source
1530       * @param string $feedback Optional teacher feedback
1531       * @param int $feedbackformat A format like FORMAT_PLAIN or FORMAT_HTML
1532       * @param int $usermodified The ID of the user making the modification
1533       * @return bool success
1534       */
1535      public function update_final_grade($userid, $finalgrade=false, $source=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
1536          global $USER, $CFG;
1537  
1538          $result = true;
1539  
1540          // no grading used or locked
1541          if ($this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
1542              return false;
1543          }
1544  
1545          $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid));
1546          $grade->grade_item =& $this; // prevent db fetching of this grade_item
1547  
1548          if (empty($usermodified)) {
1549              $grade->usermodified = $USER->id;
1550          } else {
1551              $grade->usermodified = $usermodified;
1552          }
1553  
1554          if ($grade->is_locked()) {
1555              // do not update locked grades at all
1556              return false;
1557          }
1558  
1559          $locktime = $grade->get_locktime();
1560          if ($locktime and $locktime < time()) {
1561              // do not update grades that should be already locked, force regrade instead
1562              $this->force_regrading();
1563              return false;
1564          }
1565  
1566          $oldgrade = new stdClass();
1567          $oldgrade->finalgrade     = $grade->finalgrade;
1568          $oldgrade->overridden     = $grade->overridden;
1569          $oldgrade->feedback       = $grade->feedback;
1570          $oldgrade->feedbackformat = $grade->feedbackformat;
1571  
1572          // MDL-31713 rawgramemin and max must be up to date so conditional access %'s works properly.
1573          $grade->rawgrademin = $this->grademin;
1574          $grade->rawgrademax = $this->grademax;
1575          $grade->rawscaleid  = $this->scaleid;
1576  
1577          // changed grade?
1578          if ($finalgrade !== false) {
1579              if ($this->is_overridable_item()) {
1580                  $grade->overridden = time();
1581              }
1582  
1583              $grade->finalgrade = $this->bounded_grade($finalgrade);
1584          }
1585  
1586          // do we have comment from teacher?
1587          if ($feedback !== false) {
1588              if ($this->is_overridable_item_feedback()) {
1589                  // external items (modules, plugins) may have own feedback
1590                  $grade->overridden = time();
1591              }
1592  
1593              $grade->feedback       = $feedback;
1594              $grade->feedbackformat = $feedbackformat;
1595          }
1596  
1597          if (empty($grade->id)) {
1598              $grade->timecreated  = null;   // hack alert - date submitted - no submission yet
1599              $grade->timemodified = time(); // hack alert - date graded
1600              $result = (bool)$grade->insert($source);
1601  
1602              // If the grade insert was successful and the final grade was not null then trigger a user_graded event.
1603              if ($result && !is_null($grade->finalgrade)) {
1604                  \core\event\user_graded::create_from_grade($grade)->trigger();
1605              }
1606          } else if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
1607                  or $grade->feedback       !== $oldgrade->feedback
1608                  or $grade->feedbackformat != $oldgrade->feedbackformat
1609                  or ($oldgrade->overridden == 0 and $grade->overridden > 0)) {
1610              $grade->timemodified = time(); // hack alert - date graded
1611              $result = $grade->update($source);
1612  
1613              // If the grade update was successful and the actual grade has changed then trigger a user_graded event.
1614              if ($result && grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)) {
1615                  \core\event\user_graded::create_from_grade($grade)->trigger();
1616              }
1617          } else {
1618              // no grade change
1619              return $result;
1620          }
1621  
1622          if (!$result) {
1623              // something went wrong - better force final grade recalculation
1624              $this->force_regrading();
1625  
1626          } else if ($this->is_course_item() and !$this->needsupdate) {
1627              if (grade_regrade_final_grades($this->courseid, $userid, $this) !== true) {
1628                  $this->force_regrading();
1629              }
1630  
1631          } else if (!$this->needsupdate) {
1632              $course_item = grade_item::fetch_course_item($this->courseid);
1633              if (!$course_item->needsupdate) {
1634                  if (grade_regrade_final_grades($this->courseid, $userid, $this) !== true) {
1635                      $this->force_regrading();
1636                  }
1637              } else {
1638                  $this->force_regrading();
1639              }
1640          }
1641  
1642          return $result;
1643      }
1644  
1645  
1646      /**
1647       * Updates raw grade value for given user, this is a only way to update raw
1648       * grades from external source (modules, etc.),
1649       * because it logs the change in history table and deals with final grade recalculation.
1650       *
1651       * @param int $userid the graded user
1652       * @param mixed $rawgrade float value of raw grade - false means do not change
1653       * @param string $source modification source
1654       * @param string $feedback optional teacher feedback
1655       * @param int $feedbackformat A format like FORMAT_PLAIN or FORMAT_HTML
1656       * @param int $usermodified the ID of the user who did the grading
1657       * @param int $dategraded A timestamp of when the student's work was graded
1658       * @param int $datesubmitted A timestamp of when the student's work was submitted
1659       * @param grade_grade $grade A grade object, useful for bulk upgrades
1660       * @return bool success
1661       */
1662      public function update_raw_grade($userid, $rawgrade=false, $source=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null, $dategraded=null, $datesubmitted=null, $grade=null) {
1663          global $USER;
1664  
1665          $result = true;
1666  
1667          // calculated grades can not be updated; course and category can not be updated  because they are aggregated
1668          if (!$this->is_raw_used() or $this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
1669              return false;
1670          }
1671  
1672          if (is_null($grade)) {
1673              //fetch from db
1674              $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid));
1675          }
1676          $grade->grade_item =& $this; // prevent db fetching of this grade_item
1677  
1678          if (empty($usermodified)) {
1679              $grade->usermodified = $USER->id;
1680          } else {
1681              $grade->usermodified = $usermodified;
1682          }
1683  
1684          if ($grade->is_locked()) {
1685              // do not update locked grades at all
1686              return false;
1687          }
1688  
1689          $locktime = $grade->get_locktime();
1690          if ($locktime and $locktime < time()) {
1691              // do not update grades that should be already locked and force regrade
1692              $this->force_regrading();
1693              return false;
1694          }
1695  
1696          $oldgrade = new stdClass();
1697          $oldgrade->finalgrade     = $grade->finalgrade;
1698          $oldgrade->rawgrade       = $grade->rawgrade;
1699          $oldgrade->rawgrademin    = $grade->rawgrademin;
1700          $oldgrade->rawgrademax    = $grade->rawgrademax;
1701          $oldgrade->rawscaleid     = $grade->rawscaleid;
1702          $oldgrade->feedback       = $grade->feedback;
1703          $oldgrade->feedbackformat = $grade->feedbackformat;
1704  
1705          // use new min and max
1706          $grade->rawgrade    = $grade->rawgrade;
1707          $grade->rawgrademin = $this->grademin;
1708          $grade->rawgrademax = $this->grademax;
1709          $grade->rawscaleid  = $this->scaleid;
1710  
1711          // change raw grade?
1712          if ($rawgrade !== false) {
1713              $grade->rawgrade = $rawgrade;
1714          }
1715  
1716          // empty feedback means no feedback at all
1717          if ($feedback === '') {
1718              $feedback = null;
1719          }
1720  
1721          // do we have comment from teacher?
1722          if ($feedback !== false and !$grade->is_overridden()) {
1723              $grade->feedback       = $feedback;
1724              $grade->feedbackformat = $feedbackformat;
1725          }
1726  
1727          // update final grade if possible
1728          if (!$grade->is_locked() and !$grade->is_overridden()) {
1729              $grade->finalgrade = $this->adjust_raw_grade($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
1730          }
1731  
1732          // TODO: hack alert - create new fields for these in 2.0
1733          $oldgrade->timecreated  = $grade->timecreated;
1734          $oldgrade->timemodified = $grade->timemodified;
1735  
1736          $grade->timecreated = $datesubmitted;
1737  
1738          if ($grade->is_overridden()) {
1739              // keep original graded date - update_final_grade() sets this for overridden grades
1740  
1741          } else if (is_null($grade->rawgrade) and is_null($grade->feedback)) {
1742              // no grade and feedback means no grading yet
1743              $grade->timemodified = null;
1744  
1745          } else if (!empty($dategraded)) {
1746              // fine - module sends info when graded (yay!)
1747              $grade->timemodified = $dategraded;
1748  
1749          } else if (grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)
1750                     or $grade->feedback !== $oldgrade->feedback) {
1751              // guess - if either grade or feedback changed set new graded date
1752              $grade->timemodified = time();
1753  
1754          } else {
1755              //keep original graded date
1756          }
1757          // end of hack alert
1758  
1759          if (empty($grade->id)) {
1760              $result = (bool)$grade->insert($source);
1761  
1762              // If the grade insert was successful and the final grade was not null then trigger a user_graded event.
1763              if ($result && !is_null($grade->finalgrade)) {
1764                  \core\event\user_graded::create_from_grade($grade)->trigger();
1765              }
1766          } else if (grade_floats_different($grade->finalgrade,  $oldgrade->finalgrade)
1767                  or grade_floats_different($grade->rawgrade,    $oldgrade->rawgrade)
1768                  or grade_floats_different($grade->rawgrademin, $oldgrade->rawgrademin)
1769                  or grade_floats_different($grade->rawgrademax, $oldgrade->rawgrademax)
1770                  or $grade->rawscaleid     != $oldgrade->rawscaleid
1771                  or $grade->feedback       !== $oldgrade->feedback
1772                  or $grade->feedbackformat != $oldgrade->feedbackformat
1773                  or $grade->timecreated    != $oldgrade->timecreated  // part of hack above
1774                  or $grade->timemodified   != $oldgrade->timemodified // part of hack above
1775                  ) {
1776              $result = $grade->update($source);
1777  
1778              // If the grade update was successful and the actual grade has changed then trigger a user_graded event.
1779              if ($result && grade_floats_different($grade->finalgrade, $oldgrade->finalgrade)) {
1780                  \core\event\user_graded::create_from_grade($grade)->trigger();
1781              }
1782          } else {
1783              return $result;
1784          }
1785  
1786          if (!$result) {
1787              // something went wrong - better force final grade recalculation
1788              $this->force_regrading();
1789  
1790          } else if (!$this->needsupdate) {
1791              $course_item = grade_item::fetch_course_item($this->courseid);
1792              if (!$course_item->needsupdate) {
1793                  if (grade_regrade_final_grades($this->courseid, $userid, $this) !== true) {
1794                      $this->force_regrading();
1795                  }
1796              }
1797          }
1798  
1799          return $result;
1800      }
1801  
1802      /**
1803       * Calculates final grade values using the formula in the calculation property.
1804       * The parameters are taken from final grades of grade items in current course only.
1805       *
1806       * @param int $userid Supply a user ID to limit the calculations to the grades of a single user
1807       * @return bool false if error
1808       */
1809      public function compute($userid=null) {
1810          global $CFG, $DB;
1811  
1812          if (!$this->is_calculated()) {
1813              return false;
1814          }
1815  
1816          require_once($CFG->libdir.'/mathslib.php');
1817  
1818          if ($this->is_locked()) {
1819              return true; // no need to recalculate locked items
1820          }
1821  
1822          // Precreate grades - we need them to exist
1823          if ($userid) {
1824              $missing = array();
1825              if (!$DB->record_exists('grade_grades', array('itemid'=>$this->id, 'userid'=>$userid))) {
1826                  $m = new stdClass();
1827                  $m->userid = $userid;
1828                  $missing[] = $m;
1829              }
1830          } else {
1831              // Find any users who have grades for some but not all grade items in this course
1832              $params = array('gicourseid' => $this->courseid, 'ggitemid' => $this->id);
1833              $sql = "SELECT gg.userid
1834                        FROM {grade_grades} gg
1835                             JOIN {grade_items} gi
1836                             ON (gi.id = gg.itemid AND gi.courseid = :gicourseid)
1837                       GROUP BY gg.userid
1838                       HAVING SUM(CASE WHEN gg.itemid = :ggitemid THEN 1 ELSE 0 END) = 0";
1839              $missing = $DB->get_records_sql($sql, $params);
1840          }
1841  
1842          if ($missing) {
1843              foreach ($missing as $m) {
1844                  $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$m->userid), false);
1845                  $grade->grade_item =& $this;
1846                  $grade->insert('system');
1847              }
1848          }
1849  
1850          // get used items
1851          $useditems = $this->depends_on();
1852  
1853          // prepare formula and init maths library
1854          $formula = preg_replace('/##(gi\d+)##/', '\1', $this->calculation);
1855          if (strpos($formula, '[[') !== false) {
1856              // missing item
1857              return false;
1858          }
1859          $this->formula = new calc_formula($formula);
1860  
1861          // where to look for final grades?
1862          // this itemid is added so that we use only one query for source and final grades
1863          $gis = array_merge($useditems, array($this->id));
1864          list($usql, $params) = $DB->get_in_or_equal($gis);
1865  
1866          if ($userid) {
1867              $usersql = "AND g.userid=?";
1868              $params[] = $userid;
1869          } else {
1870              $usersql = "";
1871          }
1872  
1873          $grade_inst = new grade_grade();
1874          $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
1875  
1876          $params[] = $this->courseid;
1877          $sql = "SELECT $fields
1878                    FROM {grade_grades} g, {grade_items} gi
1879                   WHERE gi.id = g.itemid AND gi.id $usql $usersql AND gi.courseid=?
1880                   ORDER BY g.userid";
1881  
1882          $return = true;
1883  
1884          // group the grades by userid and use formula on the group
1885          $rs = $DB->get_recordset_sql($sql, $params);
1886          if ($rs->valid()) {
1887              $prevuser = 0;
1888              $grade_records   = array();
1889              $oldgrade    = null;
1890              foreach ($rs as $used) {
1891                  if ($used->userid != $prevuser) {
1892                      if (!$this->use_formula($prevuser, $grade_records, $useditems, $oldgrade)) {
1893                          $return = false;
1894                      }
1895                      $prevuser = $used->userid;
1896                      $grade_records   = array();
1897                      $oldgrade    = null;
1898                  }
1899                  if ($used->itemid == $this->id) {
1900                      $oldgrade = $used;
1901                  }
1902                  $grade_records['gi'.$used->itemid] = $used->finalgrade;
1903              }
1904              if (!$this->use_formula($prevuser, $grade_records, $useditems, $oldgrade)) {
1905                  $return = false;
1906              }
1907          }
1908          $rs->close();
1909  
1910          return $return;
1911      }
1912  
1913      /**
1914       * Internal function that does the final grade calculation
1915       *
1916       * @param int $userid The user ID
1917       * @param array $params An array of grade items of the form {'gi'.$itemid]} => $finalgrade
1918       * @param array $useditems An array of grade item IDs that this grade item depends on plus its own ID
1919       * @param grade_grade $oldgrade A grade_grade instance containing the old values from the database
1920       * @return bool False if an error occurred
1921       */
1922      public function use_formula($userid, $params, $useditems, $oldgrade) {
1923          if (empty($userid)) {
1924              return true;
1925          }
1926  
1927          // add missing final grade values
1928          // not graded (null) is counted as 0 - the spreadsheet way
1929          $allinputsnull = true;
1930          foreach($useditems as $gi) {
1931              if (!array_key_exists('gi'.$gi, $params) || is_null($params['gi'.$gi])) {
1932                  $params['gi'.$gi] = 0;
1933              } else {
1934                  $params['gi'.$gi] = (float)$params['gi'.$gi];
1935                  if ($gi != $this->id) {
1936                      $allinputsnull = false;
1937                  }
1938              }
1939          }
1940  
1941          // can not use own final grade during calculation
1942          unset($params['gi'.$this->id]);
1943  
1944          // insert final grade - will be needed later anyway
1945          if ($oldgrade) {
1946              $oldfinalgrade = $oldgrade->finalgrade;
1947              $grade = new grade_grade($oldgrade, false); // fetching from db is not needed
1948              $grade->grade_item =& $this;
1949  
1950          } else {
1951              $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid), false);
1952              $grade->grade_item =& $this;
1953              $grade->insert('system');
1954              $oldfinalgrade = null;
1955          }
1956  
1957          // no need to recalculate locked or overridden grades
1958          if ($grade->is_locked() or $grade->is_overridden()) {
1959              return true;
1960          }
1961  
1962          if ($allinputsnull) {
1963              $grade->finalgrade = null;
1964              $result = true;
1965  
1966          } else {
1967  
1968              // do the calculation
1969              $this->formula->set_params($params);
1970              $result = $this->formula->evaluate();
1971  
1972              if ($result === false) {
1973                  $grade->finalgrade = null;
1974  
1975              } else {
1976                  // normalize
1977                  $grade->finalgrade = $this->bounded_grade($result);
1978              }
1979  
1980          }
1981  
1982          // update in db if changed
1983          if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
1984              $grade->timemodified = time();
1985              $success = $grade->update('compute');
1986  
1987              // If successful trigger a user_graded event.
1988              if ($success) {
1989                  \core\event\user_graded::create_from_grade($grade)->trigger();
1990              }
1991          }
1992  
1993          if ($result !== false) {
1994              //lock grade if needed
1995          }
1996  
1997          if ($result === false) {
1998              return false;
1999          } else {
2000              return true;
2001          }
2002  
2003      }
2004  
2005      /**
2006       * Validate the formula.
2007       *
2008       * @param string $formulastr
2009       * @return bool true if calculation possible, false otherwise
2010       */
2011      public function validate_formula($formulastr) {
2012          global $CFG, $DB;
2013          require_once($CFG->libdir.'/mathslib.php');
2014  
2015          $formulastr = grade_item::normalize_formula($formulastr, $this->courseid);
2016  
2017          if (empty($formulastr)) {
2018              return true;
2019          }
2020  
2021          if (strpos($formulastr, '=') !== 0) {
2022              return get_string('errorcalculationnoequal', 'grades');
2023          }
2024  
2025          // get used items
2026          if (preg_match_all('/##gi(\d+)##/', $formulastr, $matches)) {
2027              $useditems = array_unique($matches[1]); // remove duplicates
2028          } else {
2029              $useditems = array();
2030          }
2031  
2032          // MDL-11902
2033          // unset the value if formula is trying to reference to itself
2034          // but array keys does not match itemid
2035          if (!empty($this->id)) {
2036              $useditems = array_diff($useditems, array($this->id));
2037              //unset($useditems[$this->id]);
2038          }
2039  
2040          // prepare formula and init maths library
2041          $formula = preg_replace('/##(gi\d+)##/', '\1', $formulastr);
2042          $formula = new calc_formula($formula);
2043  
2044  
2045          if (empty($useditems)) {
2046              $grade_items = array();
2047  
2048          } else {
2049              list($usql, $params) = $DB->get_in_or_equal($useditems);
2050              $params[] = $this->courseid;
2051              $sql = "SELECT gi.*
2052                        FROM {grade_items} gi
2053                       WHERE gi.id $usql and gi.courseid=?"; // from the same course only!
2054  
2055              if (!$grade_items = $DB->get_records_sql($sql, $params)) {
2056                  $grade_items = array();
2057              }
2058          }
2059  
2060          $params = array();
2061          foreach ($useditems as $itemid) {
2062              // make sure all grade items exist in this course
2063              if (!array_key_exists($itemid, $grade_items)) {
2064                  return false;
2065              }
2066              // use max grade when testing formula, this should be ok in 99.9%
2067              // division by 0 is one of possible problems
2068              $params['gi'.$grade_items[$itemid]->id] = $grade_items[$itemid]->grademax;
2069          }
2070  
2071          // do the calculation
2072          $formula->set_params($params);
2073          $result = $formula->evaluate();
2074  
2075          // false as result indicates some problem
2076          if ($result === false) {
2077              // TODO: add more error hints
2078              return get_string('errorcalculationunknown', 'grades');
2079          } else {
2080              return true;
2081          }
2082      }
2083  
2084      /**
2085       * Returns the value of the display type
2086       *
2087       * It can be set at 3 levels: grade_item, course setting and site. The lowest level overrides the higher ones.
2088       *
2089       * @return int Display type
2090       */
2091      public function get_displaytype() {
2092          global $CFG;
2093  
2094          if ($this->display == GRADE_DISPLAY_TYPE_DEFAULT) {
2095              return grade_get_setting($this->courseid, 'displaytype', $CFG->grade_displaytype);
2096  
2097          } else {
2098              return $this->display;
2099          }
2100      }
2101  
2102      /**
2103       * Returns the value of the decimals field
2104       *
2105       * It can be set at 3 levels: grade_item, course setting and site. The lowest level overrides the higher ones.
2106       *
2107       * @return int Decimals (0 - 5)
2108       */
2109      public function get_decimals() {
2110          global $CFG;
2111  
2112          if (is_null($this->decimals)) {
2113              return grade_get_setting($this->courseid, 'decimalpoints', $CFG->grade_decimalpoints);
2114  
2115          } else {
2116              return $this->decimals;
2117          }
2118      }
2119  
2120      /**
2121       * Returns a string representing the range of grademin - grademax for this grade item.
2122       *
2123       * @param int $rangesdisplaytype
2124       * @param int $rangesdecimalpoints
2125       * @return string
2126       */
2127      function get_formatted_range($rangesdisplaytype=null, $rangesdecimalpoints=null) {
2128  
2129          global $USER;
2130  
2131          // Determine which display type to use for this average
2132          if (isset($USER->gradeediting) && array_key_exists($this->courseid, $USER->gradeediting) && $USER->gradeediting[$this->courseid]) {
2133              $displaytype = GRADE_DISPLAY_TYPE_REAL;
2134  
2135          } else if ($rangesdisplaytype == GRADE_REPORT_PREFERENCE_INHERIT) { // no ==0 here, please resave report and user prefs
2136              $displaytype = $this->get_displaytype();
2137  
2138          } else {
2139              $displaytype = $rangesdisplaytype;
2140          }
2141  
2142          // Override grade_item setting if a display preference (not default) was set for the averages
2143          if ($rangesdecimalpoints == GRADE_REPORT_PREFERENCE_INHERIT) {
2144              $decimalpoints = $this->get_decimals();
2145  
2146          } else {
2147              $decimalpoints = $rangesdecimalpoints;
2148          }
2149  
2150          if ($displaytype == GRADE_DISPLAY_TYPE_PERCENTAGE) {
2151              $grademin = "0 %";
2152              $grademax = "100 %";
2153  
2154          } else {
2155              $grademin = grade_format_gradevalue($this->grademin, $this, true, $displaytype, $decimalpoints);
2156              $grademax = grade_format_gradevalue($this->grademax, $this, true, $displaytype, $decimalpoints);
2157          }
2158  
2159          return $grademin.'&ndash;'. $grademax;
2160      }
2161  
2162      /**
2163       * Queries parent categories recursively to find the aggregationcoef type that applies to this grade item.
2164       *
2165       * @return string|false Returns the coefficient string of false is no coefficient is being used
2166       */
2167      public function get_coefstring() {
2168          $parent_category = $this->load_parent_category();
2169          if ($this->is_category_item()) {
2170              $parent_category = $parent_category->load_parent_category();
2171          }
2172  
2173          if ($parent_category->is_aggregationcoef_used()) {
2174              return $parent_category->get_coefstring();
2175          } else {
2176              return false;
2177          }
2178      }
2179  
2180      /**
2181       * Returns whether the grade item can control the visibility of the grades
2182       *
2183       * @return bool
2184       */
2185      public function can_control_visibility() {
2186          if (core_component::get_plugin_directory($this->itemtype, $this->itemmodule)) {
2187              return !plugin_supports($this->itemtype, $this->itemmodule, FEATURE_CONTROLS_GRADE_VISIBILITY, false);
2188          }
2189          return parent::can_control_visibility();
2190      }
2191  
2192      /**
2193       * Used to notify the completion system (if necessary) that a user's grade
2194       * has changed, and clear up a possible score cache.
2195       *
2196       * @param bool $deleted True if grade was actually deleted
2197       */
2198      protected function notify_changed($deleted) {
2199          global $CFG;
2200  
2201          // Condition code may cache the grades for conditional availability of
2202          // modules or sections. (This code should use a hook for communication
2203          // with plugin, but hooks are not implemented at time of writing.)
2204          if (!empty($CFG->enableavailability) && class_exists('\availability_grade\callbacks')) {
2205              \availability_grade\callbacks::grade_item_changed($this->courseid);
2206          }
2207      }
2208  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1