[ 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 * Library of functions for gradebook - both public and internal 19 * 20 * @package core_grades 21 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 /** Include essential files */ 28 require_once($CFG->libdir . '/grade/constants.php'); 29 30 require_once($CFG->libdir . '/grade/grade_category.php'); 31 require_once($CFG->libdir . '/grade/grade_item.php'); 32 require_once($CFG->libdir . '/grade/grade_grade.php'); 33 require_once($CFG->libdir . '/grade/grade_scale.php'); 34 require_once($CFG->libdir . '/grade/grade_outcome.php'); 35 36 ///////////////////////////////////////////////////////////////////// 37 ///// Start of public API for communication with modules/blocks ///// 38 ///////////////////////////////////////////////////////////////////// 39 40 /** 41 * Submit new or update grade; update/create grade_item definition. Grade must have userid specified, 42 * rawgrade and feedback with format are optional. rawgrade NULL means 'Not graded'. 43 * Missing property or key means does not change the existing value. 44 * 45 * Only following grade item properties can be changed 'itemname', 'idnumber', 'gradetype', 'grademax', 46 * 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted' and 'hidden'. 'reset' means delete all current grades including locked ones. 47 * 48 * Manual, course or category items can not be updated by this function. 49 * 50 * @category grade 51 * @param string $source Source of the grade such as 'mod/assignment' 52 * @param int $courseid ID of course 53 * @param string $itemtype Type of grade item. For example, mod or block 54 * @param string $itemmodule More specific then $itemtype. For example, assignment or forum. May be NULL for some item types 55 * @param int $iteminstance Instance ID of graded item 56 * @param int $itemnumber Most probably 0. Modules can use other numbers when having more than one grade for each user 57 * @param mixed $grades Grade (object, array) or several grades (arrays of arrays or objects), NULL if updating grade_item definition only 58 * @param mixed $itemdetails Object or array describing the grading item, NULL if no change 59 * @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED 60 */ 61 function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, $itemnumber, $grades=NULL, $itemdetails=NULL) { 62 global $USER, $CFG, $DB; 63 64 // only following grade_item properties can be changed in this function 65 $allowed = array('itemname', 'idnumber', 'gradetype', 'grademax', 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted', 'hidden'); 66 // list of 10,5 numeric fields 67 $floats = array('grademin', 'grademax', 'multfactor', 'plusfactor'); 68 69 // grade item identification 70 $params = compact('courseid', 'itemtype', 'itemmodule', 'iteminstance', 'itemnumber'); 71 72 if (is_null($courseid) or is_null($itemtype)) { 73 debugging('Missing courseid or itemtype'); 74 return GRADE_UPDATE_FAILED; 75 } 76 77 if (!$grade_items = grade_item::fetch_all($params)) { 78 // create a new one 79 $grade_item = false; 80 81 } else if (count($grade_items) == 1){ 82 $grade_item = reset($grade_items); 83 unset($grade_items); //release memory 84 85 } else { 86 debugging('Found more than one grade item'); 87 return GRADE_UPDATE_MULTIPLE; 88 } 89 90 if (!empty($itemdetails['deleted'])) { 91 if ($grade_item) { 92 if ($grade_item->delete($source)) { 93 return GRADE_UPDATE_OK; 94 } else { 95 return GRADE_UPDATE_FAILED; 96 } 97 } 98 return GRADE_UPDATE_OK; 99 } 100 101 /// Create or update the grade_item if needed 102 103 if (!$grade_item) { 104 if ($itemdetails) { 105 $itemdetails = (array)$itemdetails; 106 107 // grademin and grademax ignored when scale specified 108 if (array_key_exists('scaleid', $itemdetails)) { 109 if ($itemdetails['scaleid']) { 110 unset($itemdetails['grademin']); 111 unset($itemdetails['grademax']); 112 } 113 } 114 115 foreach ($itemdetails as $k=>$v) { 116 if (!in_array($k, $allowed)) { 117 // ignore it 118 continue; 119 } 120 if ($k == 'gradetype' and $v == GRADE_TYPE_NONE) { 121 // no grade item needed! 122 return GRADE_UPDATE_OK; 123 } 124 $params[$k] = $v; 125 } 126 } 127 $grade_item = new grade_item($params); 128 $grade_item->insert(); 129 130 } else { 131 if ($grade_item->is_locked()) { 132 // no notice() here, test returned value instead! 133 return GRADE_UPDATE_ITEM_LOCKED; 134 } 135 136 if ($itemdetails) { 137 $itemdetails = (array)$itemdetails; 138 $update = false; 139 foreach ($itemdetails as $k=>$v) { 140 if (!in_array($k, $allowed)) { 141 // ignore it 142 continue; 143 } 144 if (in_array($k, $floats)) { 145 if (grade_floats_different($grade_item->{$k}, $v)) { 146 $grade_item->{$k} = $v; 147 $update = true; 148 } 149 150 } else { 151 if ($grade_item->{$k} != $v) { 152 $grade_item->{$k} = $v; 153 $update = true; 154 } 155 } 156 } 157 if ($update) { 158 $grade_item->update(); 159 } 160 } 161 } 162 163 /// reset grades if requested 164 if (!empty($itemdetails['reset'])) { 165 $grade_item->delete_all_grades('reset'); 166 return GRADE_UPDATE_OK; 167 } 168 169 /// Some extra checks 170 // do we use grading? 171 if ($grade_item->gradetype == GRADE_TYPE_NONE) { 172 return GRADE_UPDATE_OK; 173 } 174 175 // no grade submitted 176 if (empty($grades)) { 177 return GRADE_UPDATE_OK; 178 } 179 180 /// Finally start processing of grades 181 if (is_object($grades)) { 182 $grades = array($grades->userid=>$grades); 183 } else { 184 if (array_key_exists('userid', $grades)) { 185 $grades = array($grades['userid']=>$grades); 186 } 187 } 188 189 /// normalize and verify grade array 190 foreach($grades as $k=>$g) { 191 if (!is_array($g)) { 192 $g = (array)$g; 193 $grades[$k] = $g; 194 } 195 196 if (empty($g['userid']) or $k != $g['userid']) { 197 debugging('Incorrect grade array index, must be user id! Grade ignored.'); 198 unset($grades[$k]); 199 } 200 } 201 202 if (empty($grades)) { 203 return GRADE_UPDATE_FAILED; 204 } 205 206 $count = count($grades); 207 if ($count > 0 and $count < 200) { 208 list($uids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED, $start='uid'); 209 $params['gid'] = $grade_item->id; 210 $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid AND userid $uids"; 211 212 } else { 213 $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid"; 214 $params = array('gid'=>$grade_item->id); 215 } 216 217 $rs = $DB->get_recordset_sql($sql, $params); 218 219 $failed = false; 220 221 while (count($grades) > 0) { 222 $grade_grade = null; 223 $grade = null; 224 225 foreach ($rs as $gd) { 226 227 $userid = $gd->userid; 228 if (!isset($grades[$userid])) { 229 // this grade not requested, continue 230 continue; 231 } 232 // existing grade requested 233 $grade = $grades[$userid]; 234 $grade_grade = new grade_grade($gd, false); 235 unset($grades[$userid]); 236 break; 237 } 238 239 if (is_null($grade_grade)) { 240 if (count($grades) == 0) { 241 // no more grades to process 242 break; 243 } 244 245 $grade = reset($grades); 246 $userid = $grade['userid']; 247 $grade_grade = new grade_grade(array('itemid'=>$grade_item->id, 'userid'=>$userid), false); 248 $grade_grade->load_optional_fields(); // add feedback and info too 249 unset($grades[$userid]); 250 } 251 252 $rawgrade = false; 253 $feedback = false; 254 $feedbackformat = FORMAT_MOODLE; 255 $usermodified = $USER->id; 256 $datesubmitted = null; 257 $dategraded = null; 258 259 if (array_key_exists('rawgrade', $grade)) { 260 $rawgrade = $grade['rawgrade']; 261 } 262 263 if (array_key_exists('feedback', $grade)) { 264 $feedback = $grade['feedback']; 265 } 266 267 if (array_key_exists('feedbackformat', $grade)) { 268 $feedbackformat = $grade['feedbackformat']; 269 } 270 271 if (array_key_exists('usermodified', $grade)) { 272 $usermodified = $grade['usermodified']; 273 } 274 275 if (array_key_exists('datesubmitted', $grade)) { 276 $datesubmitted = $grade['datesubmitted']; 277 } 278 279 if (array_key_exists('dategraded', $grade)) { 280 $dategraded = $grade['dategraded']; 281 } 282 283 // update or insert the grade 284 if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified, $dategraded, $datesubmitted, $grade_grade)) { 285 $failed = true; 286 } 287 } 288 289 if ($rs) { 290 $rs->close(); 291 } 292 293 if (!$failed) { 294 return GRADE_UPDATE_OK; 295 } else { 296 return GRADE_UPDATE_FAILED; 297 } 298 } 299 300 /** 301 * Updates a user's outcomes. Manual outcomes can not be updated. 302 * 303 * @category grade 304 * @param string $source Source of the grade such as 'mod/assignment' 305 * @param int $courseid ID of course 306 * @param string $itemtype Type of grade item. For example, 'mod' or 'block' 307 * @param string $itemmodule More specific then $itemtype. For example, 'forum' or 'quiz'. May be NULL for some item types 308 * @param int $iteminstance Instance ID of graded item. For example the forum ID. 309 * @param int $userid ID of the graded user 310 * @param array $data Array consisting of grade item itemnumber ({@link grade_update()}) => outcomegrade 311 * @return bool returns true if grade items were found and updated successfully 312 */ 313 function grade_update_outcomes($source, $courseid, $itemtype, $itemmodule, $iteminstance, $userid, $data) { 314 if ($items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) { 315 $result = true; 316 foreach ($items as $item) { 317 if (!array_key_exists($item->itemnumber, $data)) { 318 continue; 319 } 320 $grade = $data[$item->itemnumber] < 1 ? null : $data[$item->itemnumber]; 321 $result = ($item->update_final_grade($userid, $grade, $source) && $result); 322 } 323 return $result; 324 } 325 return false; //grade items not found 326 } 327 328 /** 329 * Returns grading information for given activity, optionally with user grades 330 * Manual, course or category items can not be queried. 331 * 332 * @category grade 333 * @param int $courseid ID of course 334 * @param string $itemtype Type of grade item. For example, 'mod' or 'block' 335 * @param string $itemmodule More specific then $itemtype. For example, 'forum' or 'quiz'. May be NULL for some item types 336 * @param int $iteminstance ID of the item module 337 * @param mixed $userid_or_ids Either a single user ID, an array of user IDs or null. If user ID or IDs are not supplied returns information about grade_item 338 * @return array Array of grade information objects (scaleid, name, grade and locked status, etc.) indexed with itemnumbers 339 */ 340 function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) { 341 global $CFG; 342 343 $return = new stdClass(); 344 $return->items = array(); 345 $return->outcomes = array(); 346 347 $course_item = grade_item::fetch_course_item($courseid); 348 $needsupdate = array(); 349 if ($course_item->needsupdate) { 350 $result = grade_regrade_final_grades($courseid); 351 if ($result !== true) { 352 $needsupdate = array_keys($result); 353 } 354 } 355 356 if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) { 357 foreach ($grade_items as $grade_item) { 358 $decimalpoints = null; 359 360 if (empty($grade_item->outcomeid)) { 361 // prepare information about grade item 362 $item = new stdClass(); 363 $item->id = $grade_item->id; 364 $item->itemnumber = $grade_item->itemnumber; 365 $item->itemtype = $grade_item->itemtype; 366 $item->itemmodule = $grade_item->itemmodule; 367 $item->iteminstance = $grade_item->iteminstance; 368 $item->scaleid = $grade_item->scaleid; 369 $item->name = $grade_item->get_name(); 370 $item->grademin = $grade_item->grademin; 371 $item->grademax = $grade_item->grademax; 372 $item->gradepass = $grade_item->gradepass; 373 $item->locked = $grade_item->is_locked(); 374 $item->hidden = $grade_item->is_hidden(); 375 $item->grades = array(); 376 377 switch ($grade_item->gradetype) { 378 case GRADE_TYPE_NONE: 379 continue; 380 381 case GRADE_TYPE_VALUE: 382 $item->scaleid = 0; 383 break; 384 385 case GRADE_TYPE_TEXT: 386 $item->scaleid = 0; 387 $item->grademin = 0; 388 $item->grademax = 0; 389 $item->gradepass = 0; 390 break; 391 } 392 393 if (empty($userid_or_ids)) { 394 $userids = array(); 395 396 } else if (is_array($userid_or_ids)) { 397 $userids = $userid_or_ids; 398 399 } else { 400 $userids = array($userid_or_ids); 401 } 402 403 if ($userids) { 404 $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true); 405 foreach ($userids as $userid) { 406 $grade_grades[$userid]->grade_item =& $grade_item; 407 408 $grade = new stdClass(); 409 $grade->grade = $grade_grades[$userid]->finalgrade; 410 $grade->locked = $grade_grades[$userid]->is_locked(); 411 $grade->hidden = $grade_grades[$userid]->is_hidden(); 412 $grade->overridden = $grade_grades[$userid]->overridden; 413 $grade->feedback = $grade_grades[$userid]->feedback; 414 $grade->feedbackformat = $grade_grades[$userid]->feedbackformat; 415 $grade->usermodified = $grade_grades[$userid]->usermodified; 416 $grade->datesubmitted = $grade_grades[$userid]->get_datesubmitted(); 417 $grade->dategraded = $grade_grades[$userid]->get_dategraded(); 418 419 // create text representation of grade 420 if ($grade_item->gradetype == GRADE_TYPE_TEXT or $grade_item->gradetype == GRADE_TYPE_NONE) { 421 $grade->grade = null; 422 $grade->str_grade = '-'; 423 $grade->str_long_grade = $grade->str_grade; 424 425 } else if (in_array($grade_item->id, $needsupdate)) { 426 $grade->grade = false; 427 $grade->str_grade = get_string('error'); 428 $grade->str_long_grade = $grade->str_grade; 429 430 } else if (is_null($grade->grade)) { 431 $grade->str_grade = '-'; 432 $grade->str_long_grade = $grade->str_grade; 433 434 } else { 435 $grade->str_grade = grade_format_gradevalue($grade->grade, $grade_item); 436 if ($grade_item->gradetype == GRADE_TYPE_SCALE or $grade_item->get_displaytype() != GRADE_DISPLAY_TYPE_REAL) { 437 $grade->str_long_grade = $grade->str_grade; 438 } else { 439 $a = new stdClass(); 440 $a->grade = $grade->str_grade; 441 $a->max = grade_format_gradevalue($grade_item->grademax, $grade_item); 442 $grade->str_long_grade = get_string('gradelong', 'grades', $a); 443 } 444 } 445 446 // create html representation of feedback 447 if (is_null($grade->feedback)) { 448 $grade->str_feedback = ''; 449 } else { 450 $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat); 451 } 452 453 $item->grades[$userid] = $grade; 454 } 455 } 456 $return->items[$grade_item->itemnumber] = $item; 457 458 } else { 459 if (!$grade_outcome = grade_outcome::fetch(array('id'=>$grade_item->outcomeid))) { 460 debugging('Incorect outcomeid found'); 461 continue; 462 } 463 464 // outcome info 465 $outcome = new stdClass(); 466 $outcome->id = $grade_item->id; 467 $outcome->itemnumber = $grade_item->itemnumber; 468 $outcome->itemtype = $grade_item->itemtype; 469 $outcome->itemmodule = $grade_item->itemmodule; 470 $outcome->iteminstance = $grade_item->iteminstance; 471 $outcome->scaleid = $grade_outcome->scaleid; 472 $outcome->name = $grade_outcome->get_name(); 473 $outcome->locked = $grade_item->is_locked(); 474 $outcome->hidden = $grade_item->is_hidden(); 475 476 if (empty($userid_or_ids)) { 477 $userids = array(); 478 } else if (is_array($userid_or_ids)) { 479 $userids = $userid_or_ids; 480 } else { 481 $userids = array($userid_or_ids); 482 } 483 484 if ($userids) { 485 $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true); 486 foreach ($userids as $userid) { 487 $grade_grades[$userid]->grade_item =& $grade_item; 488 489 $grade = new stdClass(); 490 $grade->grade = $grade_grades[$userid]->finalgrade; 491 $grade->locked = $grade_grades[$userid]->is_locked(); 492 $grade->hidden = $grade_grades[$userid]->is_hidden(); 493 $grade->feedback = $grade_grades[$userid]->feedback; 494 $grade->feedbackformat = $grade_grades[$userid]->feedbackformat; 495 $grade->usermodified = $grade_grades[$userid]->usermodified; 496 497 // create text representation of grade 498 if (in_array($grade_item->id, $needsupdate)) { 499 $grade->grade = false; 500 $grade->str_grade = get_string('error'); 501 502 } else if (is_null($grade->grade)) { 503 $grade->grade = 0; 504 $grade->str_grade = get_string('nooutcome', 'grades'); 505 506 } else { 507 $grade->grade = (int)$grade->grade; 508 $scale = $grade_item->load_scale(); 509 $grade->str_grade = format_string($scale->scale_items[(int)$grade->grade-1]); 510 } 511 512 // create html representation of feedback 513 if (is_null($grade->feedback)) { 514 $grade->str_feedback = ''; 515 } else { 516 $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat); 517 } 518 519 $outcome->grades[$userid] = $grade; 520 } 521 } 522 523 if (isset($return->outcomes[$grade_item->itemnumber])) { 524 // itemnumber duplicates - lets fix them! 525 $newnumber = $grade_item->itemnumber + 1; 526 while(grade_item::fetch(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid, 'itemnumber'=>$newnumber))) { 527 $newnumber++; 528 } 529 $outcome->itemnumber = $newnumber; 530 $grade_item->itemnumber = $newnumber; 531 $grade_item->update('system'); 532 } 533 534 $return->outcomes[$grade_item->itemnumber] = $outcome; 535 536 } 537 } 538 } 539 540 // sort results using itemnumbers 541 ksort($return->items, SORT_NUMERIC); 542 ksort($return->outcomes, SORT_NUMERIC); 543 544 return $return; 545 } 546 547 /////////////////////////////////////////////////////////////////// 548 ///// End of public API for communication with modules/blocks ///// 549 /////////////////////////////////////////////////////////////////// 550 551 552 553 /////////////////////////////////////////////////////////////////// 554 ///// Internal API: used by gradebook plugins and Moodle core ///// 555 /////////////////////////////////////////////////////////////////// 556 557 /** 558 * Returns a course gradebook setting 559 * 560 * @param int $courseid 561 * @param string $name of setting, maybe null if reset only 562 * @param string $default value to return if setting is not found 563 * @param bool $resetcache force reset of internal static cache 564 * @return string value of the setting, $default if setting not found, NULL if supplied $name is null 565 */ 566 function grade_get_setting($courseid, $name, $default=null, $resetcache=false) { 567 global $DB; 568 569 static $cache = array(); 570 571 if ($resetcache or !array_key_exists($courseid, $cache)) { 572 $cache[$courseid] = array(); 573 574 } else if (is_null($name)) { 575 return null; 576 577 } else if (array_key_exists($name, $cache[$courseid])) { 578 return $cache[$courseid][$name]; 579 } 580 581 if (!$data = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) { 582 $result = null; 583 } else { 584 $result = $data->value; 585 } 586 587 if (is_null($result)) { 588 $result = $default; 589 } 590 591 $cache[$courseid][$name] = $result; 592 return $result; 593 } 594 595 /** 596 * Returns all course gradebook settings as object properties 597 * 598 * @param int $courseid 599 * @return object 600 */ 601 function grade_get_settings($courseid) { 602 global $DB; 603 604 $settings = new stdClass(); 605 $settings->id = $courseid; 606 607 if ($records = $DB->get_records('grade_settings', array('courseid'=>$courseid))) { 608 foreach ($records as $record) { 609 $settings->{$record->name} = $record->value; 610 } 611 } 612 613 return $settings; 614 } 615 616 /** 617 * Add, update or delete a course gradebook setting 618 * 619 * @param int $courseid The course ID 620 * @param string $name Name of the setting 621 * @param string $value Value of the setting. NULL means delete the setting. 622 */ 623 function grade_set_setting($courseid, $name, $value) { 624 global $DB; 625 626 if (is_null($value)) { 627 $DB->delete_records('grade_settings', array('courseid'=>$courseid, 'name'=>$name)); 628 629 } else if (!$existing = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) { 630 $data = new stdClass(); 631 $data->courseid = $courseid; 632 $data->name = $name; 633 $data->value = $value; 634 $DB->insert_record('grade_settings', $data); 635 636 } else { 637 $data = new stdClass(); 638 $data->id = $existing->id; 639 $data->value = $value; 640 $DB->update_record('grade_settings', $data); 641 } 642 643 grade_get_setting($courseid, null, null, true); // reset the cache 644 } 645 646 /** 647 * Returns string representation of grade value 648 * 649 * @param float $value The grade value 650 * @param object $grade_item Grade item object passed by reference to prevent scale reloading 651 * @param bool $localized use localised decimal separator 652 * @param int $displaytype type of display. For example GRADE_DISPLAY_TYPE_REAL, GRADE_DISPLAY_TYPE_PERCENTAGE, GRADE_DISPLAY_TYPE_LETTER 653 * @param int $decimals The number of decimal places when displaying float values 654 * @return string 655 */ 656 function grade_format_gradevalue($value, &$grade_item, $localized=true, $displaytype=null, $decimals=null) { 657 if ($grade_item->gradetype == GRADE_TYPE_NONE or $grade_item->gradetype == GRADE_TYPE_TEXT) { 658 return ''; 659 } 660 661 // no grade yet? 662 if (is_null($value)) { 663 return '-'; 664 } 665 666 if ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) { 667 //unknown type?? 668 return ''; 669 } 670 671 if (is_null($displaytype)) { 672 $displaytype = $grade_item->get_displaytype(); 673 } 674 675 if (is_null($decimals)) { 676 $decimals = $grade_item->get_decimals(); 677 } 678 679 switch ($displaytype) { 680 case GRADE_DISPLAY_TYPE_REAL: 681 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized); 682 683 case GRADE_DISPLAY_TYPE_PERCENTAGE: 684 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized); 685 686 case GRADE_DISPLAY_TYPE_LETTER: 687 return grade_format_gradevalue_letter($value, $grade_item); 688 689 case GRADE_DISPLAY_TYPE_REAL_PERCENTAGE: 690 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' . 691 grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')'; 692 693 case GRADE_DISPLAY_TYPE_REAL_LETTER: 694 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' . 695 grade_format_gradevalue_letter($value, $grade_item) . ')'; 696 697 case GRADE_DISPLAY_TYPE_PERCENTAGE_REAL: 698 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' . 699 grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')'; 700 701 case GRADE_DISPLAY_TYPE_LETTER_REAL: 702 return grade_format_gradevalue_letter($value, $grade_item) . ' (' . 703 grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')'; 704 705 case GRADE_DISPLAY_TYPE_LETTER_PERCENTAGE: 706 return grade_format_gradevalue_letter($value, $grade_item) . ' (' . 707 grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')'; 708 709 case GRADE_DISPLAY_TYPE_PERCENTAGE_LETTER: 710 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' . 711 grade_format_gradevalue_letter($value, $grade_item) . ')'; 712 default: 713 return ''; 714 } 715 } 716 717 /** 718 * Returns a float representation of a grade value 719 * 720 * @param float $value The grade value 721 * @param object $grade_item Grade item object 722 * @param int $decimals The number of decimal places 723 * @param bool $localized use localised decimal separator 724 * @return string 725 */ 726 function grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) { 727 if ($grade_item->gradetype == GRADE_TYPE_SCALE) { 728 if (!$scale = $grade_item->load_scale()) { 729 return get_string('error'); 730 } 731 732 $value = $grade_item->bounded_grade($value); 733 return format_string($scale->scale_items[$value-1]); 734 735 } else { 736 return format_float($value, $decimals, $localized); 737 } 738 } 739 740 /** 741 * Returns a percentage representation of a grade value 742 * 743 * @param float $value The grade value 744 * @param object $grade_item Grade item object 745 * @param int $decimals The number of decimal places 746 * @param bool $localized use localised decimal separator 747 * @return string 748 */ 749 function grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) { 750 $min = $grade_item->grademin; 751 $max = $grade_item->grademax; 752 if ($min == $max) { 753 return ''; 754 } 755 $value = $grade_item->bounded_grade($value); 756 $percentage = (($value-$min)*100)/($max-$min); 757 return format_float($percentage, $decimals, $localized).' %'; 758 } 759 760 /** 761 * Returns a letter grade representation of a grade value 762 * The array of grade letters used is produced by {@link grade_get_letters()} using the course context 763 * 764 * @param float $value The grade value 765 * @param object $grade_item Grade item object 766 * @return string 767 */ 768 function grade_format_gradevalue_letter($value, $grade_item) { 769 $context = context_course::instance($grade_item->courseid, IGNORE_MISSING); 770 if (!$letters = grade_get_letters($context)) { 771 return ''; // no letters?? 772 } 773 774 if (is_null($value)) { 775 return '-'; 776 } 777 778 $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100); 779 $value = bounded_number(0, $value, 100); // just in case 780 foreach ($letters as $boundary => $letter) { 781 if ($value >= $boundary) { 782 return format_string($letter); 783 } 784 } 785 return '-'; // no match? maybe '' would be more correct 786 } 787 788 789 /** 790 * Returns grade options for gradebook grade category menu 791 * 792 * @param int $courseid The course ID 793 * @param bool $includenew Include option for new category at array index -1 794 * @return array of grade categories in course 795 */ 796 function grade_get_categories_menu($courseid, $includenew=false) { 797 $result = array(); 798 if (!$categories = grade_category::fetch_all(array('courseid'=>$courseid))) { 799 //make sure course category exists 800 if (!grade_category::fetch_course_category($courseid)) { 801 debugging('Can not create course grade category!'); 802 return $result; 803 } 804 $categories = grade_category::fetch_all(array('courseid'=>$courseid)); 805 } 806 foreach ($categories as $key=>$category) { 807 if ($category->is_course_category()) { 808 $result[$category->id] = get_string('uncategorised', 'grades'); 809 unset($categories[$key]); 810 } 811 } 812 if ($includenew) { 813 $result[-1] = get_string('newcategory', 'grades'); 814 } 815 $cats = array(); 816 foreach ($categories as $category) { 817 $cats[$category->id] = $category->get_name(); 818 } 819 core_collator::asort($cats); 820 821 return ($result+$cats); 822 } 823 824 /** 825 * Returns the array of grade letters to be used in the supplied context 826 * 827 * @param object $context Context object or null for defaults 828 * @return array of grade_boundary (minimum) => letter_string 829 */ 830 function grade_get_letters($context=null) { 831 global $DB; 832 833 if (empty($context)) { 834 //default grading letters 835 return array('93'=>'A', '90'=>'A-', '87'=>'B+', '83'=>'B', '80'=>'B-', '77'=>'C+', '73'=>'C', '70'=>'C-', '67'=>'D+', '60'=>'D', '0'=>'F'); 836 } 837 838 static $cache = array(); 839 840 if (array_key_exists($context->id, $cache)) { 841 return $cache[$context->id]; 842 } 843 844 if (count($cache) > 100) { 845 $cache = array(); // cache size limit 846 } 847 848 $letters = array(); 849 850 $contexts = $context->get_parent_context_ids(); 851 array_unshift($contexts, $context->id); 852 853 foreach ($contexts as $ctxid) { 854 if ($records = $DB->get_records('grade_letters', array('contextid'=>$ctxid), 'lowerboundary DESC')) { 855 foreach ($records as $record) { 856 $letters[$record->lowerboundary] = $record->letter; 857 } 858 } 859 860 if (!empty($letters)) { 861 $cache[$context->id] = $letters; 862 return $letters; 863 } 864 } 865 866 $letters = grade_get_letters(null); 867 $cache[$context->id] = $letters; 868 return $letters; 869 } 870 871 872 /** 873 * Verify new value of grade item idnumber. Checks for uniqueness of new ID numbers. Old ID numbers are kept intact. 874 * 875 * @param string $idnumber string (with magic quotes) 876 * @param int $courseid ID numbers are course unique only 877 * @param grade_item $grade_item The grade item this idnumber is associated with 878 * @param stdClass $cm used for course module idnumbers and items attached to modules 879 * @return bool true means idnumber ok 880 */ 881 function grade_verify_idnumber($idnumber, $courseid, $grade_item=null, $cm=null) { 882 global $DB; 883 884 if ($idnumber == '') { 885 //we allow empty idnumbers 886 return true; 887 } 888 889 // keep existing even when not unique 890 if ($cm and $cm->idnumber == $idnumber) { 891 if ($grade_item and $grade_item->itemnumber != 0) { 892 // grade item with itemnumber > 0 can't have the same idnumber as the main 893 // itemnumber 0 which is synced with course_modules 894 return false; 895 } 896 return true; 897 } else if ($grade_item and $grade_item->idnumber == $idnumber) { 898 return true; 899 } 900 901 if ($DB->record_exists('course_modules', array('course'=>$courseid, 'idnumber'=>$idnumber))) { 902 return false; 903 } 904 905 if ($DB->record_exists('grade_items', array('courseid'=>$courseid, 'idnumber'=>$idnumber))) { 906 return false; 907 } 908 909 return true; 910 } 911 912 /** 913 * Force final grade recalculation in all course items 914 * 915 * @param int $courseid The course ID to recalculate 916 */ 917 function grade_force_full_regrading($courseid) { 918 global $DB; 919 $DB->set_field('grade_items', 'needsupdate', 1, array('courseid'=>$courseid)); 920 } 921 922 /** 923 * Forces regrading of all site grades. Used when changing site setings 924 */ 925 function grade_force_site_regrading() { 926 global $CFG, $DB; 927 $DB->set_field('grade_items', 'needsupdate', 1); 928 } 929 930 /** 931 * Recover a user's grades from grade_grades_history 932 * @param int $userid the user ID whose grades we want to recover 933 * @param int $courseid the relevant course 934 * @return bool true if successful or false if there was an error or no grades could be recovered 935 */ 936 function grade_recover_history_grades($userid, $courseid) { 937 global $CFG, $DB; 938 939 if ($CFG->disablegradehistory) { 940 debugging('Attempting to recover grades when grade history is disabled.'); 941 return false; 942 } 943 944 //Were grades recovered? Flag to return. 945 $recoveredgrades = false; 946 947 //Check the user is enrolled in this course 948 //Dont bother checking if they have a gradeable role. They may get one later so recover 949 //whatever grades they have now just in case. 950 $course_context = context_course::instance($courseid); 951 if (!is_enrolled($course_context, $userid)) { 952 debugging('Attempting to recover the grades of a user who is deleted or not enrolled. Skipping recover.'); 953 return false; 954 } 955 956 //Check for existing grades for this user in this course 957 //Recovering grades when the user already has grades can lead to duplicate indexes and bad data 958 //In the future we could move the existing grades to the history table then recover the grades from before then 959 $sql = "SELECT gg.id 960 FROM {grade_grades} gg 961 JOIN {grade_items} gi ON gi.id = gg.itemid 962 WHERE gi.courseid = :courseid AND gg.userid = :userid"; 963 $params = array('userid' => $userid, 'courseid' => $courseid); 964 if ($DB->record_exists_sql($sql, $params)) { 965 debugging('Attempting to recover the grades of a user who already has grades. Skipping recover.'); 966 return false; 967 } else { 968 //Retrieve the user's old grades 969 //have history ID as first column to guarantee we a unique first column 970 $sql = "SELECT h.id, gi.itemtype, gi.itemmodule, gi.iteminstance as iteminstance, gi.itemnumber, h.source, h.itemid, h.userid, h.rawgrade, h.rawgrademax, 971 h.rawgrademin, h.rawscaleid, h.usermodified, h.finalgrade, h.hidden, h.locked, h.locktime, h.exported, h.overridden, h.excluded, h.feedback, 972 h.feedbackformat, h.information, h.informationformat, h.timemodified, itemcreated.tm AS timecreated 973 FROM {grade_grades_history} h 974 JOIN (SELECT itemid, MAX(id) AS id 975 FROM {grade_grades_history} 976 WHERE userid = :userid1 977 GROUP BY itemid) maxquery ON h.id = maxquery.id AND h.itemid = maxquery.itemid 978 JOIN {grade_items} gi ON gi.id = h.itemid 979 JOIN (SELECT itemid, MAX(timemodified) AS tm 980 FROM {grade_grades_history} 981 WHERE userid = :userid2 AND action = :insertaction 982 GROUP BY itemid) itemcreated ON itemcreated.itemid = h.itemid 983 WHERE gi.courseid = :courseid"; 984 $params = array('userid1' => $userid, 'userid2' => $userid , 'insertaction' => GRADE_HISTORY_INSERT, 'courseid' => $courseid); 985 $oldgrades = $DB->get_records_sql($sql, $params); 986 987 //now move the old grades to the grade_grades table 988 foreach ($oldgrades as $oldgrade) { 989 unset($oldgrade->id); 990 991 $grade = new grade_grade($oldgrade, false);//2nd arg false as dont want to try and retrieve a record from the DB 992 $grade->insert($oldgrade->source); 993 994 //dont include default empty grades created when activities are created 995 if (!is_null($oldgrade->finalgrade) || !is_null($oldgrade->feedback)) { 996 $recoveredgrades = true; 997 } 998 } 999 } 1000 1001 //Some activities require manual grade synching (moving grades from the activity into the gradebook) 1002 //If the student was deleted when synching was done they may have grades in the activity that haven't been moved across 1003 grade_grab_course_grades($courseid, null, $userid); 1004 1005 return $recoveredgrades; 1006 } 1007 1008 /** 1009 * Updates all final grades in course. 1010 * 1011 * @param int $courseid The course ID 1012 * @param int $userid If specified try to do a quick regrading of the grades of this user only 1013 * @param object $updated_item Optional grade item to be marked for regrading 1014 * @return bool true if ok, array of errors if problems found. Grade item id => error message 1015 */ 1016 function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null) { 1017 1018 $course_item = grade_item::fetch_course_item($courseid); 1019 1020 if ($userid) { 1021 // one raw grade updated for one user 1022 if (empty($updated_item)) { 1023 print_error("cannotbenull", 'debug', '', "updated_item"); 1024 } 1025 if ($course_item->needsupdate) { 1026 $updated_item->force_regrading(); 1027 return array($course_item->id =>'Can not do fast regrading after updating of raw grades'); 1028 } 1029 1030 } else { 1031 if (!$course_item->needsupdate) { 1032 // nothing to do :-) 1033 return true; 1034 } 1035 } 1036 1037 // Categories might have to run some processing before we fetch the grade items. 1038 // This gives them a final opportunity to update and mark their children to be updated. 1039 // We need to work on the children categories up to the parent ones, so that, for instance, 1040 // if a category total is updated it will be reflected in the parent category. 1041 $cats = grade_category::fetch_all(array('courseid' => $courseid)); 1042 $flatcattree = array(); 1043 foreach ($cats as $cat) { 1044 if (!isset($flatcattree[$cat->depth])) { 1045 $flatcattree[$cat->depth] = array(); 1046 } 1047 $flatcattree[$cat->depth][] = $cat; 1048 } 1049 krsort($flatcattree); 1050 foreach ($flatcattree as $depth => $cats) { 1051 foreach ($cats as $cat) { 1052 $cat->pre_regrade_final_grades(); 1053 } 1054 } 1055 1056 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid)); 1057 $depends_on = array(); 1058 1059 // first mark all category and calculated items as needing regrading 1060 // this is slower, but 100% accurate 1061 foreach ($grade_items as $gid=>$gitem) { 1062 if (!empty($updated_item) and $updated_item->id == $gid) { 1063 $grade_items[$gid]->needsupdate = 1; 1064 1065 } else if ($gitem->is_course_item() or $gitem->is_category_item() or $gitem->is_calculated()) { 1066 $grade_items[$gid]->needsupdate = 1; 1067 } 1068 1069 // construct depends_on lookup array 1070 $depends_on[$gid] = $grade_items[$gid]->depends_on(); 1071 } 1072 1073 $errors = array(); 1074 $finalids = array(); 1075 $gids = array_keys($grade_items); 1076 $failed = 0; 1077 1078 while (count($finalids) < count($gids)) { // work until all grades are final or error found 1079 $count = 0; 1080 foreach ($gids as $gid) { 1081 if (in_array($gid, $finalids)) { 1082 continue; // already final 1083 } 1084 1085 if (!$grade_items[$gid]->needsupdate) { 1086 $finalids[] = $gid; // we can make it final - does not need update 1087 continue; 1088 } 1089 1090 $doupdate = true; 1091 foreach ($depends_on[$gid] as $did) { 1092 if (!in_array($did, $finalids)) { 1093 $doupdate = false; 1094 continue; // this item depends on something that is not yet in finals array 1095 } 1096 } 1097 1098 //oki - let's update, calculate or aggregate :-) 1099 if ($doupdate) { 1100 $result = $grade_items[$gid]->regrade_final_grades($userid); 1101 1102 if ($result === true) { 1103 $grade_items[$gid]->regrading_finished(); 1104 $grade_items[$gid]->check_locktime(); // do the locktime item locking 1105 $count++; 1106 $finalids[] = $gid; 1107 1108 } else { 1109 $grade_items[$gid]->force_regrading(); 1110 $errors[$gid] = $result; 1111 } 1112 } 1113 } 1114 1115 if ($count == 0) { 1116 $failed++; 1117 } else { 1118 $failed = 0; 1119 } 1120 1121 if ($failed > 1) { 1122 foreach($gids as $gid) { 1123 if (in_array($gid, $finalids)) { 1124 continue; // this one is ok 1125 } 1126 $grade_items[$gid]->force_regrading(); 1127 $errors[$grade_items[$gid]->id] = get_string('errorcalculationbroken', 'grades'); 1128 } 1129 break; // Found error. 1130 } 1131 } 1132 1133 if (count($errors) == 0) { 1134 if (empty($userid)) { 1135 // do the locktime locking of grades, but only when doing full regrading 1136 grade_grade::check_locktime_all($gids); 1137 } 1138 return true; 1139 } else { 1140 return $errors; 1141 } 1142 } 1143 1144 /** 1145 * Refetches grade data from course activities 1146 * 1147 * @param int $courseid The course ID 1148 * @param string $modname Limit the grade fetch to a single module type. For example 'forum' 1149 * @param int $userid limit the grade fetch to a single user 1150 */ 1151 function grade_grab_course_grades($courseid, $modname=null, $userid=0) { 1152 global $CFG, $DB; 1153 1154 if ($modname) { 1155 $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname 1156 FROM {".$modname."} a, {course_modules} cm, {modules} m 1157 WHERE m.name=:modname AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid"; 1158 $params = array('modname'=>$modname, 'courseid'=>$courseid); 1159 1160 if ($modinstances = $DB->get_records_sql($sql, $params)) { 1161 foreach ($modinstances as $modinstance) { 1162 grade_update_mod_grades($modinstance, $userid); 1163 } 1164 } 1165 return; 1166 } 1167 1168 if (!$mods = core_component::get_plugin_list('mod') ) { 1169 print_error('nomodules', 'debug'); 1170 } 1171 1172 foreach ($mods as $mod => $fullmod) { 1173 if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it 1174 continue; 1175 } 1176 1177 // include the module lib once 1178 if (file_exists($fullmod.'/lib.php')) { 1179 // get all instance of the activity 1180 $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname 1181 FROM {".$mod."} a, {course_modules} cm, {modules} m 1182 WHERE m.name=:mod AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid"; 1183 $params = array('mod'=>$mod, 'courseid'=>$courseid); 1184 1185 if ($modinstances = $DB->get_records_sql($sql, $params)) { 1186 foreach ($modinstances as $modinstance) { 1187 grade_update_mod_grades($modinstance, $userid); 1188 } 1189 } 1190 } 1191 } 1192 } 1193 1194 /** 1195 * Force full update of module grades in central gradebook 1196 * 1197 * @param object $modinstance Module object with extra cmidnumber and modname property 1198 * @param int $userid Optional user ID if limiting the update to a single user 1199 * @return bool True if success 1200 */ 1201 function grade_update_mod_grades($modinstance, $userid=0) { 1202 global $CFG, $DB; 1203 1204 $fullmod = $CFG->dirroot.'/mod/'.$modinstance->modname; 1205 if (!file_exists($fullmod.'/lib.php')) { 1206 debugging('missing lib.php file in module ' . $modinstance->modname); 1207 return false; 1208 } 1209 include_once($fullmod.'/lib.php'); 1210 1211 $updateitemfunc = $modinstance->modname.'_grade_item_update'; 1212 $updategradesfunc = $modinstance->modname.'_update_grades'; 1213 1214 if (function_exists($updategradesfunc) and function_exists($updateitemfunc)) { 1215 //new grading supported, force updating of grades 1216 $updateitemfunc($modinstance); 1217 $updategradesfunc($modinstance, $userid); 1218 1219 } else { 1220 // Module does not support grading? 1221 } 1222 1223 return true; 1224 } 1225 1226 /** 1227 * Remove grade letters for given context 1228 * 1229 * @param context $context The context 1230 * @param bool $showfeedback If true a success notification will be displayed 1231 */ 1232 function remove_grade_letters($context, $showfeedback) { 1233 global $DB, $OUTPUT; 1234 1235 $strdeleted = get_string('deleted'); 1236 1237 $DB->delete_records('grade_letters', array('contextid'=>$context->id)); 1238 if ($showfeedback) { 1239 echo $OUTPUT->notification($strdeleted.' - '.get_string('letters', 'grades'), 'notifysuccess'); 1240 } 1241 } 1242 1243 /** 1244 * Remove all grade related course data 1245 * Grade history is kept 1246 * 1247 * @param int $courseid The course ID 1248 * @param bool $showfeedback If true success notifications will be displayed 1249 */ 1250 function remove_course_grades($courseid, $showfeedback) { 1251 global $DB, $OUTPUT; 1252 1253 $fs = get_file_storage(); 1254 $strdeleted = get_string('deleted'); 1255 1256 $course_category = grade_category::fetch_course_category($courseid); 1257 $course_category->delete('coursedelete'); 1258 $fs->delete_area_files(context_course::instance($courseid)->id, 'grade', 'feedback'); 1259 if ($showfeedback) { 1260 echo $OUTPUT->notification($strdeleted.' - '.get_string('grades', 'grades').', '.get_string('items', 'grades').', '.get_string('categories', 'grades'), 'notifysuccess'); 1261 } 1262 1263 if ($outcomes = grade_outcome::fetch_all(array('courseid'=>$courseid))) { 1264 foreach ($outcomes as $outcome) { 1265 $outcome->delete('coursedelete'); 1266 } 1267 } 1268 $DB->delete_records('grade_outcomes_courses', array('courseid'=>$courseid)); 1269 if ($showfeedback) { 1270 echo $OUTPUT->notification($strdeleted.' - '.get_string('outcomes', 'grades'), 'notifysuccess'); 1271 } 1272 1273 if ($scales = grade_scale::fetch_all(array('courseid'=>$courseid))) { 1274 foreach ($scales as $scale) { 1275 $scale->delete('coursedelete'); 1276 } 1277 } 1278 if ($showfeedback) { 1279 echo $OUTPUT->notification($strdeleted.' - '.get_string('scales'), 'notifysuccess'); 1280 } 1281 1282 $DB->delete_records('grade_settings', array('courseid'=>$courseid)); 1283 if ($showfeedback) { 1284 echo $OUTPUT->notification($strdeleted.' - '.get_string('settings', 'grades'), 'notifysuccess'); 1285 } 1286 } 1287 1288 /** 1289 * Called when course category is deleted 1290 * Cleans the gradebook of associated data 1291 * 1292 * @param int $categoryid The course category id 1293 * @param int $newparentid If empty everything is deleted. Otherwise the ID of the category where content moved 1294 * @param bool $showfeedback print feedback 1295 */ 1296 function grade_course_category_delete($categoryid, $newparentid, $showfeedback) { 1297 global $DB; 1298 1299 $context = context_coursecat::instance($categoryid); 1300 $DB->delete_records('grade_letters', array('contextid'=>$context->id)); 1301 } 1302 1303 /** 1304 * Does gradebook cleanup when a module is uninstalled 1305 * Deletes all associated grade items 1306 * 1307 * @param string $modname The grade item module name to remove. For example 'forum' 1308 */ 1309 function grade_uninstalled_module($modname) { 1310 global $CFG, $DB; 1311 1312 $sql = "SELECT * 1313 FROM {grade_items} 1314 WHERE itemtype='mod' AND itemmodule=?"; 1315 1316 // go all items for this module and delete them including the grades 1317 $rs = $DB->get_recordset_sql($sql, array($modname)); 1318 foreach ($rs as $item) { 1319 $grade_item = new grade_item($item, false); 1320 $grade_item->delete('moduninstall'); 1321 } 1322 $rs->close(); 1323 } 1324 1325 /** 1326 * Deletes all of a user's grade data from gradebook 1327 * 1328 * @param int $userid The user whose grade data should be deleted 1329 */ 1330 function grade_user_delete($userid) { 1331 if ($grades = grade_grade::fetch_all(array('userid'=>$userid))) { 1332 foreach ($grades as $grade) { 1333 $grade->delete('userdelete'); 1334 } 1335 } 1336 } 1337 1338 /** 1339 * Purge course data when user unenrolls from a course 1340 * 1341 * @param int $courseid The ID of the course the user has unenrolled from 1342 * @param int $userid The ID of the user unenrolling 1343 */ 1344 function grade_user_unenrol($courseid, $userid) { 1345 if ($items = grade_item::fetch_all(array('courseid'=>$courseid))) { 1346 foreach ($items as $item) { 1347 if ($grades = grade_grade::fetch_all(array('userid'=>$userid, 'itemid'=>$item->id))) { 1348 foreach ($grades as $grade) { 1349 $grade->delete('userdelete'); 1350 } 1351 } 1352 } 1353 } 1354 } 1355 1356 /** 1357 * Grading cron job. Performs background clean up on the gradebook 1358 */ 1359 function grade_cron() { 1360 global $CFG, $DB; 1361 1362 $now = time(); 1363 1364 $sql = "SELECT i.* 1365 FROM {grade_items} i 1366 WHERE i.locked = 0 AND i.locktime > 0 AND i.locktime < ? AND EXISTS ( 1367 SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)"; 1368 1369 // go through all courses that have proper final grades and lock them if needed 1370 $rs = $DB->get_recordset_sql($sql, array($now)); 1371 foreach ($rs as $item) { 1372 $grade_item = new grade_item($item, false); 1373 $grade_item->locked = $now; 1374 $grade_item->update('locktime'); 1375 } 1376 $rs->close(); 1377 1378 $grade_inst = new grade_grade(); 1379 $fields = 'g.'.implode(',g.', $grade_inst->required_fields); 1380 1381 $sql = "SELECT $fields 1382 FROM {grade_grades} g, {grade_items} i 1383 WHERE g.locked = 0 AND g.locktime > 0 AND g.locktime < ? AND g.itemid=i.id AND EXISTS ( 1384 SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)"; 1385 1386 // go through all courses that have proper final grades and lock them if needed 1387 $rs = $DB->get_recordset_sql($sql, array($now)); 1388 foreach ($rs as $grade) { 1389 $grade_grade = new grade_grade($grade, false); 1390 $grade_grade->locked = $now; 1391 $grade_grade->update('locktime'); 1392 } 1393 $rs->close(); 1394 1395 //TODO: do not run this cleanup every cron invocation 1396 // cleanup history tables 1397 if (!empty($CFG->gradehistorylifetime)) { // value in days 1398 $histlifetime = $now - ($CFG->gradehistorylifetime * 3600 * 24); 1399 $tables = array('grade_outcomes_history', 'grade_categories_history', 'grade_items_history', 'grade_grades_history', 'scale_history'); 1400 foreach ($tables as $table) { 1401 if ($DB->delete_records_select($table, "timemodified < ?", array($histlifetime))) { 1402 mtrace(" Deleted old grade history records from '$table'"); 1403 } 1404 } 1405 } 1406 } 1407 1408 /** 1409 * Reset all course grades, refetch from the activities and recalculate 1410 * 1411 * @param int $courseid The course to reset 1412 * @return bool success 1413 */ 1414 function grade_course_reset($courseid) { 1415 1416 // no recalculations 1417 grade_force_full_regrading($courseid); 1418 1419 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid)); 1420 foreach ($grade_items as $gid=>$grade_item) { 1421 $grade_item->delete_all_grades('reset'); 1422 } 1423 1424 //refetch all grades 1425 grade_grab_course_grades($courseid); 1426 1427 // recalculate all grades 1428 grade_regrade_final_grades($courseid); 1429 return true; 1430 } 1431 1432 /** 1433 * Convert a number to 5 decimal point float, an empty string or a null db compatible format 1434 * (we need this to decide if db value changed) 1435 * 1436 * @param mixed $number The number to convert 1437 * @return mixed float or null 1438 */ 1439 function grade_floatval($number) { 1440 if (is_null($number) or $number === '') { 1441 return null; 1442 } 1443 // we must round to 5 digits to get the same precision as in 10,5 db fields 1444 // note: db rounding for 10,5 is different from php round() function 1445 return round($number, 5); 1446 } 1447 1448 /** 1449 * Compare two float numbers safely. Uses 5 decimals php precision using {@link grade_floatval()}. Nulls accepted too. 1450 * Used for determining if a database update is required 1451 * 1452 * @param float $f1 Float one to compare 1453 * @param float $f2 Float two to compare 1454 * @return bool True if the supplied values are different 1455 */ 1456 function grade_floats_different($f1, $f2) { 1457 // note: db rounding for 10,5 is different from php round() function 1458 return (grade_floatval($f1) !== grade_floatval($f2)); 1459 } 1460 1461 /** 1462 * Compare two float numbers safely. Uses 5 decimals php precision using {@link grade_floatval()} 1463 * 1464 * Do not use rounding for 10,5 at the database level as the results may be 1465 * different from php round() function. 1466 * 1467 * @since Moodle 2.0 1468 * @param float $f1 Float one to compare 1469 * @param float $f2 Float two to compare 1470 * @return bool True if the values should be considered as the same grades 1471 */ 1472 function grade_floats_equal($f1, $f2) { 1473 return (grade_floatval($f1) === grade_floatval($f2)); 1474 }
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 |