[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |