[ Index ]

PHP Cross Reference of moodle-2.8

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 timestamp when last graded, null if no grade present
 342       *
 343       * @return int
 344       */
 345      public function get_dategraded() {
 346          //TODO: HACK - create new fields (MDL-31379)
 347          if (is_null($this->finalgrade) and is_null($this->feedback)) {
 348              return null; // no grade == no date
 349          } else if ($this->overridden) {
 350              return $this->overridden;
 351          } else {
 352              return $this->timemodified;
 353          }
 354      }
 355  
 356      /**
 357       * Set the overridden status of grade
 358       *
 359       * @param bool $state requested overridden state
 360       * @param bool $refresh refresh grades from external activities if needed
 361       * @return bool true is db state changed
 362       */
 363      public function set_overridden($state, $refresh = true) {
 364          if (empty($this->overridden) and $state) {
 365              $this->overridden = time();
 366              $this->update();
 367              return true;
 368  
 369          } else if (!empty($this->overridden) and !$state) {
 370              $this->overridden = 0;
 371              $this->update();
 372  
 373              if ($refresh) {
 374                  //refresh when unlocking
 375                  $this->grade_item->refresh_grades($this->userid);
 376              }
 377  
 378              return true;
 379          }
 380          return false;
 381      }
 382  
 383      /**
 384       * Checks if grade excluded from aggregation functions
 385       *
 386       * @return bool True if grade is excluded from aggregation
 387       */
 388      public function is_excluded() {
 389          return !empty($this->excluded);
 390      }
 391  
 392      /**
 393       * Set the excluded status of grade
 394       *
 395       * @param bool $state requested excluded state
 396       * @return bool True is database state changed
 397       */
 398      public function set_excluded($state) {
 399          if (empty($this->excluded) and $state) {
 400              $this->excluded = time();
 401              $this->update();
 402              return true;
 403  
 404          } else if (!empty($this->excluded) and !$state) {
 405              $this->excluded = 0;
 406              $this->update();
 407              return true;
 408          }
 409          return false;
 410      }
 411  
 412      /**
 413       * Lock/unlock this grade.
 414       *
 415       * @param int $lockedstate 0, 1 or a timestamp int(10) after which date the item will be locked.
 416       * @param bool $cascade Ignored param
 417       * @param bool $refresh Refresh grades when unlocking
 418       * @return bool True if successful, false if can not set new lock state for grade
 419       */
 420      public function set_locked($lockedstate, $cascade=false, $refresh=true) {
 421          $this->load_grade_item();
 422  
 423          if ($lockedstate) {
 424              if ($this->grade_item->needsupdate) {
 425                  //can not lock grade if final not calculated!
 426                  return false;
 427              }
 428  
 429              $this->locked = time();
 430              $this->update();
 431  
 432              return true;
 433  
 434          } else {
 435              if (!empty($this->locked) and $this->locktime < time()) {
 436                  //we have to reset locktime or else it would lock up again
 437                  $this->locktime = 0;
 438              }
 439  
 440              // remove the locked flag
 441              $this->locked = 0;
 442              $this->update();
 443  
 444              if ($refresh and !$this->is_overridden()) {
 445                  //refresh when unlocking and not overridden
 446                  $this->grade_item->refresh_grades($this->userid);
 447              }
 448  
 449              return true;
 450          }
 451      }
 452  
 453      /**
 454       * Lock the grade if needed. Make sure this is called only when final grades are valid
 455       *
 456       * @param array $items array of all grade item ids
 457       * @return void
 458       */
 459      public static function check_locktime_all($items) {
 460          global $CFG, $DB;
 461  
 462          $now = time(); // no rounding needed, this is not supposed to be called every 10 seconds
 463          list($usql, $params) = $DB->get_in_or_equal($items);
 464          $params[] = $now;
 465          $rs = $DB->get_recordset_select('grade_grades', "itemid $usql AND locked = 0 AND locktime > 0 AND locktime < ?", $params);
 466          foreach ($rs as $grade) {
 467              $grade_grade = new grade_grade($grade, false);
 468              $grade_grade->locked = time();
 469              $grade_grade->update('locktime');
 470          }
 471          $rs->close();
 472      }
 473  
 474      /**
 475       * Set the locktime for this grade.
 476       *
 477       * @param int $locktime timestamp for lock to activate
 478       * @return void
 479       */
 480      public function set_locktime($locktime) {
 481          $this->locktime = $locktime;
 482          $this->update();
 483      }
 484  
 485      /**
 486       * Get the locktime for this grade.
 487       *
 488       * @return int $locktime timestamp for lock to activate
 489       */
 490      public function get_locktime() {
 491          $this->load_grade_item();
 492  
 493          $item_locktime = $this->grade_item->get_locktime();
 494  
 495          if (empty($this->locktime) or ($item_locktime and $item_locktime < $this->locktime)) {
 496              return $item_locktime;
 497  
 498          } else {
 499              return $this->locktime;
 500          }
 501      }
 502  
 503      /**
 504       * Check grade hidden status. Uses data from both grade item and grade.
 505       *
 506       * @return bool true if hidden, false if not
 507       */
 508      public function is_hidden() {
 509          $this->load_grade_item();
 510          if (empty($this->grade_item)) {
 511              return $this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time());
 512          } else {
 513              return $this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()) or $this->grade_item->is_hidden();
 514          }
 515      }
 516  
 517      /**
 518       * Check grade hidden status. Uses data from both grade item and grade.
 519       *
 520       * @return bool true if hiddenuntil, false if not
 521       */
 522      public function is_hiddenuntil() {
 523          $this->load_grade_item();
 524  
 525          if ($this->hidden == 1 or $this->grade_item->hidden == 1) {
 526              return false; //always hidden
 527          }
 528  
 529          if ($this->hidden > 1 or $this->grade_item->hidden > 1) {
 530              return true;
 531          }
 532  
 533          return false;
 534      }
 535  
 536      /**
 537       * Check grade hidden status. Uses data from both grade item and grade.
 538       *
 539       * @return int 0 means visible, 1 hidden always, timestamp hidden until
 540       */
 541      public function get_hidden() {
 542          $this->load_grade_item();
 543  
 544          $item_hidden = $this->grade_item->get_hidden();
 545  
 546          if ($item_hidden == 1) {
 547              return 1;
 548  
 549          } else if ($item_hidden == 0) {
 550              return $this->hidden;
 551  
 552          } else {
 553              if ($this->hidden == 0) {
 554                  return $item_hidden;
 555              } else if ($this->hidden == 1) {
 556                  return 1;
 557              } else if ($this->hidden > $item_hidden) {
 558                  return $this->hidden;
 559              } else {
 560                  return $item_hidden;
 561              }
 562          }
 563      }
 564  
 565      /**
 566       * Set the hidden status of grade, 0 mean visible, 1 always hidden, number means date to hide until.
 567       *
 568       * @param int $hidden new hidden status
 569       * @param bool $cascade ignored
 570       */
 571      public function set_hidden($hidden, $cascade=false) {
 572         $this->hidden = $hidden;
 573         $this->update();
 574      }
 575  
 576      /**
 577       * Finds and returns a grade_grade instance based on params.
 578       *
 579       * @param array $params associative arrays varname=>value
 580       * @return grade_grade Returns a grade_grade instance or false if none found
 581       */
 582      public static function fetch($params) {
 583          return grade_object::fetch_helper('grade_grades', 'grade_grade', $params);
 584      }
 585  
 586      /**
 587       * Finds and returns all grade_grade instances based on params.
 588       *
 589       * @param array $params associative arrays varname=>value
 590       * @return array array of grade_grade instances or false if none found.
 591       */
 592      public static function fetch_all($params) {
 593          return grade_object::fetch_all_helper('grade_grades', 'grade_grade', $params);
 594      }
 595  
 596      /**
 597       * Given a float value situated between a source minimum and a source maximum, converts it to the
 598       * corresponding value situated between a target minimum and a target maximum. Thanks to Darlene
 599       * for the formula :-)
 600       *
 601       * @param float $rawgrade
 602       * @param float $source_min
 603       * @param float $source_max
 604       * @param float $target_min
 605       * @param float $target_max
 606       * @return float Converted value
 607       */
 608      public static function standardise_score($rawgrade, $source_min, $source_max, $target_min, $target_max) {
 609          if (is_null($rawgrade)) {
 610            return null;
 611          }
 612  
 613          if ($source_max == $source_min or $target_min == $target_max) {
 614              // prevent division by 0
 615              return $target_max;
 616          }
 617  
 618          $factor = ($rawgrade - $source_min) / ($source_max - $source_min);
 619          $diff = $target_max - $target_min;
 620          $standardised_value = $factor * $diff + $target_min;
 621          return $standardised_value;
 622      }
 623  
 624      /**
 625       * Given an array like this:
 626       * $a = array(1=>array(2, 3),
 627       *            2=>array(4),
 628       *            3=>array(1),
 629       *            4=>array())
 630       * this function fully resolves the dependencies so each value will be an array of
 631       * the all items this item depends on and their dependencies (and their dependencies...).
 632       * It should not explode if there are circular dependencies.
 633       * The dependency depth array will list the number of branches in the tree above each leaf.
 634       *
 635       * @param array $dependson Array to flatten
 636       * @param array $dependencydepth Array of itemids => depth. Initially these should be all set to 1.
 637       * @return array Flattened array
 638       */
 639      protected static function flatten_dependencies_array(&$dependson, &$dependencydepth) {
 640          // Flatten the nested dependencies - this will handle recursion bombs because it removes duplicates.
 641          $somethingchanged = true;
 642          while ($somethingchanged) {
 643              $somethingchanged = false;
 644  
 645              foreach ($dependson as $itemid => $depends) {
 646                  // Make a copy so we can tell if it changed.
 647                  $before = $dependson[$itemid];
 648                  foreach ($depends as $subitemid => $subdepends) {
 649                      $dependson[$itemid] = array_unique(array_merge($depends, $dependson[$subdepends]));
 650                      sort($dependson[$itemid], SORT_NUMERIC);
 651                  }
 652                  if ($before != $dependson[$itemid]) {
 653                      $somethingchanged = true;
 654                      if (!isset($dependencydepth[$itemid])) {
 655                          $dependencydepth[$itemid] = 1;
 656                      } else {
 657                          $dependencydepth[$itemid]++;
 658                      }
 659                  }
 660              }
 661          }
 662      }
 663  
 664      /**
 665       * Return array of grade item ids that are either hidden or indirectly depend
 666       * on hidden grades, excluded grades are not returned.
 667       * THIS IS A REALLY BIG HACK! to be replaced by conditional aggregation of hidden grades in 2.0
 668       *
 669       * @param array $grade_grades all course grades of one user, & used for better internal caching
 670       * @param array $grade_items array of grade items, & used for better internal caching
 671       * @return array This is an array of 3 arrays:
 672       *      unknown => list of item ids that may be affected by hiding (with the calculated grade as the value)
 673       *      altered => list of item ids that are definitely affected by hiding (with the calculated grade as the value)
 674       *      alteredgrademax => for each item in altered or unknown, the new value of the grademax
 675       *      alteredgrademin => for each item in altered or unknown, the new value of the grademin
 676       *      alteredgradestatus => for each item with a modified status - the value of the new status
 677       *      alteredgradeweight => for each item with a modified weight - the value of the new weight
 678       */
 679      public static function get_hiding_affected(&$grade_grades, &$grade_items) {
 680          global $CFG;
 681  
 682          if (count($grade_grades) !== count($grade_items)) {
 683              print_error('invalidarraysize', 'debug', '', 'grade_grade::get_hiding_affected()!');
 684          }
 685  
 686          $dependson = array();
 687          $todo = array();
 688          $unknown = array();  // can not find altered
 689          $altered = array();  // altered grades
 690          $alteredgrademax = array();  // Altered grade max values.
 691          $alteredgrademin = array();  // Altered grade min values.
 692          $alteredaggregationstatus = array();  // Altered aggregation status.
 693          $alteredaggregationweight = array();  // Altered aggregation weight.
 694          $dependencydepth = array();
 695  
 696          $hiddenfound = false;
 697          foreach($grade_grades as $itemid=>$unused) {
 698              $grade_grade =& $grade_grades[$itemid];
 699              // We need the immediate dependencies of all every grade_item so we can calculate nested dependencies.
 700              $dependson[$grade_grade->itemid] = $grade_items[$grade_grade->itemid]->depends_on();
 701              if ($grade_grade->is_excluded()) {
 702                  //nothing to do, aggregation is ok
 703              } else if ($grade_grade->is_hidden()) {
 704                  $hiddenfound = true;
 705                  $altered[$grade_grade->itemid] = null;
 706                  $alteredaggregationstatus[$grade_grade->itemid] = 'dropped';
 707                  $alteredaggregationweight[$grade_grade->itemid] = 0;
 708              } else if ($grade_grade->is_locked() or $grade_grade->is_overridden()) {
 709                  // no need to recalculate locked or overridden grades
 710              } else {
 711                  if (!empty($dependson[$grade_grade->itemid])) {
 712                      $dependencydepth[$grade_grade->itemid] = 1;
 713                      $todo[] = $grade_grade->itemid;
 714                  }
 715              }
 716          }
 717  
 718          // Flatten the dependency tree and count number of branches to each leaf.
 719          self::flatten_dependencies_array($dependson, $dependencydepth);
 720  
 721          if (!$hiddenfound) {
 722              return array('unknown' => array(),
 723                           'altered' => array(),
 724                           'alteredgrademax' => array(),
 725                           'alteredgrademin' => array(),
 726                           'alteredaggregationstatus' => array(),
 727                           'alteredaggregationweight' => array());
 728          }
 729          // This line ensures that $dependencydepth has the same number of items as $todo.
 730          $dependencydepth = array_intersect_key($dependencydepth, array_flip($todo));
 731          // We need to resort the todo list by the dependency depth. This guarantees we process the leaves, then the branches.
 732          array_multisort($dependencydepth, $todo);
 733  
 734          $max = count($todo);
 735          $hidden_precursors = null;
 736          for($i=0; $i<$max; $i++) {
 737              $found = false;
 738              foreach($todo as $key=>$do) {
 739                  $hidden_precursors = array_intersect($dependson[$do], $unknown);
 740                  if ($hidden_precursors) {
 741                      // this item depends on hidden grade indirectly
 742                      $unknown[$do] = $do;
 743                      unset($todo[$key]);
 744                      $found = true;
 745                      continue;
 746  
 747                  } else if (!array_intersect($dependson[$do], $todo)) {
 748                      $hidden_precursors = array_intersect($dependson[$do], array_keys($altered));
 749                      if (!$hidden_precursors) {
 750                          // hiding does not affect this grade
 751                          unset($todo[$key]);
 752                          $found = true;
 753                          continue;
 754  
 755                      } else {
 756                          // depends on altered grades - we should try to recalculate if possible
 757                          if ($grade_items[$do]->is_calculated() or
 758                              (!$grade_items[$do]->is_category_item() and !$grade_items[$do]->is_course_item())
 759                          ) {
 760                              // This is a grade item that is not a category or course and has been affected by grade hiding.
 761                              // I guess this means it is a calculation that needs to be recalculated.
 762                              $unknown[$do] = $do;
 763                              unset($todo[$key]);
 764                              $found = true;
 765                              continue;
 766  
 767                          } else {
 768                              // This is a grade category (or course).
 769                              $grade_category = $grade_items[$do]->load_item_category();
 770  
 771                              // Build a new list of the grades in this category.
 772                              $values = array();
 773                              $immediatedepends = $grade_items[$do]->depends_on();
 774                              foreach ($immediatedepends as $itemid) {
 775                                  if (array_key_exists($itemid, $altered)) {
 776                                      //nulling an altered precursor
 777                                      $values[$itemid] = $altered[$itemid];
 778                                      if (is_null($values[$itemid])) {
 779                                          // This means this was a hidden grade item removed from the result.
 780                                          unset($values[$itemid]);
 781                                      }
 782                                  } elseif (empty($values[$itemid])) {
 783                                      $values[$itemid] = $grade_grades[$itemid]->finalgrade;
 784                                  }
 785                              }
 786  
 787                              foreach ($values as $itemid=>$value) {
 788                                  if ($grade_grades[$itemid]->is_excluded()) {
 789                                      unset($values[$itemid]);
 790                                      $alteredaggregationstatus[$itemid] = 'excluded';
 791                                      $alteredaggregationweight[$itemid] = null;
 792                                      continue;
 793                                  }
 794                                  // The grade min/max may have been altered by hiding.
 795                                  $grademin = $grade_items[$itemid]->grademin;
 796                                  if (isset($alteredgrademin[$itemid])) {
 797                                      $grademin = $alteredgrademin[$itemid];
 798                                  }
 799                                  $grademax = $grade_items[$itemid]->grademax;
 800                                  if (isset($alteredgrademax[$itemid])) {
 801                                      $grademax = $alteredgrademax[$itemid];
 802                                  }
 803                                  $values[$itemid] = grade_grade::standardise_score($value, $grademin, $grademax, 0, 1);
 804                              }
 805  
 806                              if ($grade_category->aggregateonlygraded) {
 807                                  foreach ($values as $itemid=>$value) {
 808                                      if (is_null($value)) {
 809                                          unset($values[$itemid]);
 810                                          $alteredaggregationstatus[$itemid] = 'novalue';
 811                                          $alteredaggregationweight[$itemid] = null;
 812                                      }
 813                                  }
 814                              } else {
 815                                  foreach ($values as $itemid=>$value) {
 816                                      if (is_null($value)) {
 817                                          $values[$itemid] = 0;
 818                                      }
 819                                  }
 820                              }
 821  
 822                              // limit and sort
 823                              $allvalues = $values;
 824                              $grade_category->apply_limit_rules($values, $grade_items);
 825  
 826                              $moredropped = array_diff($allvalues, $values);
 827                              foreach ($moredropped as $drop => $unused) {
 828                                  $alteredaggregationstatus[$drop] = 'dropped';
 829                                  $alteredaggregationweight[$drop] = null;
 830                              }
 831  
 832                              foreach ($values as $itemid => $val) {
 833                                  if ($grade_category->is_extracredit_used() && ($grade_items[$itemid]->aggregationcoef > 0)) {
 834                                      $alteredaggregationstatus[$itemid] = 'extra';
 835                                  }
 836                              }
 837  
 838                              asort($values, SORT_NUMERIC);
 839  
 840                              // let's see we have still enough grades to do any statistics
 841                              if (count($values) == 0) {
 842                                  // not enough attempts yet
 843                                  $altered[$do] = null;
 844                                  unset($todo[$key]);
 845                                  $found = true;
 846                                  continue;
 847                              }
 848  
 849                              $usedweights = array();
 850                              $adjustedgrade = $grade_category->aggregate_values_and_adjust_bounds($values, $grade_items, $usedweights);
 851  
 852                              // recalculate the rawgrade back to requested range
 853                              $finalgrade = grade_grade::standardise_score($adjustedgrade['grade'],
 854                                                                           0,
 855                                                                           1,
 856                                                                           $adjustedgrade['grademin'],
 857                                                                           $adjustedgrade['grademax']);
 858  
 859                              foreach ($usedweights as $itemid => $weight) {
 860                                  if (!isset($alteredaggregationstatus[$itemid])) {
 861                                      $alteredaggregationstatus[$itemid] = 'used';
 862                                  }
 863                                  $alteredaggregationweight[$itemid] = $weight;
 864                              }
 865  
 866                              $finalgrade = $grade_items[$do]->bounded_grade($finalgrade);
 867                              $alteredgrademin[$do] = $adjustedgrade['grademin'];
 868                              $alteredgrademax[$do] = $adjustedgrade['grademax'];
 869                              // We need to muck with the "in-memory" grade_items records so
 870                              // that subsequent calculations will use the adjusted grademin and grademax.
 871                              $grade_items[$do]->grademin = $adjustedgrade['grademin'];
 872                              $grade_items[$do]->grademax = $adjustedgrade['grademax'];
 873  
 874                              $altered[$do] = $finalgrade;
 875                              unset($todo[$key]);
 876                              $found = true;
 877                              continue;
 878                          }
 879                      }
 880                  }
 881              }
 882              if (!$found) {
 883                  break;
 884              }
 885          }
 886  
 887          return array('unknown' => $unknown,
 888                       'altered' => $altered,
 889                       'alteredgrademax' => $alteredgrademax,
 890                       'alteredgrademin' => $alteredgrademin,
 891                       'alteredaggregationstatus' => $alteredaggregationstatus,
 892                       'alteredaggregationweight' => $alteredaggregationweight);
 893      }
 894  
 895      /**
 896       * Returns true if the grade's value is superior or equal to the grade item's gradepass value, false otherwise.
 897       *
 898       * @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
 899       * @return bool
 900       */
 901      public function is_passed($grade_item = null) {
 902          if (empty($grade_item)) {
 903              if (!isset($this->grade_item)) {
 904                  $this->load_grade_item();
 905              }
 906          } else {
 907              $this->grade_item = $grade_item;
 908              $this->itemid = $grade_item->id;
 909          }
 910  
 911          // Return null if finalgrade is null
 912          if (is_null($this->finalgrade)) {
 913              return null;
 914          }
 915  
 916          // Return null if gradepass == grademin or gradepass is null
 917          if (is_null($this->grade_item->gradepass) || $this->grade_item->gradepass == $this->grade_item->grademin) {
 918              return null;
 919          }
 920  
 921          return $this->finalgrade >= $this->grade_item->gradepass;
 922      }
 923  
 924      /**
 925       * Insert the grade_grade instance into the database.
 926       *
 927       * @param string $source From where was the object inserted (mod/forum, manual, etc.)
 928       * @return int The new grade_grade ID if successful, false otherwise
 929       */
 930      public function insert($source=null) {
 931          // TODO: dategraded hack - do not update times, they are used for submission and grading (MDL-31379)
 932          //$this->timecreated = $this->timemodified = time();
 933          return parent::insert($source);
 934      }
 935  
 936      /**
 937       * In addition to update() as defined in grade_object rounds the float numbers using php function,
 938       * the reason is we need to compare the db value with computed number to skip updates if possible.
 939       *
 940       * @param string $source from where was the object inserted (mod/forum, manual, etc.)
 941       * @return bool success
 942       */
 943      public function update($source=null) {
 944          $this->rawgrade    = grade_floatval($this->rawgrade);
 945          $this->finalgrade  = grade_floatval($this->finalgrade);
 946          $this->rawgrademin = grade_floatval($this->rawgrademin);
 947          $this->rawgrademax = grade_floatval($this->rawgrademax);
 948          return parent::update($source);
 949      }
 950  
 951      /**
 952       * Deletes the grade_grade instance from the database.
 953       *
 954       * @param string $source The location the deletion occurred (mod/forum, manual, etc.).
 955       * @return bool Returns true if the deletion was successful, false otherwise.
 956       */
 957      public function delete($source = null) {
 958          $success = parent::delete($source);
 959  
 960          // If the grade was deleted successfully trigger a grade_deleted event.
 961          if ($success) {
 962              $this->load_grade_item();
 963              \core\event\grade_deleted::create_from_grade($this)->trigger();
 964          }
 965  
 966          return $success;
 967      }
 968  
 969      /**
 970       * Used to notify the completion system (if necessary) that a user's grade
 971       * has changed, and clear up a possible score cache.
 972       *
 973       * @param bool $deleted True if grade was actually deleted
 974       */
 975      protected function notify_changed($deleted) {
 976          global $CFG;
 977  
 978          // Condition code may cache the grades for conditional availability of
 979          // modules or sections. (This code should use a hook for communication
 980          // with plugin, but hooks are not implemented at time of writing.)
 981          if (!empty($CFG->enableavailability) && class_exists('\availability_grade\callbacks')) {
 982              \availability_grade\callbacks::grade_changed($this->userid);
 983          }
 984  
 985          require_once($CFG->libdir.'/completionlib.php');
 986  
 987          // Bail out immediately if completion is not enabled for site (saves loading
 988          // grade item & requiring the restore stuff).
 989          if (!completion_info::is_enabled_for_site()) {
 990              return;
 991          }
 992  
 993          // Ignore during restore, as completion data will be updated anyway and
 994          // doing it now will result in incorrect dates (it will say they got the
 995          // grade completion now, instead of the correct time).
 996          if (class_exists('restore_controller', false) && restore_controller::is_executing()) {
 997              return;
 998          }
 999  
1000          // Load information about grade item
1001          $this->load_grade_item();
1002  
1003          // Only course-modules have completion data
1004          if ($this->grade_item->itemtype!='mod') {
1005              return;
1006          }
1007  
1008          // Use $COURSE if available otherwise get it via item fields
1009          $course = get_course($this->grade_item->courseid, false);
1010  
1011          // Bail out if completion is not enabled for course
1012          $completion = new completion_info($course);
1013          if (!$completion->is_enabled()) {
1014              return;
1015          }
1016  
1017          // Get course-module
1018          $cm = get_coursemodule_from_instance($this->grade_item->itemmodule,
1019                $this->grade_item->iteminstance, $this->grade_item->courseid);
1020          // If the course-module doesn't exist, display a warning...
1021          if (!$cm) {
1022              // ...unless the grade is being deleted in which case it's likely
1023              // that the course-module was just deleted too, so that's okay.
1024              if (!$deleted) {
1025                  debugging("Couldn't find course-module for module '" .
1026                          $this->grade_item->itemmodule . "', instance '" .
1027                          $this->grade_item->iteminstance . "', course '" .
1028                          $this->grade_item->courseid . "'");
1029              }
1030              return;
1031          }
1032  
1033          // Pass information on to completion system
1034          $completion->inform_grade_changed($cm, $this->grade_item, $this, $deleted);
1035      }
1036  
1037      /**
1038       * Get some useful information about how this grade_grade is reflected in the aggregation
1039       * for the grade_category. For example this could be an extra credit item, and it could be
1040       * dropped because it's in the X lowest or highest.
1041       *
1042       * @return array(status, weight) - A keyword and a numerical weight that represents how this grade was included in the aggregation.
1043       */
1044      function get_aggregation_hint() {
1045          return array('status' => $this->get_aggregationstatus(),
1046                       'weight' => $this->get_aggregationweight());
1047      }
1048  }


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