[ Index ] |
PHP Cross Reference of Unnamed Project |
[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 * Return true if the course needs regrading. 330 * 331 * @param int $courseid The course ID 332 * @return bool true if course grades need updating. 333 */ 334 function grade_needs_regrade_final_grades($courseid) { 335 $course_item = grade_item::fetch_course_item($courseid); 336 return $course_item->needsupdate; 337 } 338 339 /** 340 * Return true if the regrade process is likely to be time consuming and 341 * will therefore require the progress bar. 342 * 343 * @param int $courseid The course ID 344 * @return bool Whether the regrade process is likely to be time consuming 345 */ 346 function grade_needs_regrade_progress_bar($courseid) { 347 global $DB; 348 $grade_items = grade_item::fetch_all(array('courseid' => $courseid)); 349 350 list($sql, $params) = $DB->get_in_or_equal(array_keys($grade_items), SQL_PARAMS_NAMED, 'gi'); 351 $gradecount = $DB->count_records_select('grade_grades', 'itemid ' . $sql, $params); 352 353 // This figure may seem arbitrary, but after analysis it seems that 100 grade_grades can be calculated in ~= 0.5 seconds. 354 // Any longer than this and we want to show the progress bar. 355 return $gradecount > 100; 356 } 357 358 /** 359 * Check whether regarding of final grades is required and, if so, perform the regrade. 360 * 361 * If the regrade is expected to be time consuming (see grade_needs_regrade_progress_bar), then this 362 * function will output the progress bar, and redirect to the current PAGE->url after regrading 363 * completes. Otherwise the regrading will happen immediately and the page will be loaded as per 364 * normal. 365 * 366 * A callback may be specified, which is called if regrading has taken place. 367 * The callback may optionally return a URL which will be redirected to when the progress bar is present. 368 * 369 * @param stdClass $course The course to regrade 370 * @param callable $callback A function to call if regrading took place 371 * @return moodle_url The URL to redirect to if redirecting 372 */ 373 function grade_regrade_final_grades_if_required($course, callable $callback = null) { 374 global $PAGE, $OUTPUT; 375 376 if (!grade_needs_regrade_final_grades($course->id)) { 377 return false; 378 } 379 380 if (grade_needs_regrade_progress_bar($course->id)) { 381 $PAGE->set_heading($course->fullname); 382 echo $OUTPUT->header(); 383 echo $OUTPUT->heading(get_string('recalculatinggrades', 'grades')); 384 $progress = new \core\progress\display(true); 385 grade_regrade_final_grades($course->id, null, null, $progress); 386 387 if ($callback) { 388 // 389 $url = call_user_func($callback); 390 } 391 392 if (empty($url)) { 393 $url = $PAGE->url; 394 } 395 396 echo $OUTPUT->continue_button($url); 397 echo $OUTPUT->footer(); 398 die(); 399 } else { 400 $result = grade_regrade_final_grades($course->id); 401 if ($callback) { 402 call_user_func($callback); 403 } 404 return $result; 405 } 406 } 407 408 /** 409 * Returns grading information for given activity, optionally with user grades 410 * Manual, course or category items can not be queried. 411 * 412 * @category grade 413 * @param int $courseid ID of course 414 * @param string $itemtype Type of grade item. For example, 'mod' or 'block' 415 * @param string $itemmodule More specific then $itemtype. For example, 'forum' or 'quiz'. May be NULL for some item types 416 * @param int $iteminstance ID of the item module 417 * @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 418 * @return array Array of grade information objects (scaleid, name, grade and locked status, etc.) indexed with itemnumbers 419 */ 420 function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) { 421 global $CFG; 422 423 $return = new stdClass(); 424 $return->items = array(); 425 $return->outcomes = array(); 426 427 $course_item = grade_item::fetch_course_item($courseid); 428 $needsupdate = array(); 429 if ($course_item->needsupdate) { 430 $result = grade_regrade_final_grades($courseid); 431 if ($result !== true) { 432 $needsupdate = array_keys($result); 433 } 434 } 435 436 if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) { 437 foreach ($grade_items as $grade_item) { 438 $decimalpoints = null; 439 440 if (empty($grade_item->outcomeid)) { 441 // prepare information about grade item 442 $item = new stdClass(); 443 $item->id = $grade_item->id; 444 $item->itemnumber = $grade_item->itemnumber; 445 $item->itemtype = $grade_item->itemtype; 446 $item->itemmodule = $grade_item->itemmodule; 447 $item->iteminstance = $grade_item->iteminstance; 448 $item->scaleid = $grade_item->scaleid; 449 $item->name = $grade_item->get_name(); 450 $item->grademin = $grade_item->grademin; 451 $item->grademax = $grade_item->grademax; 452 $item->gradepass = $grade_item->gradepass; 453 $item->locked = $grade_item->is_locked(); 454 $item->hidden = $grade_item->is_hidden(); 455 $item->grades = array(); 456 457 switch ($grade_item->gradetype) { 458 case GRADE_TYPE_NONE: 459 continue; 460 461 case GRADE_TYPE_VALUE: 462 $item->scaleid = 0; 463 break; 464 465 case GRADE_TYPE_TEXT: 466 $item->scaleid = 0; 467 $item->grademin = 0; 468 $item->grademax = 0; 469 $item->gradepass = 0; 470 break; 471 } 472 473 if (empty($userid_or_ids)) { 474 $userids = array(); 475 476 } else if (is_array($userid_or_ids)) { 477 $userids = $userid_or_ids; 478 479 } else { 480 $userids = array($userid_or_ids); 481 } 482 483 if ($userids) { 484 $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true); 485 foreach ($userids as $userid) { 486 $grade_grades[$userid]->grade_item =& $grade_item; 487 488 $grade = new stdClass(); 489 $grade->grade = $grade_grades[$userid]->finalgrade; 490 $grade->locked = $grade_grades[$userid]->is_locked(); 491 $grade->hidden = $grade_grades[$userid]->is_hidden(); 492 $grade->overridden = $grade_grades[$userid]->overridden; 493 $grade->feedback = $grade_grades[$userid]->feedback; 494 $grade->feedbackformat = $grade_grades[$userid]->feedbackformat; 495 $grade->usermodified = $grade_grades[$userid]->usermodified; 496 $grade->datesubmitted = $grade_grades[$userid]->get_datesubmitted(); 497 $grade->dategraded = $grade_grades[$userid]->get_dategraded(); 498 499 // create text representation of grade 500 if ($grade_item->gradetype == GRADE_TYPE_TEXT or $grade_item->gradetype == GRADE_TYPE_NONE) { 501 $grade->grade = null; 502 $grade->str_grade = '-'; 503 $grade->str_long_grade = $grade->str_grade; 504 505 } else if (in_array($grade_item->id, $needsupdate)) { 506 $grade->grade = false; 507 $grade->str_grade = get_string('error'); 508 $grade->str_long_grade = $grade->str_grade; 509 510 } else if (is_null($grade->grade)) { 511 $grade->str_grade = '-'; 512 $grade->str_long_grade = $grade->str_grade; 513 514 } else { 515 $grade->str_grade = grade_format_gradevalue($grade->grade, $grade_item); 516 if ($grade_item->gradetype == GRADE_TYPE_SCALE or $grade_item->get_displaytype() != GRADE_DISPLAY_TYPE_REAL) { 517 $grade->str_long_grade = $grade->str_grade; 518 } else { 519 $a = new stdClass(); 520 $a->grade = $grade->str_grade; 521 $a->max = grade_format_gradevalue($grade_item->grademax, $grade_item); 522 $grade->str_long_grade = get_string('gradelong', 'grades', $a); 523 } 524 } 525 526 // create html representation of feedback 527 if (is_null($grade->feedback)) { 528 $grade->str_feedback = ''; 529 } else { 530 $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat); 531 } 532 533 $item->grades[$userid] = $grade; 534 } 535 } 536 $return->items[$grade_item->itemnumber] = $item; 537 538 } else { 539 if (!$grade_outcome = grade_outcome::fetch(array('id'=>$grade_item->outcomeid))) { 540 debugging('Incorect outcomeid found'); 541 continue; 542 } 543 544 // outcome info 545 $outcome = new stdClass(); 546 $outcome->id = $grade_item->id; 547 $outcome->itemnumber = $grade_item->itemnumber; 548 $outcome->itemtype = $grade_item->itemtype; 549 $outcome->itemmodule = $grade_item->itemmodule; 550 $outcome->iteminstance = $grade_item->iteminstance; 551 $outcome->scaleid = $grade_outcome->scaleid; 552 $outcome->name = $grade_outcome->get_name(); 553 $outcome->locked = $grade_item->is_locked(); 554 $outcome->hidden = $grade_item->is_hidden(); 555 556 if (empty($userid_or_ids)) { 557 $userids = array(); 558 } else if (is_array($userid_or_ids)) { 559 $userids = $userid_or_ids; 560 } else { 561 $userids = array($userid_or_ids); 562 } 563 564 if ($userids) { 565 $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true); 566 foreach ($userids as $userid) { 567 $grade_grades[$userid]->grade_item =& $grade_item; 568 569 $grade = new stdClass(); 570 $grade->grade = $grade_grades[$userid]->finalgrade; 571 $grade->locked = $grade_grades[$userid]->is_locked(); 572 $grade->hidden = $grade_grades[$userid]->is_hidden(); 573 $grade->feedback = $grade_grades[$userid]->feedback; 574 $grade->feedbackformat = $grade_grades[$userid]->feedbackformat; 575 $grade->usermodified = $grade_grades[$userid]->usermodified; 576 577 // create text representation of grade 578 if (in_array($grade_item->id, $needsupdate)) { 579 $grade->grade = false; 580 $grade->str_grade = get_string('error'); 581 582 } else if (is_null($grade->grade)) { 583 $grade->grade = 0; 584 $grade->str_grade = get_string('nooutcome', 'grades'); 585 586 } else { 587 $grade->grade = (int)$grade->grade; 588 $scale = $grade_item->load_scale(); 589 $grade->str_grade = format_string($scale->scale_items[(int)$grade->grade-1]); 590 } 591 592 // create html representation of feedback 593 if (is_null($grade->feedback)) { 594 $grade->str_feedback = ''; 595 } else { 596 $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat); 597 } 598 599 $outcome->grades[$userid] = $grade; 600 } 601 } 602 603 if (isset($return->outcomes[$grade_item->itemnumber])) { 604 // itemnumber duplicates - lets fix them! 605 $newnumber = $grade_item->itemnumber + 1; 606 while(grade_item::fetch(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid, 'itemnumber'=>$newnumber))) { 607 $newnumber++; 608 } 609 $outcome->itemnumber = $newnumber; 610 $grade_item->itemnumber = $newnumber; 611 $grade_item->update('system'); 612 } 613 614 $return->outcomes[$grade_item->itemnumber] = $outcome; 615 616 } 617 } 618 } 619 620 // sort results using itemnumbers 621 ksort($return->items, SORT_NUMERIC); 622 ksort($return->outcomes, SORT_NUMERIC); 623 624 return $return; 625 } 626 627 /////////////////////////////////////////////////////////////////// 628 ///// End of public API for communication with modules/blocks ///// 629 /////////////////////////////////////////////////////////////////// 630 631 632 633 /////////////////////////////////////////////////////////////////// 634 ///// Internal API: used by gradebook plugins and Moodle core ///// 635 /////////////////////////////////////////////////////////////////// 636 637 /** 638 * Returns a course gradebook setting 639 * 640 * @param int $courseid 641 * @param string $name of setting, maybe null if reset only 642 * @param string $default value to return if setting is not found 643 * @param bool $resetcache force reset of internal static cache 644 * @return string value of the setting, $default if setting not found, NULL if supplied $name is null 645 */ 646 function grade_get_setting($courseid, $name, $default=null, $resetcache=false) { 647 global $DB; 648 649 static $cache = array(); 650 651 if ($resetcache or !array_key_exists($courseid, $cache)) { 652 $cache[$courseid] = array(); 653 654 } else if (is_null($name)) { 655 return null; 656 657 } else if (array_key_exists($name, $cache[$courseid])) { 658 return $cache[$courseid][$name]; 659 } 660 661 if (!$data = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) { 662 $result = null; 663 } else { 664 $result = $data->value; 665 } 666 667 if (is_null($result)) { 668 $result = $default; 669 } 670 671 $cache[$courseid][$name] = $result; 672 return $result; 673 } 674 675 /** 676 * Returns all course gradebook settings as object properties 677 * 678 * @param int $courseid 679 * @return object 680 */ 681 function grade_get_settings($courseid) { 682 global $DB; 683 684 $settings = new stdClass(); 685 $settings->id = $courseid; 686 687 if ($records = $DB->get_records('grade_settings', array('courseid'=>$courseid))) { 688 foreach ($records as $record) { 689 $settings->{$record->name} = $record->value; 690 } 691 } 692 693 return $settings; 694 } 695 696 /** 697 * Add, update or delete a course gradebook setting 698 * 699 * @param int $courseid The course ID 700 * @param string $name Name of the setting 701 * @param string $value Value of the setting. NULL means delete the setting. 702 */ 703 function grade_set_setting($courseid, $name, $value) { 704 global $DB; 705 706 if (is_null($value)) { 707 $DB->delete_records('grade_settings', array('courseid'=>$courseid, 'name'=>$name)); 708 709 } else if (!$existing = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) { 710 $data = new stdClass(); 711 $data->courseid = $courseid; 712 $data->name = $name; 713 $data->value = $value; 714 $DB->insert_record('grade_settings', $data); 715 716 } else { 717 $data = new stdClass(); 718 $data->id = $existing->id; 719 $data->value = $value; 720 $DB->update_record('grade_settings', $data); 721 } 722 723 grade_get_setting($courseid, null, null, true); // reset the cache 724 } 725 726 /** 727 * Returns string representation of grade value 728 * 729 * @param float $value The grade value 730 * @param object $grade_item Grade item object passed by reference to prevent scale reloading 731 * @param bool $localized use localised decimal separator 732 * @param int $displaytype type of display. For example GRADE_DISPLAY_TYPE_REAL, GRADE_DISPLAY_TYPE_PERCENTAGE, GRADE_DISPLAY_TYPE_LETTER 733 * @param int $decimals The number of decimal places when displaying float values 734 * @return string 735 */ 736 function grade_format_gradevalue($value, &$grade_item, $localized=true, $displaytype=null, $decimals=null) { 737 if ($grade_item->gradetype == GRADE_TYPE_NONE or $grade_item->gradetype == GRADE_TYPE_TEXT) { 738 return ''; 739 } 740 741 // no grade yet? 742 if (is_null($value)) { 743 return '-'; 744 } 745 746 if ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) { 747 //unknown type?? 748 return ''; 749 } 750 751 if (is_null($displaytype)) { 752 $displaytype = $grade_item->get_displaytype(); 753 } 754 755 if (is_null($decimals)) { 756 $decimals = $grade_item->get_decimals(); 757 } 758 759 switch ($displaytype) { 760 case GRADE_DISPLAY_TYPE_REAL: 761 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized); 762 763 case GRADE_DISPLAY_TYPE_PERCENTAGE: 764 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized); 765 766 case GRADE_DISPLAY_TYPE_LETTER: 767 return grade_format_gradevalue_letter($value, $grade_item); 768 769 case GRADE_DISPLAY_TYPE_REAL_PERCENTAGE: 770 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' . 771 grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')'; 772 773 case GRADE_DISPLAY_TYPE_REAL_LETTER: 774 return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' . 775 grade_format_gradevalue_letter($value, $grade_item) . ')'; 776 777 case GRADE_DISPLAY_TYPE_PERCENTAGE_REAL: 778 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' . 779 grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')'; 780 781 case GRADE_DISPLAY_TYPE_LETTER_REAL: 782 return grade_format_gradevalue_letter($value, $grade_item) . ' (' . 783 grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')'; 784 785 case GRADE_DISPLAY_TYPE_LETTER_PERCENTAGE: 786 return grade_format_gradevalue_letter($value, $grade_item) . ' (' . 787 grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')'; 788 789 case GRADE_DISPLAY_TYPE_PERCENTAGE_LETTER: 790 return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' . 791 grade_format_gradevalue_letter($value, $grade_item) . ')'; 792 default: 793 return ''; 794 } 795 } 796 797 /** 798 * Returns a float representation of a grade value 799 * 800 * @param float $value The grade value 801 * @param object $grade_item Grade item object 802 * @param int $decimals The number of decimal places 803 * @param bool $localized use localised decimal separator 804 * @return string 805 */ 806 function grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) { 807 if ($grade_item->gradetype == GRADE_TYPE_SCALE) { 808 if (!$scale = $grade_item->load_scale()) { 809 return get_string('error'); 810 } 811 812 $value = $grade_item->bounded_grade($value); 813 return format_string($scale->scale_items[$value-1]); 814 815 } else { 816 return format_float($value, $decimals, $localized); 817 } 818 } 819 820 /** 821 * Returns a percentage representation of a grade value 822 * 823 * @param float $value The grade value 824 * @param object $grade_item Grade item object 825 * @param int $decimals The number of decimal places 826 * @param bool $localized use localised decimal separator 827 * @return string 828 */ 829 function grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) { 830 $min = $grade_item->grademin; 831 $max = $grade_item->grademax; 832 if ($min == $max) { 833 return ''; 834 } 835 $value = $grade_item->bounded_grade($value); 836 $percentage = (($value-$min)*100)/($max-$min); 837 return format_float($percentage, $decimals, $localized).' %'; 838 } 839 840 /** 841 * Returns a letter grade representation of a grade value 842 * The array of grade letters used is produced by {@link grade_get_letters()} using the course context 843 * 844 * @param float $value The grade value 845 * @param object $grade_item Grade item object 846 * @return string 847 */ 848 function grade_format_gradevalue_letter($value, $grade_item) { 849 global $CFG; 850 $context = context_course::instance($grade_item->courseid, IGNORE_MISSING); 851 if (!$letters = grade_get_letters($context)) { 852 return ''; // no letters?? 853 } 854 855 if (is_null($value)) { 856 return '-'; 857 } 858 859 $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100); 860 $value = bounded_number(0, $value, 100); // just in case 861 862 $gradebookcalculationsfreeze = 'gradebook_calculations_freeze_' . $grade_item->courseid; 863 864 foreach ($letters as $boundary => $letter) { 865 if (property_exists($CFG, $gradebookcalculationsfreeze) && (int)$CFG->{$gradebookcalculationsfreeze} <= 20160518) { 866 // Do nothing. 867 } else { 868 // The boundary is a percentage out of 100 so use 0 as the min and 100 as the max. 869 $boundary = grade_grade::standardise_score($boundary, 0, 100, 0, 100); 870 } 871 if ($value >= $boundary) { 872 return format_string($letter); 873 } 874 } 875 return '-'; // no match? maybe '' would be more correct 876 } 877 878 879 /** 880 * Returns grade options for gradebook grade category menu 881 * 882 * @param int $courseid The course ID 883 * @param bool $includenew Include option for new category at array index -1 884 * @return array of grade categories in course 885 */ 886 function grade_get_categories_menu($courseid, $includenew=false) { 887 $result = array(); 888 if (!$categories = grade_category::fetch_all(array('courseid'=>$courseid))) { 889 //make sure course category exists 890 if (!grade_category::fetch_course_category($courseid)) { 891 debugging('Can not create course grade category!'); 892 return $result; 893 } 894 $categories = grade_category::fetch_all(array('courseid'=>$courseid)); 895 } 896 foreach ($categories as $key=>$category) { 897 if ($category->is_course_category()) { 898 $result[$category->id] = get_string('uncategorised', 'grades'); 899 unset($categories[$key]); 900 } 901 } 902 if ($includenew) { 903 $result[-1] = get_string('newcategory', 'grades'); 904 } 905 $cats = array(); 906 foreach ($categories as $category) { 907 $cats[$category->id] = $category->get_name(); 908 } 909 core_collator::asort($cats); 910 911 return ($result+$cats); 912 } 913 914 /** 915 * Returns the array of grade letters to be used in the supplied context 916 * 917 * @param object $context Context object or null for defaults 918 * @return array of grade_boundary (minimum) => letter_string 919 */ 920 function grade_get_letters($context=null) { 921 global $DB; 922 923 if (empty($context)) { 924 //default grading letters 925 return array('93'=>'A', '90'=>'A-', '87'=>'B+', '83'=>'B', '80'=>'B-', '77'=>'C+', '73'=>'C', '70'=>'C-', '67'=>'D+', '60'=>'D', '0'=>'F'); 926 } 927 928 static $cache = array(); 929 930 if (array_key_exists($context->id, $cache)) { 931 return $cache[$context->id]; 932 } 933 934 if (count($cache) > 100) { 935 $cache = array(); // cache size limit 936 } 937 938 $letters = array(); 939 940 $contexts = $context->get_parent_context_ids(); 941 array_unshift($contexts, $context->id); 942 943 foreach ($contexts as $ctxid) { 944 if ($records = $DB->get_records('grade_letters', array('contextid'=>$ctxid), 'lowerboundary DESC')) { 945 foreach ($records as $record) { 946 $letters[$record->lowerboundary] = $record->letter; 947 } 948 } 949 950 if (!empty($letters)) { 951 $cache[$context->id] = $letters; 952 return $letters; 953 } 954 } 955 956 $letters = grade_get_letters(null); 957 $cache[$context->id] = $letters; 958 return $letters; 959 } 960 961 962 /** 963 * Verify new value of grade item idnumber. Checks for uniqueness of new ID numbers. Old ID numbers are kept intact. 964 * 965 * @param string $idnumber string (with magic quotes) 966 * @param int $courseid ID numbers are course unique only 967 * @param grade_item $grade_item The grade item this idnumber is associated with 968 * @param stdClass $cm used for course module idnumbers and items attached to modules 969 * @return bool true means idnumber ok 970 */ 971 function grade_verify_idnumber($idnumber, $courseid, $grade_item=null, $cm=null) { 972 global $DB; 973 974 if ($idnumber == '') { 975 //we allow empty idnumbers 976 return true; 977 } 978 979 // keep existing even when not unique 980 if ($cm and $cm->idnumber == $idnumber) { 981 if ($grade_item and $grade_item->itemnumber != 0) { 982 // grade item with itemnumber > 0 can't have the same idnumber as the main 983 // itemnumber 0 which is synced with course_modules 984 return false; 985 } 986 return true; 987 } else if ($grade_item and $grade_item->idnumber == $idnumber) { 988 return true; 989 } 990 991 if ($DB->record_exists('course_modules', array('course'=>$courseid, 'idnumber'=>$idnumber))) { 992 return false; 993 } 994 995 if ($DB->record_exists('grade_items', array('courseid'=>$courseid, 'idnumber'=>$idnumber))) { 996 return false; 997 } 998 999 return true; 1000 } 1001 1002 /** 1003 * Force final grade recalculation in all course items 1004 * 1005 * @param int $courseid The course ID to recalculate 1006 */ 1007 function grade_force_full_regrading($courseid) { 1008 global $DB; 1009 $DB->set_field('grade_items', 'needsupdate', 1, array('courseid'=>$courseid)); 1010 } 1011 1012 /** 1013 * Forces regrading of all site grades. Used when changing site setings 1014 */ 1015 function grade_force_site_regrading() { 1016 global $CFG, $DB; 1017 $DB->set_field('grade_items', 'needsupdate', 1); 1018 } 1019 1020 /** 1021 * Recover a user's grades from grade_grades_history 1022 * @param int $userid the user ID whose grades we want to recover 1023 * @param int $courseid the relevant course 1024 * @return bool true if successful or false if there was an error or no grades could be recovered 1025 */ 1026 function grade_recover_history_grades($userid, $courseid) { 1027 global $CFG, $DB; 1028 1029 if ($CFG->disablegradehistory) { 1030 debugging('Attempting to recover grades when grade history is disabled.'); 1031 return false; 1032 } 1033 1034 //Were grades recovered? Flag to return. 1035 $recoveredgrades = false; 1036 1037 //Check the user is enrolled in this course 1038 //Dont bother checking if they have a gradeable role. They may get one later so recover 1039 //whatever grades they have now just in case. 1040 $course_context = context_course::instance($courseid); 1041 if (!is_enrolled($course_context, $userid)) { 1042 debugging('Attempting to recover the grades of a user who is deleted or not enrolled. Skipping recover.'); 1043 return false; 1044 } 1045 1046 //Check for existing grades for this user in this course 1047 //Recovering grades when the user already has grades can lead to duplicate indexes and bad data 1048 //In the future we could move the existing grades to the history table then recover the grades from before then 1049 $sql = "SELECT gg.id 1050 FROM {grade_grades} gg 1051 JOIN {grade_items} gi ON gi.id = gg.itemid 1052 WHERE gi.courseid = :courseid AND gg.userid = :userid"; 1053 $params = array('userid' => $userid, 'courseid' => $courseid); 1054 if ($DB->record_exists_sql($sql, $params)) { 1055 debugging('Attempting to recover the grades of a user who already has grades. Skipping recover.'); 1056 return false; 1057 } else { 1058 //Retrieve the user's old grades 1059 //have history ID as first column to guarantee we a unique first column 1060 $sql = "SELECT h.id, gi.itemtype, gi.itemmodule, gi.iteminstance as iteminstance, gi.itemnumber, h.source, h.itemid, h.userid, h.rawgrade, h.rawgrademax, 1061 h.rawgrademin, h.rawscaleid, h.usermodified, h.finalgrade, h.hidden, h.locked, h.locktime, h.exported, h.overridden, h.excluded, h.feedback, 1062 h.feedbackformat, h.information, h.informationformat, h.timemodified, itemcreated.tm AS timecreated 1063 FROM {grade_grades_history} h 1064 JOIN (SELECT itemid, MAX(id) AS id 1065 FROM {grade_grades_history} 1066 WHERE userid = :userid1 1067 GROUP BY itemid) maxquery ON h.id = maxquery.id AND h.itemid = maxquery.itemid 1068 JOIN {grade_items} gi ON gi.id = h.itemid 1069 JOIN (SELECT itemid, MAX(timemodified) AS tm 1070 FROM {grade_grades_history} 1071 WHERE userid = :userid2 AND action = :insertaction 1072 GROUP BY itemid) itemcreated ON itemcreated.itemid = h.itemid 1073 WHERE gi.courseid = :courseid"; 1074 $params = array('userid1' => $userid, 'userid2' => $userid , 'insertaction' => GRADE_HISTORY_INSERT, 'courseid' => $courseid); 1075 $oldgrades = $DB->get_records_sql($sql, $params); 1076 1077 //now move the old grades to the grade_grades table 1078 foreach ($oldgrades as $oldgrade) { 1079 unset($oldgrade->id); 1080 1081 $grade = new grade_grade($oldgrade, false);//2nd arg false as dont want to try and retrieve a record from the DB 1082 $grade->insert($oldgrade->source); 1083 1084 //dont include default empty grades created when activities are created 1085 if (!is_null($oldgrade->finalgrade) || !is_null($oldgrade->feedback)) { 1086 $recoveredgrades = true; 1087 } 1088 } 1089 } 1090 1091 //Some activities require manual grade synching (moving grades from the activity into the gradebook) 1092 //If the student was deleted when synching was done they may have grades in the activity that haven't been moved across 1093 grade_grab_course_grades($courseid, null, $userid); 1094 1095 return $recoveredgrades; 1096 } 1097 1098 /** 1099 * Updates all final grades in course. 1100 * 1101 * @param int $courseid The course ID 1102 * @param int $userid If specified try to do a quick regrading of the grades of this user only 1103 * @param object $updated_item Optional grade item to be marked for regrading 1104 * @param \core\progress\base $progress If provided, will be used to update progress on this long operation. 1105 * @return bool true if ok, array of errors if problems found. Grade item id => error message 1106 */ 1107 function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null, $progress=null) { 1108 // This may take a very long time. 1109 \core_php_time_limit::raise(); 1110 1111 $course_item = grade_item::fetch_course_item($courseid); 1112 1113 if ($progress == null) { 1114 $progress = new \core\progress\none(); 1115 } 1116 1117 if ($userid) { 1118 // one raw grade updated for one user 1119 if (empty($updated_item)) { 1120 print_error("cannotbenull", 'debug', '', "updated_item"); 1121 } 1122 if ($course_item->needsupdate) { 1123 $updated_item->force_regrading(); 1124 return array($course_item->id =>'Can not do fast regrading after updating of raw grades'); 1125 } 1126 1127 } else { 1128 if (!$course_item->needsupdate) { 1129 // nothing to do :-) 1130 return true; 1131 } 1132 } 1133 1134 // Categories might have to run some processing before we fetch the grade items. 1135 // This gives them a final opportunity to update and mark their children to be updated. 1136 // We need to work on the children categories up to the parent ones, so that, for instance, 1137 // if a category total is updated it will be reflected in the parent category. 1138 $cats = grade_category::fetch_all(array('courseid' => $courseid)); 1139 $flatcattree = array(); 1140 foreach ($cats as $cat) { 1141 if (!isset($flatcattree[$cat->depth])) { 1142 $flatcattree[$cat->depth] = array(); 1143 } 1144 $flatcattree[$cat->depth][] = $cat; 1145 } 1146 krsort($flatcattree); 1147 foreach ($flatcattree as $depth => $cats) { 1148 foreach ($cats as $cat) { 1149 $cat->pre_regrade_final_grades(); 1150 } 1151 } 1152 1153 $progresstotal = 0; 1154 $progresscurrent = 0; 1155 1156 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid)); 1157 $depends_on = array(); 1158 1159 foreach ($grade_items as $gid=>$gitem) { 1160 if ((!empty($updated_item) and $updated_item->id == $gid) || 1161 $gitem->is_course_item() || $gitem->is_category_item() || $gitem->is_calculated()) { 1162 $grade_items[$gid]->needsupdate = 1; 1163 } 1164 1165 // We load all dependencies of these items later we can discard some grade_items based on this. 1166 if ($grade_items[$gid]->needsupdate) { 1167 $depends_on[$gid] = $grade_items[$gid]->depends_on(); 1168 $progresstotal++; 1169 } 1170 } 1171 1172 $progress->start_progress('regrade_course', $progresstotal); 1173 1174 $errors = array(); 1175 $finalids = array(); 1176 $updatedids = array(); 1177 $gids = array_keys($grade_items); 1178 $failed = 0; 1179 1180 while (count($finalids) < count($gids)) { // work until all grades are final or error found 1181 $count = 0; 1182 foreach ($gids as $gid) { 1183 if (in_array($gid, $finalids)) { 1184 continue; // already final 1185 } 1186 1187 if (!$grade_items[$gid]->needsupdate) { 1188 $finalids[] = $gid; // we can make it final - does not need update 1189 continue; 1190 } 1191 $thisprogress = $progresstotal; 1192 foreach ($grade_items as $item) { 1193 if ($item->needsupdate) { 1194 $thisprogress--; 1195 } 1196 } 1197 // Clip between $progresscurrent and $progresstotal. 1198 $thisprogress = max(min($thisprogress, $progresstotal), $progresscurrent); 1199 $progress->progress($thisprogress); 1200 $progresscurrent = $thisprogress; 1201 1202 foreach ($depends_on[$gid] as $did) { 1203 if (!in_array($did, $finalids)) { 1204 // This item depends on something that is not yet in finals array. 1205 continue 2; 1206 } 1207 } 1208 1209 // If this grade item has no dependancy with any updated item at all, then remove it from being recalculated. 1210 1211 // When we get here, all of this grade item's decendents are marked as final so they would be marked as updated too 1212 // if they would have been regraded. We don't need to regrade items which dependants (not only the direct ones 1213 // but any dependant in the cascade) have not been updated. 1214 1215 // If $updated_item was specified we discard the grade items that do not depend on it or on any grade item that 1216 // depend on $updated_item. 1217 1218 // Here we check to see if the direct decendants are marked as updated. 1219 if (!empty($updated_item) && $gid != $updated_item->id && !in_array($updated_item->id, $depends_on[$gid])) { 1220 1221 // We need to ensure that none of this item's dependencies have been updated. 1222 // If we find that one of the direct decendants of this grade item is marked as updated then this 1223 // grade item needs to be recalculated and marked as updated. 1224 // Being marked as updated is done further down in the code. 1225 1226 $updateddependencies = false; 1227 foreach ($depends_on[$gid] as $dependency) { 1228 if (in_array($dependency, $updatedids)) { 1229 $updateddependencies = true; 1230 break; 1231 } 1232 } 1233 if ($updateddependencies === false) { 1234 // If no direct descendants are marked as updated, then we don't need to update this grade item. We then mark it 1235 // as final. 1236 1237 $finalids[] = $gid; 1238 continue; 1239 } 1240 } 1241 1242 // Let's update, calculate or aggregate. 1243 $result = $grade_items[$gid]->regrade_final_grades($userid); 1244 1245 if ($result === true) { 1246 1247 // We should only update the database if we regraded all users. 1248 if (empty($userid)) { 1249 $grade_items[$gid]->regrading_finished(); 1250 // Do the locktime item locking. 1251 $grade_items[$gid]->check_locktime(); 1252 } else { 1253 $grade_items[$gid]->needsupdate = 0; 1254 } 1255 $count++; 1256 $finalids[] = $gid; 1257 $updatedids[] = $gid; 1258 1259 } else { 1260 $grade_items[$gid]->force_regrading(); 1261 $errors[$gid] = $result; 1262 } 1263 } 1264 1265 if ($count == 0) { 1266 $failed++; 1267 } else { 1268 $failed = 0; 1269 } 1270 1271 if ($failed > 1) { 1272 foreach($gids as $gid) { 1273 if (in_array($gid, $finalids)) { 1274 continue; // this one is ok 1275 } 1276 $grade_items[$gid]->force_regrading(); 1277 $errors[$grade_items[$gid]->id] = get_string('errorcalculationbroken', 'grades'); 1278 } 1279 break; // Found error. 1280 } 1281 } 1282 $progress->end_progress(); 1283 1284 if (count($errors) == 0) { 1285 if (empty($userid)) { 1286 // do the locktime locking of grades, but only when doing full regrading 1287 grade_grade::check_locktime_all($gids); 1288 } 1289 return true; 1290 } else { 1291 return $errors; 1292 } 1293 } 1294 1295 /** 1296 * Refetches grade data from course activities 1297 * 1298 * @param int $courseid The course ID 1299 * @param string $modname Limit the grade fetch to a single module type. For example 'forum' 1300 * @param int $userid limit the grade fetch to a single user 1301 */ 1302 function grade_grab_course_grades($courseid, $modname=null, $userid=0) { 1303 global $CFG, $DB; 1304 1305 if ($modname) { 1306 $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname 1307 FROM {".$modname."} a, {course_modules} cm, {modules} m 1308 WHERE m.name=:modname AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid"; 1309 $params = array('modname'=>$modname, 'courseid'=>$courseid); 1310 1311 if ($modinstances = $DB->get_records_sql($sql, $params)) { 1312 foreach ($modinstances as $modinstance) { 1313 grade_update_mod_grades($modinstance, $userid); 1314 } 1315 } 1316 return; 1317 } 1318 1319 if (!$mods = core_component::get_plugin_list('mod') ) { 1320 print_error('nomodules', 'debug'); 1321 } 1322 1323 foreach ($mods as $mod => $fullmod) { 1324 if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it 1325 continue; 1326 } 1327 1328 // include the module lib once 1329 if (file_exists($fullmod.'/lib.php')) { 1330 // get all instance of the activity 1331 $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname 1332 FROM {".$mod."} a, {course_modules} cm, {modules} m 1333 WHERE m.name=:mod AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid"; 1334 $params = array('mod'=>$mod, 'courseid'=>$courseid); 1335 1336 if ($modinstances = $DB->get_records_sql($sql, $params)) { 1337 foreach ($modinstances as $modinstance) { 1338 grade_update_mod_grades($modinstance, $userid); 1339 } 1340 } 1341 } 1342 } 1343 } 1344 1345 /** 1346 * Force full update of module grades in central gradebook 1347 * 1348 * @param object $modinstance Module object with extra cmidnumber and modname property 1349 * @param int $userid Optional user ID if limiting the update to a single user 1350 * @return bool True if success 1351 */ 1352 function grade_update_mod_grades($modinstance, $userid=0) { 1353 global $CFG, $DB; 1354 1355 $fullmod = $CFG->dirroot.'/mod/'.$modinstance->modname; 1356 if (!file_exists($fullmod.'/lib.php')) { 1357 debugging('missing lib.php file in module ' . $modinstance->modname); 1358 return false; 1359 } 1360 include_once($fullmod.'/lib.php'); 1361 1362 $updateitemfunc = $modinstance->modname.'_grade_item_update'; 1363 $updategradesfunc = $modinstance->modname.'_update_grades'; 1364 1365 if (function_exists($updategradesfunc) and function_exists($updateitemfunc)) { 1366 //new grading supported, force updating of grades 1367 $updateitemfunc($modinstance); 1368 $updategradesfunc($modinstance, $userid); 1369 1370 } else { 1371 // Module does not support grading? 1372 } 1373 1374 return true; 1375 } 1376 1377 /** 1378 * Remove grade letters for given context 1379 * 1380 * @param context $context The context 1381 * @param bool $showfeedback If true a success notification will be displayed 1382 */ 1383 function remove_grade_letters($context, $showfeedback) { 1384 global $DB, $OUTPUT; 1385 1386 $strdeleted = get_string('deleted'); 1387 1388 $DB->delete_records('grade_letters', array('contextid'=>$context->id)); 1389 if ($showfeedback) { 1390 echo $OUTPUT->notification($strdeleted.' - '.get_string('letters', 'grades'), 'notifysuccess'); 1391 } 1392 } 1393 1394 /** 1395 * Remove all grade related course data 1396 * Grade history is kept 1397 * 1398 * @param int $courseid The course ID 1399 * @param bool $showfeedback If true success notifications will be displayed 1400 */ 1401 function remove_course_grades($courseid, $showfeedback) { 1402 global $DB, $OUTPUT; 1403 1404 $fs = get_file_storage(); 1405 $strdeleted = get_string('deleted'); 1406 1407 $course_category = grade_category::fetch_course_category($courseid); 1408 $course_category->delete('coursedelete'); 1409 $fs->delete_area_files(context_course::instance($courseid)->id, 'grade', 'feedback'); 1410 if ($showfeedback) { 1411 echo $OUTPUT->notification($strdeleted.' - '.get_string('grades', 'grades').', '.get_string('items', 'grades').', '.get_string('categories', 'grades'), 'notifysuccess'); 1412 } 1413 1414 if ($outcomes = grade_outcome::fetch_all(array('courseid'=>$courseid))) { 1415 foreach ($outcomes as $outcome) { 1416 $outcome->delete('coursedelete'); 1417 } 1418 } 1419 $DB->delete_records('grade_outcomes_courses', array('courseid'=>$courseid)); 1420 if ($showfeedback) { 1421 echo $OUTPUT->notification($strdeleted.' - '.get_string('outcomes', 'grades'), 'notifysuccess'); 1422 } 1423 1424 if ($scales = grade_scale::fetch_all(array('courseid'=>$courseid))) { 1425 foreach ($scales as $scale) { 1426 $scale->delete('coursedelete'); 1427 } 1428 } 1429 if ($showfeedback) { 1430 echo $OUTPUT->notification($strdeleted.' - '.get_string('scales'), 'notifysuccess'); 1431 } 1432 1433 $DB->delete_records('grade_settings', array('courseid'=>$courseid)); 1434 if ($showfeedback) { 1435 echo $OUTPUT->notification($strdeleted.' - '.get_string('settings', 'grades'), 'notifysuccess'); 1436 } 1437 } 1438 1439 /** 1440 * Called when course category is deleted 1441 * Cleans the gradebook of associated data 1442 * 1443 * @param int $categoryid The course category id 1444 * @param int $newparentid If empty everything is deleted. Otherwise the ID of the category where content moved 1445 * @param bool $showfeedback print feedback 1446 */ 1447 function grade_course_category_delete($categoryid, $newparentid, $showfeedback) { 1448 global $DB; 1449 1450 $context = context_coursecat::instance($categoryid); 1451 $DB->delete_records('grade_letters', array('contextid'=>$context->id)); 1452 } 1453 1454 /** 1455 * Does gradebook cleanup when a module is uninstalled 1456 * Deletes all associated grade items 1457 * 1458 * @param string $modname The grade item module name to remove. For example 'forum' 1459 */ 1460 function grade_uninstalled_module($modname) { 1461 global $CFG, $DB; 1462 1463 $sql = "SELECT * 1464 FROM {grade_items} 1465 WHERE itemtype='mod' AND itemmodule=?"; 1466 1467 // go all items for this module and delete them including the grades 1468 $rs = $DB->get_recordset_sql($sql, array($modname)); 1469 foreach ($rs as $item) { 1470 $grade_item = new grade_item($item, false); 1471 $grade_item->delete('moduninstall'); 1472 } 1473 $rs->close(); 1474 } 1475 1476 /** 1477 * Deletes all of a user's grade data from gradebook 1478 * 1479 * @param int $userid The user whose grade data should be deleted 1480 */ 1481 function grade_user_delete($userid) { 1482 if ($grades = grade_grade::fetch_all(array('userid'=>$userid))) { 1483 foreach ($grades as $grade) { 1484 $grade->delete('userdelete'); 1485 } 1486 } 1487 } 1488 1489 /** 1490 * Purge course data when user unenrolls from a course 1491 * 1492 * @param int $courseid The ID of the course the user has unenrolled from 1493 * @param int $userid The ID of the user unenrolling 1494 */ 1495 function grade_user_unenrol($courseid, $userid) { 1496 if ($items = grade_item::fetch_all(array('courseid'=>$courseid))) { 1497 foreach ($items as $item) { 1498 if ($grades = grade_grade::fetch_all(array('userid'=>$userid, 'itemid'=>$item->id))) { 1499 foreach ($grades as $grade) { 1500 $grade->delete('userdelete'); 1501 } 1502 } 1503 } 1504 } 1505 } 1506 1507 /** 1508 * Grading cron job. Performs background clean up on the gradebook 1509 */ 1510 function grade_cron() { 1511 global $CFG, $DB; 1512 1513 $now = time(); 1514 1515 $sql = "SELECT i.* 1516 FROM {grade_items} i 1517 WHERE i.locked = 0 AND i.locktime > 0 AND i.locktime < ? AND EXISTS ( 1518 SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)"; 1519 1520 // go through all courses that have proper final grades and lock them if needed 1521 $rs = $DB->get_recordset_sql($sql, array($now)); 1522 foreach ($rs as $item) { 1523 $grade_item = new grade_item($item, false); 1524 $grade_item->locked = $now; 1525 $grade_item->update('locktime'); 1526 } 1527 $rs->close(); 1528 1529 $grade_inst = new grade_grade(); 1530 $fields = 'g.'.implode(',g.', $grade_inst->required_fields); 1531 1532 $sql = "SELECT $fields 1533 FROM {grade_grades} g, {grade_items} i 1534 WHERE g.locked = 0 AND g.locktime > 0 AND g.locktime < ? AND g.itemid=i.id AND EXISTS ( 1535 SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)"; 1536 1537 // go through all courses that have proper final grades and lock them if needed 1538 $rs = $DB->get_recordset_sql($sql, array($now)); 1539 foreach ($rs as $grade) { 1540 $grade_grade = new grade_grade($grade, false); 1541 $grade_grade->locked = $now; 1542 $grade_grade->update('locktime'); 1543 } 1544 $rs->close(); 1545 1546 //TODO: do not run this cleanup every cron invocation 1547 // cleanup history tables 1548 if (!empty($CFG->gradehistorylifetime)) { // value in days 1549 $histlifetime = $now - ($CFG->gradehistorylifetime * 3600 * 24); 1550 $tables = array('grade_outcomes_history', 'grade_categories_history', 'grade_items_history', 'grade_grades_history', 'scale_history'); 1551 foreach ($tables as $table) { 1552 if ($DB->delete_records_select($table, "timemodified < ?", array($histlifetime))) { 1553 mtrace(" Deleted old grade history records from '$table'"); 1554 } 1555 } 1556 } 1557 } 1558 1559 /** 1560 * Reset all course grades, refetch from the activities and recalculate 1561 * 1562 * @param int $courseid The course to reset 1563 * @return bool success 1564 */ 1565 function grade_course_reset($courseid) { 1566 1567 // no recalculations 1568 grade_force_full_regrading($courseid); 1569 1570 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid)); 1571 foreach ($grade_items as $gid=>$grade_item) { 1572 $grade_item->delete_all_grades('reset'); 1573 } 1574 1575 //refetch all grades 1576 grade_grab_course_grades($courseid); 1577 1578 // recalculate all grades 1579 grade_regrade_final_grades($courseid); 1580 return true; 1581 } 1582 1583 /** 1584 * Convert a number to 5 decimal point float, an empty string or a null db compatible format 1585 * (we need this to decide if db value changed) 1586 * 1587 * @param mixed $number The number to convert 1588 * @return mixed float or null 1589 */ 1590 function grade_floatval($number) { 1591 if (is_null($number) or $number === '') { 1592 return null; 1593 } 1594 // we must round to 5 digits to get the same precision as in 10,5 db fields 1595 // note: db rounding for 10,5 is different from php round() function 1596 return round($number, 5); 1597 } 1598 1599 /** 1600 * Compare two float numbers safely. Uses 5 decimals php precision using {@link grade_floatval()}. Nulls accepted too. 1601 * Used for determining if a database update is required 1602 * 1603 * @param float $f1 Float one to compare 1604 * @param float $f2 Float two to compare 1605 * @return bool True if the supplied values are different 1606 */ 1607 function grade_floats_different($f1, $f2) { 1608 // note: db rounding for 10,5 is different from php round() function 1609 return (grade_floatval($f1) !== grade_floatval($f2)); 1610 } 1611 1612 /** 1613 * Compare two float numbers safely. Uses 5 decimals php precision using {@link grade_floatval()} 1614 * 1615 * Do not use rounding for 10,5 at the database level as the results may be 1616 * different from php round() function. 1617 * 1618 * @since Moodle 2.0 1619 * @param float $f1 Float one to compare 1620 * @param float $f2 Float two to compare 1621 * @return bool True if the values should be considered as the same grades 1622 */ 1623 function grade_floats_equal($f1, $f2) { 1624 return (grade_floatval($f1) === grade_floatval($f2)); 1625 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |