[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/grade/ -> grade_grade.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 an individual user's grade
  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  
  28  require_once ('grade_object.php');
  29  
  30  /**
  31   * grade_grades is an object mapped to DB table {prefix}grade_grades
  32   *
  33   * @package   core_grades
  34   * @category  grade
  35   * @copyright 2006 Nicolas Connault
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class grade_grade extends grade_object {
  39  
  40      /**
  41       * The DB table.
  42       * @var string $table
  43       */
  44      public $table = 'grade_grades';
  45  
  46      /**
  47       * Array of required table fields, must start with 'id'.
  48       * @var array $required_fields
  49       */
  50      public $required_fields = array('id', 'itemid', 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
  51                                   'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 'locked',
  52                                   'locktime', 'exported', 'overridden', 'excluded', 'timecreated',
  53                                   'timemodified', 'aggregationstatus', 'aggregationweight');
  54  
  55      /**
  56       * Array of optional fields with default values (these should match db defaults)
  57       * @var array $optional_fields
  58       */
  59      public $optional_fields = array('feedback'=>null, 'feedbackformat'=>0, 'information'=>null, 'informationformat'=>0);
  60  
  61      /**
  62       * The id of the grade_item this grade belongs to.
  63       * @var int $itemid
  64       */
  65      public $itemid;
  66  
  67      /**
  68       * The grade_item object referenced by $this->itemid.
  69       * @var grade_item $grade_item
  70       */
  71      public $grade_item;
  72  
  73      /**
  74       * The id of the user this grade belongs to.
  75       * @var int $userid
  76       */
  77      public $userid;
  78  
  79      /**
  80       * The grade value of this raw grade, if such was provided by the module.
  81       * @var float $rawgrade
  82       */
  83      public $rawgrade;
  84  
  85      /**
  86       * The maximum allowable grade when this grade was created.
  87       * @var float $rawgrademax
  88       */
  89      public $rawgrademax = 100;
  90  
  91      /**
  92       * The minimum allowable grade when this grade was created.
  93       * @var float $rawgrademin
  94       */
  95      public $rawgrademin = 0;
  96  
  97      /**
  98       * id of the scale, if this grade is based on a scale.
  99       * @var int $rawscaleid
 100       */
 101      public $rawscaleid;
 102  
 103      /**
 104       * The userid of the person who last modified this grade.
 105       * @var int $usermodified
 106       */
 107      public $usermodified;
 108  
 109      /**
 110       * The final value of this grade.
 111       * @var float $finalgrade
 112       */
 113      public $finalgrade;
 114  
 115      /**
 116       * 0 if visible, 1 always hidden or date not visible until
 117       * @var float $hidden
 118       */
 119      public $hidden = 0;
 120  
 121      /**
 122       * 0 not locked, date when the item was locked
 123       * @var float locked
 124       */
 125      public $locked = 0;
 126  
 127      /**
 128       * 0 no automatic locking, date when to lock the grade automatically
 129       * @var float $locktime
 130       */
 131      public $locktime = 0;
 132  
 133      /**
 134       * Exported flag
 135       * @var bool $exported
 136       */
 137      public $exported = 0;
 138  
 139      /**
 140       * Overridden flag
 141       * @var bool $overridden
 142       */
 143      public $overridden = 0;
 144  
 145      /**
 146       * Grade excluded from aggregation functions
 147       * @var bool $excluded
 148       */
 149      public $excluded = 0;
 150  
 151      /**
 152       * TODO: HACK: create a new field datesubmitted - the date of submission if any (MDL-31377)
 153       * @var bool $timecreated
 154       */
 155      public $timecreated = null;
 156  
 157      /**
 158       * TODO: HACK: create a new field dategraded - the date of grading (MDL-31378)
 159       * @var bool $timemodified
 160       */
 161      public $timemodified = null;
 162  
 163      /**
 164       * Aggregation status flag. Can be one of 'unknown', 'dropped', 'novalue' or 'used'.
 165       * @var string $aggregationstatus
 166       */
 167      public $aggregationstatus = 'unknown';
 168  
 169      /**
 170       * Aggregation weight is the specific weight used in the aggregation calculation for this grade.
 171       * @var float $aggregationweight
 172       */
 173      public $aggregationweight = null;
 174  
 175      /**
 176       * Returns array of grades for given grade_item+users
 177       *
 178       * @param grade_item $grade_item
 179       * @param array $userids
 180       * @param bool $include_missing include grades that do not exist yet
 181       * @return array userid=>grade_grade array
 182       */
 183      public static function fetch_users_grades($grade_item, $userids, $include_missing=true) {
 184          global $DB;
 185  
 186          // hmm, there might be a problem with length of sql query
 187          // if there are too many users requested - we might run out of memory anyway
 188          $limit = 2000;
 189          $count = count($userids);
 190          if ($count > $limit) {
 191              $half = (int)($count/2);
 192              $first  = array_slice($userids, 0, $half);
 193              $second = array_slice($userids, $half);
 194              return grade_grade::fetch_users_grades($grade_item, $first, $include_missing) + grade_grade::fetch_users_grades($grade_item, $second, $include_missing);
 195          }
 196  
 197          list($user_ids_cvs, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'uid0');
 198          $params['giid'] = $grade_item->id;
 199          $result = array();
 200          if ($grade_records = $DB->get_records_select('grade_grades', "itemid=:giid AND userid $user_ids_cvs", $params)) {
 201              foreach ($grade_records as $record) {
 202                  $result[$record->userid] = new grade_grade($record, false);
 203              }
 204          }
 205          if ($include_missing) {
 206              foreach ($userids as $userid) {
 207                  if (!array_key_exists($userid, $result)) {
 208                      $grade_grade = new grade_grade();
 209                      $grade_grade->userid = $userid;
 210                      $grade_grade->itemid = $grade_item->id;
 211                      $result[$userid] = $grade_grade;
 212                  }
 213              }
 214          }
 215  
 216          return $result;
 217      }
 218  
 219      /**
 220       * Loads the grade_item object referenced by $this->itemid and saves it as $this->grade_item for easy access
 221       *
 222       * @return grade_item The grade_item instance referenced by $this->itemid
 223       */
 224      public function load_grade_item() {
 225          if (empty($this->itemid)) {
 226              debugging('Missing itemid');
 227              $this->grade_item = null;
 228              return null;
 229          }
 230  
 231          if (empty($this->grade_item)) {
 232              $this->grade_item = grade_item::fetch(array('id'=>$this->itemid));
 233  
 234          } else if ($this->grade_item->id != $this->itemid) {
 235              debugging('Itemid mismatch');
 236              $this->grade_item = grade_item::fetch(array('id'=>$this->itemid));
 237          }
 238  
 239          return $this->grade_item;
 240      }
 241  
 242      /**
 243       * Is grading object editable?
 244       *
 245       * @return bool
 246       */
 247      public function is_editable() {
 248          if ($this->is_locked()) {
 249              return false;
 250          }
 251  
 252          $grade_item = $this->load_grade_item();
 253  
 254          if ($grade_item->gradetype == GRADE_TYPE_NONE) {
 255              return false;
 256          }
 257  
 258          if ($grade_item->is_course_item() or $grade_item->is_category_item()) {
 259              return (bool)get_config('moodle', 'grade_overridecat');
 260          }
 261  
 262          return true;
 263      }
 264  
 265      /**
 266       * Check grade lock status. Uses both grade item lock and grade lock.
 267       * Internally any date in locked field (including future ones) means locked,
 268       * the date is stored for logging purposes only.
 269       *
 270       * @return bool True if locked, false if not
 271       */
 272      public function is_locked() {
 273          $this->load_grade_item();
 274          if (empty($this->grade_item)) {
 275              return !empty($this->locked);
 276          } else {
 277              return !empty($this->locked) or $this->grade_item->is_locked();
 278          }
 279      }
 280  
 281      /**
 282       * Checks if grade overridden
 283       *
 284       * @return bool True if grade is overriden
 285       */
 286      public function is_overridden() {
 287          return !empty($this->overridden);
 288      }
 289  
 290      /**
 291       * Returns timestamp of submission related to this grade, null if not submitted.
 292       *
 293       * @return int Timestamp
 294       */
 295      public function get_datesubmitted() {
 296          //TODO: HACK - create new fields (MDL-31379)
 297          return $this->timecreated;
 298      }
 299  
 300      /**
 301       * Returns the weight this grade contributed to the aggregated grade
 302       *
 303       * @return float|null
 304       */
 305      public function get_aggregationweight() {
 306          return $this->aggregationweight;
 307      }
 308  
 309      /**
 310       * Set aggregationweight.
 311       *
 312       * @param float $aggregationweight
 313       * @return void
 314       */
 315      public function set_aggregationweight($aggregationweight) {
 316          $this->aggregationweight = $aggregationweight;
 317          $this->update();
 318      }
 319  
 320      /**
 321       * Returns the info on how this value was used in the aggregated grade
 322       *
 323       * @return string One of 'dropped', 'excluded', 'novalue', 'used' or 'extra'
 324       */
 325      public function get_aggregationstatus() {
 326          return $this->aggregationstatus;
 327      }
 328  
 329      /**
 330       * Set aggregationstatus flag
 331       *
 332       * @param string $aggregationstatus
 333       * @return void
 334       */
 335      public function set_aggregationstatus($aggregationstatus) {
 336          $this->aggregationstatus = $aggregationstatus;
 337          $this->update();
 338      }
 339  
 340      /**
 341       * Returns the minimum and maximum number of points this grade is graded with respect to.
 342       *
 343       * @since  Moodle 2.8.7, 2.9.1
 344       * @return array A list containing, in order, the minimum and maximum number of points.
 345       */
 346      protected function get_grade_min_and_max() {
 347          global $CFG;
 348          $this->load_grade_item();
 349  
 350          // When the following setting is turned on we use the grade_grade raw min and max values.
 351          $minmaxtouse = grade_get_setting($this->grade_item->courseid, 'minmaxtouse', $CFG->grade_minmaxtouse);
 352  
 353          // Check to see if the gradebook is frozen. This allows grades to not be altered at all until a user verifies that they
 354          // wish to update the grades.
 355          $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->grade_item->courseid);
 356          // Gradebook is frozen, run through old code.
 357          if ($gradebookcalculationsfreeze && (int)$gradebookcalculationsfreeze <= 20150627) {
 358              // Only aggregate items use separate min grades.
 359              if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_GRADE || $this->grade_item->is_aggregate_item()) {
 360                  return array($this->rawgrademin, $this->rawgrademax);
 361              } else {
 362                  return array($this->grade_item->grademin, $this->grade_item->grademax);
 363              }
 364          } else {
 365              // Only aggregate items use separate min grades, unless they are calculated grade items.
 366              if (($this->grade_item->is_aggregate_item() && !$this->grade_item->is_calculated())
 367                      || $minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_GRADE) {
 368                  return array($this->rawgrademin, $this->rawgrademax);
 369              } else {
 370                  return array($this->grade_item->grademin, $this->grade_item->grademax);
 371              }
 372          }
 373      }
 374  
 375      /**
 376       * Returns the minimum number of points this grade is graded with.
 377       *
 378       * @since  Moodle 2.8.7, 2.9.1
 379       * @return float The minimum number of points
 380       */
 381      public function get_grade_min() {
 382          list($min, $max) = $this->get_grade_min_and_max();
 383  
 384          return $min;
 385      }
 386  
 387      /**
 388       * Returns the maximum number of points this grade is graded with respect to.
 389       *
 390       * @since  Moodle 2.8.7, 2.9.1
 391       * @return float The maximum number of points
 392       */
 393      public function get_grade_max() {
 394          list($min, $max) = $this->get_grade_min_and_max();
 395  
 396          return $max;
 397      }
 398  
 399      /**
 400       * Returns timestamp when last graded, null if no grade present
 401       *
 402       * @return int
 403       */
 404      public function get_dategraded() {
 405          //TODO: HACK - create new fields (MDL-31379)
 406          if (is_null($this->finalgrade) and is_null($this->feedback)) {
 407              return null; // no grade == no date
 408          } else if ($this->overridden) {
 409              return $this->overridden;
 410          } else {
 411              return $this->timemodified;
 412          }
 413      }
 414  
 415      /**
 416       * Set the overridden status of grade
 417       *
 418       * @param bool $state requested overridden state
 419       * @param bool $refresh refresh grades from external activities if needed
 420       * @return bool true is db state changed
 421       */
 422      public function set_overridden($state, $refresh = true) {
 423          if (empty($this->overridden) and $state) {
 424              $this->overridden = time();
 425              $this->update();
 426              return true;
 427  
 428          } else if (!empty($this->overridden) and !$state) {
 429              $this->overridden = 0;
 430              $this->update();
 431  
 432              if ($refresh) {
 433                  //refresh when unlocking
 434                  $this->grade_item->refresh_grades($this->userid);
 435              }
 436  
 437              return true;
 438          }
 439          return false;
 440      }
 441  
 442      /**
 443       * Checks if grade excluded from aggregation functions
 444       *
 445       * @return bool True if grade is excluded from aggregation
 446       */
 447      public function is_excluded() {
 448          return !empty($this->excluded);
 449      }
 450  
 451      /**
 452       * Set the excluded status of grade
 453       *
 454       * @param bool $state requested excluded state
 455       * @return bool True is database state changed
 456       */
 457      public function set_excluded($state) {
 458          if (empty($this->excluded) and $state) {
 459              $this->excluded = time();
 460              $this->update();
 461              return true;
 462  
 463          } else if (!empty($this->excluded) and !$state) {
 464              $this->excluded = 0;
 465              $this->update();
 466              return true;
 467          }
 468          return false;
 469      }
 470  
 471      /**
 472       * Lock/unlock this grade.
 473       *
 474       * @param int $lockedstate 0, 1 or a timestamp int(10) after which date the item will be locked.
 475       * @param bool $cascade Ignored param
 476       * @param bool $refresh Refresh grades when unlocking
 477       * @return bool True if successful, false if can not set new lock state for grade
 478       */
 479      public function set_locked($lockedstate, $cascade=false, $refresh=true) {
 480          $this->load_grade_item();
 481  
 482          if ($lockedstate) {
 483              if ($this->grade_item->needsupdate) {
 484                  //can not lock grade if final not calculated!
 485                  return false;
 486              }
 487  
 488              $this->locked = time();
 489              $this->update();
 490  
 491              return true;
 492  
 493          } else {
 494              if (!empty($this->locked) and $this->locktime < time()) {
 495                  //we have to reset locktime or else it would lock up again
 496                  $this->locktime = 0;
 497              }
 498  
 499              // remove the locked flag
 500              $this->locked = 0;
 501              $this->update();
 502  
 503              if ($refresh and !$this->is_overridden()) {
 504                  //refresh when unlocking and not overridden
 505                  $this->grade_item->refresh_grades($this->userid);
 506              }
 507  
 508              return true;
 509          }
 510      }
 511  
 512      /**
 513       * Lock the grade if needed. Make sure this is called only when final grades are valid
 514       *
 515       * @param array $items array of all grade item ids
 516       * @return void
 517       */
 518      public static function check_locktime_all($items) {
 519          global $CFG, $DB;
 520  
 521          $now = time(); // no rounding needed, this is not supposed to be called every 10 seconds
 522          list($usql, $params) = $DB->get_in_or_equal($items);
 523          $params[] = $now;
 524          $rs = $DB->get_recordset_select('grade_grades', "itemid $usql AND locked = 0 AND locktime > 0 AND locktime < ?", $params);
 525          foreach ($rs as $grade) {
 526              $grade_grade = new grade_grade($grade, false);
 527              $grade_grade->locked = time();
 528              $grade_grade->update('locktime');
 529          }
 530          $rs->close();
 531      }
 532  
 533      /**
 534       * Set the locktime for this grade.
 535       *
 536       * @param int $locktime timestamp for lock to activate
 537       * @return void
 538       */
 539      public function set_locktime($locktime) {
 540          $this->locktime = $locktime;
 541          $this->update();
 542      }
 543  
 544      /**
 545       * Get the locktime for this grade.
 546       *
 547       * @return int $locktime timestamp for lock to activate
 548       */
 549      public function get_locktime() {
 550          $this->load_grade_item();
 551  
 552          $item_locktime = $this->grade_item->get_locktime();
 553  
 554          if (empty($this->locktime) or ($item_locktime and $item_locktime < $this->locktime)) {
 555              return $item_locktime;
 556  
 557          } else {
 558              return $this->locktime;
 559          }
 560      }
 561  
 562      /**
 563       * Check grade hidden status. Uses data from both grade item and grade.
 564       *
 565       * @return bool true if hidden, false if not
 566       */
 567      public function is_hidden() {
 568          $this->load_grade_item();
 569          if (empty($this->grade_item)) {
 570              return $this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time());
 571          } else {
 572              return $this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()) or $this->grade_item->is_hidden();
 573          }
 574      }
 575  
 576      /**
 577       * Check grade hidden status. Uses data from both grade item and grade.
 578       *
 579       * @return bool true if hiddenuntil, false if not
 580       */
 581      public function is_hiddenuntil() {
 582          $this->load_grade_item();
 583  
 584          if ($this->hidden == 1 or $this->grade_item->hidden == 1) {
 585              return false; //always hidden
 586          }
 587  
 588          if ($this->hidden > 1 or $this->grade_item->hidden > 1) {
 589              return true;
 590          }
 591  
 592          return false;
 593      }
 594  
 595      /**
 596       * Check grade hidden status. Uses data from both grade item and grade.
 597       *
 598       * @return int 0 means visible, 1 hidden always, timestamp hidden until
 599       */
 600      public function get_hidden() {
 601          $this->load_grade_item();
 602  
 603          $item_hidden = $this->grade_item->get_hidden();
 604  
 605          if ($item_hidden == 1) {
 606              return 1;
 607  
 608          } else if ($item_hidden == 0) {
 609              return $this->hidden;
 610  
 611          } else {
 612              if ($this->hidden == 0) {
 613                  return $item_hidden;
 614              } else if ($this->hidden == 1) {
 615                  return 1;
 616              } else if ($this->hidden > $item_hidden) {
 617                  return $this->hidden;
 618              } else {
 619                  return $item_hidden;
 620              }
 621          }
 622      }
 623  
 624      /**
 625       * Set the hidden status of grade, 0 mean visible, 1 always hidden, number means date to hide until.
 626       *
 627       * @param int $hidden new hidden status
 628       * @param bool $cascade ignored
 629       */
 630      public function set_hidden($hidden, $cascade=false) {
 631         $this->hidden = $hidden;
 632         $this->update();
 633      }
 634  
 635      /**
 636       * Finds and returns a grade_grade instance based on params.
 637       *
 638       * @param array $params associative arrays varname=>value
 639       * @return grade_grade Returns a grade_grade instance or false if none found
 640       */
 641      public static function fetch($params) {
 642          return grade_object::fetch_helper('grade_grades', 'grade_grade', $params);
 643      }
 644  
 645      /**
 646       * Finds and returns all grade_grade instances based on params.
 647       *
 648       * @param array $params associative arrays varname=>value
 649       * @return array array of grade_grade instances or false if none found.
 650       */
 651      public static function fetch_all($params) {
 652          return grade_object::fetch_all_helper('grade_grades', 'grade_grade', $params);
 653      }
 654  
 655      /**
 656       * Given a float value situated between a source minimum and a source maximum, converts it to the
 657       * corresponding value situated between a target minimum and a target maximum. Thanks to Darlene
 658       * for the formula :-)
 659       *
 660       * @param float $rawgrade
 661       * @param float $source_min
 662       * @param float $source_max
 663       * @param float $target_min
 664       * @param float $target_max
 665       * @return float Converted value
 666       */
 667      public static function standardise_score($rawgrade, $source_min, $source_max, $target_min, $target_max) {
 668          if (is_null($rawgrade)) {
 669            return null;
 670          }
 671  
 672          if ($source_max == $source_min or $target_min == $target_max) {
 673              // prevent division by 0
 674              return $target_max;
 675          }
 676  
 677          $factor = ($rawgrade - $source_min) / ($source_max - $source_min);
 678          $diff = $target_max - $target_min;
 679          $standardised_value = $factor * $diff + $target_min;
 680          return $standardised_value;
 681      }
 682  
 683      /**
 684       * Given an array like this:
 685       * $a = array(1=>array(2, 3),
 686       *            2=>array(4),
 687       *            3=>array(1),
 688       *            4=>array())
 689       * this function fully resolves the dependencies so each value will be an array of
 690       * the all items this item depends on and their dependencies (and their dependencies...).
 691       * It should not explode if there are circular dependencies.
 692       * The dependency depth array will list the number of branches in the tree above each leaf.
 693       *
 694       * @param array $dependson Array to flatten
 695       * @param array $dependencydepth Array of itemids => depth. Initially these should be all set to 1.
 696       * @return array Flattened array
 697       */
 698      protected static function flatten_dependencies_array(&$dependson, &$dependencydepth) {
 699          // Flatten the nested dependencies - this will handle recursion bombs because it removes duplicates.
 700          $somethingchanged = true;
 701          while ($somethingchanged) {
 702              $somethingchanged = false;
 703  
 704              foreach ($dependson as $itemid => $depends) {
 705                  // Make a copy so we can tell if it changed.
 706                  $before = $dependson[$itemid];
 707                  foreach ($depends as $subitemid => $subdepends) {
 708                      $dependson[$itemid] = array_unique(array_merge($depends, $dependson[$subdepends]));
 709                      sort($dependson[$itemid], SORT_NUMERIC);
 710                  }
 711                  if ($before != $dependson[$itemid]) {
 712                      $somethingchanged = true;
 713                      if (!isset($dependencydepth[$itemid])) {
 714                          $dependencydepth[$itemid] = 1;
 715                      } else {
 716                          $dependencydepth[$itemid]++;
 717                      }
 718                  }
 719              }
 720          }
 721      }
 722  
 723      /**
 724       * Return array of grade item ids that are either hidden or indirectly depend
 725       * on hidden grades, excluded grades are not returned.
 726       * THIS IS A REALLY BIG HACK! to be replaced by conditional aggregation of hidden grades in 2.0
 727       *
 728       * @param array $grade_grades all course grades of one user, & used for better internal caching
 729       * @param array $grade_items array of grade items, & used for better internal caching
 730       * @return array This is an array of 3 arrays:
 731       *      unknown => list of item ids that may be affected by hiding (with the calculated grade as the value)
 732       *      altered => list of item ids that are definitely affected by hiding (with the calculated grade as the value)
 733       *      alteredgrademax => for each item in altered or unknown, the new value of the grademax
 734       *      alteredgrademin => for each item in altered or unknown, the new value of the grademin
 735       *      alteredgradestatus => for each item with a modified status - the value of the new status
 736       *      alteredgradeweight => for each item with a modified weight - the value of the new weight
 737       */
 738      public static function get_hiding_affected(&$grade_grades, &$grade_items) {
 739          global $CFG;
 740  
 741          if (count($grade_grades) !== count($grade_items)) {
 742              print_error('invalidarraysize', 'debug', '', 'grade_grade::get_hiding_affected()!');
 743          }
 744  
 745          $dependson = array();
 746          $todo = array();
 747          $unknown = array();  // can not find altered
 748          $altered = array();  // altered grades
 749          $alteredgrademax = array();  // Altered grade max values.
 750          $alteredgrademin = array();  // Altered grade min values.
 751          $alteredaggregationstatus = array();  // Altered aggregation status.
 752          $alteredaggregationweight = array();  // Altered aggregation weight.
 753          $dependencydepth = array();
 754  
 755          $hiddenfound = false;
 756          foreach($grade_grades as $itemid=>$unused) {
 757              $grade_grade =& $grade_grades[$itemid];
 758              // We need the immediate dependencies of all every grade_item so we can calculate nested dependencies.
 759              $dependson[$grade_grade->itemid] = $grade_items[$grade_grade->itemid]->depends_on();
 760              if ($grade_grade->is_excluded()) {
 761                  //nothing to do, aggregation is ok
 762              } else if ($grade_grade->is_hidden()) {
 763                  $hiddenfound = true;
 764                  $altered[$grade_grade->itemid] = null;
 765                  $alteredaggregationstatus[$grade_grade->itemid] = 'dropped';
 766                  $alteredaggregationweight[$grade_grade->itemid] = 0;
 767              } else if ($grade_grade->is_locked() or $grade_grade->is_overridden()) {
 768                  // no need to recalculate locked or overridden grades
 769              } else {
 770                  if (!empty($dependson[$grade_grade->itemid])) {
 771                      $dependencydepth[$grade_grade->itemid] = 1;
 772                      $todo[] = $grade_grade->itemid;
 773                  }
 774              }
 775          }
 776  
 777          // Flatten the dependency tree and count number of branches to each leaf.
 778          self::flatten_dependencies_array($dependson, $dependencydepth);
 779  
 780          if (!$hiddenfound) {
 781              return array('unknown' => array(),
 782                           'altered' => array(),
 783                           'alteredgrademax' => array(),
 784                           'alteredgrademin' => array(),
 785                           'alteredaggregationstatus' => array(),
 786                           'alteredaggregationweight' => array());
 787          }
 788          // This line ensures that $dependencydepth has the same number of items as $todo.
 789          $dependencydepth = array_intersect_key($dependencydepth, array_flip($todo));
 790          // We need to resort the todo list by the dependency depth. This guarantees we process the leaves, then the branches.
 791          array_multisort($dependencydepth, $todo);
 792  
 793          $max = count($todo);
 794          $hidden_precursors = null;
 795          for($i=0; $i<$max; $i++) {
 796              $found = false;
 797              foreach($todo as $key=>$do) {
 798                  $hidden_precursors = array_intersect($dependson[$do], $unknown);
 799                  if ($hidden_precursors) {
 800                      // this item depends on hidden grade indirectly
 801                      $unknown[$do] = $do;
 802                      unset($todo[$key]);
 803                      $found = true;
 804                      continue;
 805  
 806                  } else if (!array_intersect($dependson[$do], $todo)) {
 807                      $hidden_precursors = array_intersect($dependson[$do], array_keys($altered));
 808                      // If the dependency is a sum aggregation, we need to process it as if it had hidden items.
 809                      // The reason for this, is that the code will recalculate the maxgrade by removing ungraded
 810                      // items and accounting for 'drop x grades' and then stored back in our virtual grade_items.
 811                      // This recalculation is necessary because there will be a call to:
 812                      //              $grade_category->aggregate_values_and_adjust_bounds
 813                      // for the top level grade that will depend on knowing what that caclulated grademax is
 814                      // and it finds that value by checking the virtual grade_items.
 815                      $issumaggregate = false;
 816                      if ($grade_items[$do]->itemtype == 'category') {
 817                          $issumaggregate = $grade_items[$do]->load_item_category()->aggregation == GRADE_AGGREGATE_SUM;
 818                      }
 819                      if (!$hidden_precursors && !$issumaggregate) {
 820                          unset($todo[$key]);
 821                          $found = true;
 822                          continue;
 823  
 824                      } else {
 825                          // depends on altered grades - we should try to recalculate if possible
 826                          if ($grade_items[$do]->is_calculated() or
 827                              (!$grade_items[$do]->is_category_item() and !$grade_items[$do]->is_course_item())
 828                          ) {
 829                              // This is a grade item that is not a category or course and has been affected by grade hiding.
 830                              // I guess this means it is a calculation that needs to be recalculated.
 831                              $unknown[$do] = $do;
 832                              unset($todo[$key]);
 833                              $found = true;
 834                              continue;
 835  
 836                          } else {
 837                              // This is a grade category (or course).
 838                              $grade_category = $grade_items[$do]->load_item_category();
 839  
 840                              // Build a new list of the grades in this category.
 841                              $values = array();
 842                              $immediatedepends = $grade_items[$do]->depends_on();
 843                              foreach ($immediatedepends as $itemid) {
 844                                  if (array_key_exists($itemid, $altered)) {
 845                                      //nulling an altered precursor
 846                                      $values[$itemid] = $altered[$itemid];
 847                                      if (is_null($values[$itemid])) {
 848                                          // This means this was a hidden grade item removed from the result.
 849                                          unset($values[$itemid]);
 850                                      }
 851                                  } elseif (empty($values[$itemid])) {
 852                                      $values[$itemid] = $grade_grades[$itemid]->finalgrade;
 853                                  }
 854                              }
 855  
 856                              foreach ($values as $itemid=>$value) {
 857                                  if ($grade_grades[$itemid]->is_excluded()) {
 858                                      unset($values[$itemid]);
 859                                      $alteredaggregationstatus[$itemid] = 'excluded';
 860                                      $alteredaggregationweight[$itemid] = null;
 861                                      continue;
 862                                  }
 863                                  // The grade min/max may have been altered by hiding.
 864                                  $grademin = $grade_items[$itemid]->grademin;
 865                                  if (isset($alteredgrademin[$itemid])) {
 866                                      $grademin = $alteredgrademin[$itemid];
 867                                  }
 868                                  $grademax = $grade_items[$itemid]->grademax;
 869                                  if (isset($alteredgrademax[$itemid])) {
 870                                      $grademax = $alteredgrademax[$itemid];
 871                                  }
 872                                  $values[$itemid] = grade_grade::standardise_score($value, $grademin, $grademax, 0, 1);
 873                              }
 874  
 875                              if ($grade_category->aggregateonlygraded) {
 876                                  foreach ($values as $itemid=>$value) {
 877                                      if (is_null($value)) {
 878                                          unset($values[$itemid]);
 879                                          $alteredaggregationstatus[$itemid] = 'novalue';
 880                                          $alteredaggregationweight[$itemid] = null;
 881                                      }
 882                                  }
 883                              } else {
 884                                  foreach ($values as $itemid=>$value) {
 885                                      if (is_null($value)) {
 886                                          $values[$itemid] = 0;
 887                                      }
 888                                  }
 889                              }
 890  
 891                              // limit and sort
 892                              $allvalues = $values;
 893                              $grade_category->apply_limit_rules($values, $grade_items);
 894  
 895                              $moredropped = array_diff($allvalues, $values);
 896                              foreach ($moredropped as $drop => $unused) {
 897                                  $alteredaggregationstatus[$drop] = 'dropped';
 898                                  $alteredaggregationweight[$drop] = null;
 899                              }
 900  
 901                              foreach ($values as $itemid => $val) {
 902                                  if ($grade_category->is_extracredit_used() && ($grade_items[$itemid]->aggregationcoef > 0)) {
 903                                      $alteredaggregationstatus[$itemid] = 'extra';
 904                                  }
 905                              }
 906  
 907                              asort($values, SORT_NUMERIC);
 908  
 909                              // let's see we have still enough grades to do any statistics
 910                              if (count($values) == 0) {
 911                                  // not enough attempts yet
 912                                  $altered[$do] = null;
 913                                  unset($todo[$key]);
 914                                  $found = true;
 915                                  continue;
 916                              }
 917  
 918                              $usedweights = array();
 919                              $adjustedgrade = $grade_category->aggregate_values_and_adjust_bounds($values, $grade_items, $usedweights);
 920  
 921                              // recalculate the rawgrade back to requested range
 922                              $finalgrade = grade_grade::standardise_score($adjustedgrade['grade'],
 923                                                                           0,
 924                                                                           1,
 925                                                                           $adjustedgrade['grademin'],
 926                                                                           $adjustedgrade['grademax']);
 927  
 928                              foreach ($usedweights as $itemid => $weight) {
 929                                  if (!isset($alteredaggregationstatus[$itemid])) {
 930                                      $alteredaggregationstatus[$itemid] = 'used';
 931                                  }
 932                                  $alteredaggregationweight[$itemid] = $weight;
 933                              }
 934  
 935                              $finalgrade = $grade_items[$do]->bounded_grade($finalgrade);
 936                              $alteredgrademin[$do] = $adjustedgrade['grademin'];
 937                              $alteredgrademax[$do] = $adjustedgrade['grademax'];
 938                              // We need to muck with the "in-memory" grade_items records so
 939                              // that subsequent calculations will use the adjusted grademin and grademax.
 940                              $grade_items[$do]->grademin = $adjustedgrade['grademin'];
 941                              $grade_items[$do]->grademax = $adjustedgrade['grademax'];
 942  
 943                              $altered[$do] = $finalgrade;
 944                              unset($todo[$key]);
 945                              $found = true;
 946                              continue;
 947                          }
 948                      }
 949                  }
 950              }
 951              if (!$found) {
 952                  break;
 953              }
 954          }
 955  
 956          return array('unknown' => $unknown,
 957                       'altered' => $altered,
 958                       'alteredgrademax' => $alteredgrademax,
 959                       'alteredgrademin' => $alteredgrademin,
 960                       'alteredaggregationstatus' => $alteredaggregationstatus,
 961                       'alteredaggregationweight' => $alteredaggregationweight);
 962      }
 963  
 964      /**
 965       * Returns true if the grade's value is superior or equal to the grade item's gradepass value, false otherwise.
 966       *
 967       * @param grade_item $grade_item An optional grade_item of which gradepass value we can use, saves having to load the grade_grade's grade_item
 968       * @return bool
 969       */
 970      public function is_passed($grade_item = null) {
 971          if (empty($grade_item)) {
 972              if (!isset($this->grade_item)) {
 973                  $this->load_grade_item();
 974              }
 975          } else {
 976              $this->grade_item = $grade_item;
 977              $this->itemid = $grade_item->id;
 978          }
 979  
 980          // Return null if finalgrade is null
 981          if (is_null($this->finalgrade)) {
 982              return null;
 983          }
 984  
 985          // Return null if gradepass == grademin, gradepass is null, or grade item is a scale and gradepass is 0.
 986          if (is_null($this->grade_item->gradepass)) {
 987              return null;
 988          } else if ($this->grade_item->gradepass == $this->grade_item->grademin) {
 989              return null;
 990          } else if ($this->grade_item->gradetype == GRADE_TYPE_SCALE && !grade_floats_different($this->grade_item->gradepass, 0.0)) {
 991              return null;
 992          }
 993  
 994          return $this->finalgrade >= $this->grade_item->gradepass;
 995      }
 996  
 997      /**
 998       * Insert the grade_grade instance into the database.
 999       *
1000       * @param string $source From where was the object inserted (mod/forum, manual, etc.)
1001       * @return int The new grade_grade ID if successful, false otherwise
1002       */
1003      public function insert($source=null) {
1004          // TODO: dategraded hack - do not update times, they are used for submission and grading (MDL-31379)
1005          //$this->timecreated = $this->timemodified = time();
1006          return parent::insert($source);
1007      }
1008  
1009      /**
1010       * In addition to update() as defined in grade_object rounds the float numbers using php function,
1011       * the reason is we need to compare the db value with computed number to skip updates if possible.
1012       *
1013       * @param string $source from where was the object inserted (mod/forum, manual, etc.)
1014       * @return bool success
1015       */
1016      public function update($source=null) {
1017          $this->rawgrade    = grade_floatval($this->rawgrade);
1018          $this->finalgrade  = grade_floatval($this->finalgrade);
1019          $this->rawgrademin = grade_floatval($this->rawgrademin);
1020          $this->rawgrademax = grade_floatval($this->rawgrademax);
1021          return parent::update($source);
1022      }
1023  
1024      /**
1025       * Deletes the grade_grade instance from the database.
1026       *
1027       * @param string $source The location the deletion occurred (mod/forum, manual, etc.).
1028       * @return bool Returns true if the deletion was successful, false otherwise.
1029       */
1030      public function delete($source = null) {
1031          $success = parent::delete($source);
1032  
1033          // If the grade was deleted successfully trigger a grade_deleted event.
1034          if ($success) {
1035              $this->load_grade_item();
1036              \core\event\grade_deleted::create_from_grade($this)->trigger();
1037          }
1038  
1039          return $success;
1040      }
1041  
1042      /**
1043       * Used to notify the completion system (if necessary) that a user's grade
1044       * has changed, and clear up a possible score cache.
1045       *
1046       * @param bool $deleted True if grade was actually deleted
1047       */
1048      protected function notify_changed($deleted) {
1049          global $CFG;
1050  
1051          // Condition code may cache the grades for conditional availability of
1052          // modules or sections. (This code should use a hook for communication
1053          // with plugin, but hooks are not implemented at time of writing.)
1054          if (!empty($CFG->enableavailability) && class_exists('\availability_grade\callbacks')) {
1055              \availability_grade\callbacks::grade_changed($this->userid);
1056          }
1057  
1058          require_once($CFG->libdir.'/completionlib.php');
1059  
1060          // Bail out immediately if completion is not enabled for site (saves loading
1061          // grade item & requiring the restore stuff).
1062          if (!completion_info::is_enabled_for_site()) {
1063              return;
1064          }
1065  
1066          // Ignore during restore, as completion data will be updated anyway and
1067          // doing it now will result in incorrect dates (it will say they got the
1068          // grade completion now, instead of the correct time).
1069          if (class_exists('restore_controller', false) && restore_controller::is_executing()) {
1070              return;
1071          }
1072  
1073          // Load information about grade item
1074          $this->load_grade_item();
1075  
1076          // Only course-modules have completion data
1077          if ($this->grade_item->itemtype!='mod') {
1078              return;
1079          }
1080  
1081          // Use $COURSE if available otherwise get it via item fields
1082          $course = get_course($this->grade_item->courseid, false);
1083  
1084          // Bail out if completion is not enabled for course
1085          $completion = new completion_info($course);
1086          if (!$completion->is_enabled()) {
1087              return;
1088          }
1089  
1090          // Get course-module
1091          $cm = get_coursemodule_from_instance($this->grade_item->itemmodule,
1092                $this->grade_item->iteminstance, $this->grade_item->courseid);
1093          // If the course-module doesn't exist, display a warning...
1094          if (!$cm) {
1095              // ...unless the grade is being deleted in which case it's likely
1096              // that the course-module was just deleted too, so that's okay.
1097              if (!$deleted) {
1098                  debugging("Couldn't find course-module for module '" .
1099                          $this->grade_item->itemmodule . "', instance '" .
1100                          $this->grade_item->iteminstance . "', course '" .
1101                          $this->grade_item->courseid . "'");
1102              }
1103              return;
1104          }
1105  
1106          // Pass information on to completion system
1107          $completion->inform_grade_changed($cm, $this->grade_item, $this, $deleted);
1108      }
1109  
1110      /**
1111       * Get some useful information about how this grade_grade is reflected in the aggregation
1112       * for the grade_category. For example this could be an extra credit item, and it could be
1113       * dropped because it's in the X lowest or highest.
1114       *
1115       * @return array(status, weight) - A keyword and a numerical weight that represents how this grade was included in the aggregation.
1116       */
1117      function get_aggregation_hint() {
1118          return array('status' => $this->get_aggregationstatus(),
1119                       'weight' => $this->get_aggregationweight());
1120      }
1121  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1