PageRenderTime 79ms CodeModel.GetById 4ms app.highlight 64ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/MoodleWebRole/lib/gradelib.php

#
PHP | 1440 lines | 936 code | 232 blank | 272 comment | 234 complexity | 9b5f63f77b233f841a5f56753a3f3e74 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php // $Id: gradelib.php,v 1.120.2.32 2009/11/13 22:09:03 mudrd8mz Exp $
   2
   3///////////////////////////////////////////////////////////////////////////
   4// NOTICE OF COPYRIGHT                                                   //
   5//                                                                       //
   6// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
   7//          http://moodle.org                                            //
   8//                                                                       //
   9// Copyright (C) 1999 onwards  Martin Dougiamas  http://moodle.com       //
  10//                                                                       //
  11// This program is free software; you can redistribute it and/or modify  //
  12// it under the terms of the GNU General Public License as published by  //
  13// the Free Software Foundation; either version 2 of the License, or     //
  14// (at your option) any later version.                                   //
  15//                                                                       //
  16// This program is distributed in the hope that it will be useful,       //
  17// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
  18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
  19// GNU General Public License for more details:                          //
  20//                                                                       //
  21//          http://www.gnu.org/copyleft/gpl.html                         //
  22//                                                                       //
  23///////////////////////////////////////////////////////////////////////////
  24
  25/**
  26 * Library of functions for gradebook - both public and internal
  27 *
  28 * @author Moodle HQ developers
  29 * @version  $Id: gradelib.php,v 1.120.2.32 2009/11/13 22:09:03 mudrd8mz Exp $
  30 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  31 * @package moodlecore
  32 */
  33
  34require_once($CFG->libdir . '/grade/constants.php');
  35
  36require_once($CFG->libdir . '/grade/grade_category.php');
  37require_once($CFG->libdir . '/grade/grade_item.php');
  38require_once($CFG->libdir . '/grade/grade_grade.php');
  39require_once($CFG->libdir . '/grade/grade_scale.php');
  40require_once($CFG->libdir . '/grade/grade_outcome.php');
  41
  42/////////////////////////////////////////////////////////////////////
  43///// Start of public API for communication with modules/blocks /////
  44/////////////////////////////////////////////////////////////////////
  45
  46/**
  47 * Submit new or update grade; update/create grade_item definition. Grade must have userid specified,
  48 * rawgrade and feedback with format are optional. rawgrade NULL means 'Not graded', missing property
  49 * or key means do not change existing.
  50 *
  51 * Only following grade item properties can be changed 'itemname', 'idnumber', 'gradetype', 'grademax',
  52 * 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted' and 'hidden'. 'reset' means delete all current grades including locked ones.
  53 *
  54 * Manual, course or category items can not be updated by this function.
  55 * @public
  56 * @param string $source source of the grade such as 'mod/assignment'
  57 * @param int $courseid id of course
  58 * @param string $itemtype type of grade item - mod, block
  59 * @param string $itemmodule more specific then $itemtype - assignment, forum, etc.; maybe NULL for some item types
  60 * @param int $iteminstance instance it of graded subject
  61 * @param int $itemnumber most probably 0, modules can use other numbers when having more than one grades for each user
  62 * @param mixed $grades grade (object, array) or several grades (arrays of arrays or objects), NULL if updating grade_item definition only
  63 * @param mixed $itemdetails object or array describing the grading item, NULL if no change
  64 */
  65function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, $itemnumber, $grades=NULL, $itemdetails=NULL) {
  66    global $USER, $CFG;
  67
  68    // only following grade_item properties can be changed in this function
  69    $allowed = array('itemname', 'idnumber', 'gradetype', 'grademax', 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted', 'hidden');
  70    // list of 10,5 numeric fields
  71    $floats  = array('grademin', 'grademax', 'multfactor', 'plusfactor');
  72
  73    // grade item identification
  74    $params = compact('courseid', 'itemtype', 'itemmodule', 'iteminstance', 'itemnumber');
  75
  76    if (is_null($courseid) or is_null($itemtype)) {
  77        debugging('Missing courseid or itemtype');
  78        return GRADE_UPDATE_FAILED;
  79    }
  80
  81    if (!$grade_items = grade_item::fetch_all($params)) {
  82        // create a new one
  83        $grade_item = false;
  84
  85    } else if (count($grade_items) == 1){
  86        $grade_item = reset($grade_items);
  87        unset($grade_items); //release memory
  88
  89    } else {
  90        debugging('Found more than one grade item');
  91        return GRADE_UPDATE_MULTIPLE;
  92    }
  93
  94    if (!empty($itemdetails['deleted'])) {
  95        if ($grade_item) {
  96            if ($grade_item->delete($source)) {
  97                return GRADE_UPDATE_OK;
  98            } else {
  99                return GRADE_UPDATE_FAILED;
 100            }
 101        }
 102        return GRADE_UPDATE_OK;
 103    }
 104
 105/// Create or update the grade_item if needed
 106
 107    if (!$grade_item) {
 108        if ($itemdetails) {
 109            $itemdetails = (array)$itemdetails;
 110
 111            // grademin and grademax ignored when scale specified
 112            if (array_key_exists('scaleid', $itemdetails)) {
 113                if ($itemdetails['scaleid']) {
 114                    unset($itemdetails['grademin']);
 115                    unset($itemdetails['grademax']);
 116                }
 117            }
 118
 119            foreach ($itemdetails as $k=>$v) {
 120                if (!in_array($k, $allowed)) {
 121                    // ignore it
 122                    continue;
 123                }
 124                if ($k == 'gradetype' and $v == GRADE_TYPE_NONE) {
 125                    // no grade item needed!
 126                    return GRADE_UPDATE_OK;
 127                }
 128                $params[$k] = $v;
 129            }
 130        }
 131        $grade_item = new grade_item($params);
 132        $grade_item->insert();
 133
 134    } else {
 135        if ($grade_item->is_locked()) {
 136            // no notice() here, test returned value instead!
 137            return GRADE_UPDATE_ITEM_LOCKED;
 138        }
 139
 140        if ($itemdetails) {
 141            $itemdetails = (array)$itemdetails;
 142            $update = false;
 143            foreach ($itemdetails as $k=>$v) {
 144                if (!in_array($k, $allowed)) {
 145                    // ignore it
 146                    continue;
 147                }
 148                if (in_array($k, $floats)) {
 149                    if (grade_floats_different($grade_item->{$k}, $v)) {
 150                        $grade_item->{$k} = $v;
 151                        $update = true;
 152                    }
 153
 154                } else {
 155                    if ($grade_item->{$k} != $v) {
 156                        $grade_item->{$k} = $v;
 157                        $update = true;
 158                    }
 159                }
 160            }
 161            if ($update) {
 162                $grade_item->update();
 163            }
 164        }
 165    }
 166
 167/// reset grades if requested
 168    if (!empty($itemdetails['reset'])) {
 169        $grade_item->delete_all_grades('reset');
 170        return GRADE_UPDATE_OK;
 171    }
 172
 173/// Some extra checks
 174    // do we use grading?
 175    if ($grade_item->gradetype == GRADE_TYPE_NONE) {
 176        return GRADE_UPDATE_OK;
 177    }
 178
 179    // no grade submitted
 180    if (empty($grades)) {
 181        return GRADE_UPDATE_OK;
 182    }
 183
 184/// Finally start processing of grades
 185    if (is_object($grades)) {
 186        $grades = array($grades->userid=>$grades);
 187    } else {
 188        if (array_key_exists('userid', $grades)) {
 189            $grades = array($grades['userid']=>$grades);
 190        }
 191    }
 192
 193/// normalize and verify grade array
 194    foreach($grades as $k=>$g) {
 195        if (!is_array($g)) {
 196            $g = (array)$g;
 197            $grades[$k] = $g;
 198        }
 199
 200        if (empty($g['userid']) or $k != $g['userid']) {
 201            debugging('Incorrect grade array index, must be user id! Grade ignored.');
 202            unset($grades[$k]);
 203        }
 204    }
 205
 206    if (empty($grades)) {
 207        return GRADE_UPDATE_FAILED;
 208    }
 209
 210    $count = count($grades);
 211    if ($count == 1) {
 212        reset($grades);
 213        $uid = key($grades);
 214        $sql = "SELECT * FROM {$CFG->prefix}grade_grades WHERE itemid = $grade_item->id AND userid = $uid";
 215
 216    } else if ($count < 200) {
 217        $uids = implode(',', array_keys($grades));
 218        $sql = "SELECT * FROM {$CFG->prefix}grade_grades WHERE itemid = $grade_item->id AND userid IN ($uids)";
 219
 220    } else {
 221        $sql = "SELECT * FROM {$CFG->prefix}grade_grades WHERE itemid = $grade_item->id";
 222    }
 223
 224    $rs = get_recordset_sql($sql);
 225
 226    $failed = false;
 227
 228    while (count($grades) > 0) {
 229        $grade_grade = null;
 230        $grade       = null;
 231
 232        while ($rs and !rs_EOF($rs)) {
 233            if (!$gd = rs_fetch_next_record($rs)) {
 234                break;
 235            }
 236            $userid = $gd->userid;
 237            if (!isset($grades[$userid])) {
 238                // this grade not requested, continue
 239                continue;
 240            }
 241            // existing grade requested
 242            $grade       = $grades[$userid];
 243            $grade_grade = new grade_grade($gd, false);
 244            unset($grades[$userid]);
 245            break;
 246        }
 247
 248        if (is_null($grade_grade)) {
 249            if (count($grades) == 0) {
 250                // no more grades to process
 251                break;
 252            }
 253
 254            $grade       = reset($grades);
 255            $userid      = $grade['userid'];
 256            $grade_grade = new grade_grade(array('itemid'=>$grade_item->id, 'userid'=>$userid), false);
 257            $grade_grade->load_optional_fields(); // add feedback and info too
 258            unset($grades[$userid]);
 259        }
 260
 261        $rawgrade       = false;
 262        $feedback       = false;
 263        $feedbackformat = FORMAT_MOODLE;
 264        $usermodified   = $USER->id;
 265        $datesubmitted  = null;
 266        $dategraded     = null;
 267
 268        if (array_key_exists('rawgrade', $grade)) {
 269            $rawgrade = $grade['rawgrade'];
 270        }
 271
 272        if (array_key_exists('feedback', $grade)) {
 273            $feedback = $grade['feedback'];
 274        }
 275
 276        if (array_key_exists('feedbackformat', $grade)) {
 277            $feedbackformat = $grade['feedbackformat'];
 278        }
 279
 280        if (array_key_exists('usermodified', $grade)) {
 281            $usermodified = $grade['usermodified'];
 282        }
 283
 284        if (array_key_exists('datesubmitted', $grade)) {
 285            $datesubmitted = $grade['datesubmitted'];
 286        }
 287
 288        if (array_key_exists('dategraded', $grade)) {
 289            $dategraded = $grade['dategraded'];
 290        }
 291
 292        // update or insert the grade
 293        if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified, $dategraded, $datesubmitted, $grade_grade)) {
 294            $failed = true;
 295        }
 296    }
 297
 298    if ($rs) {
 299        rs_close($rs);
 300    }
 301
 302    if (!$failed) {
 303        return GRADE_UPDATE_OK;
 304    } else {
 305        return GRADE_UPDATE_FAILED;
 306    }
 307}
 308
 309/**
 310 * Updates outcomes of user
 311 * Manual outcomes can not be updated.
 312 * @public
 313 * @param string $source source of the grade such as 'mod/assignment'
 314 * @param int $courseid id of course
 315 * @param string $itemtype 'mod', 'block'
 316 * @param string $itemmodule 'forum, 'quiz', etc.
 317 * @param int $iteminstance id of the item module
 318 * @param int $userid ID of the graded user
 319 * @param array $data array itemnumber=>outcomegrade
 320 */
 321function grade_update_outcomes($source, $courseid, $itemtype, $itemmodule, $iteminstance, $userid, $data) {
 322    if ($items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) {
 323        foreach ($items as $item) {
 324            if (!array_key_exists($item->itemnumber, $data)) {
 325                continue;
 326            }
 327            $grade = $data[$item->itemnumber] < 1 ? null : $data[$item->itemnumber];
 328            $item->update_final_grade($userid, $grade, $source);
 329        }
 330    }
 331}
 332
 333/**
 334 * Returns grading information for given activity - optionally with users grades
 335 * Manual, course or category items can not be queried.
 336 * @public
 337 * @param int $courseid id of course
 338 * @param string $itemtype 'mod', 'block'
 339 * @param string $itemmodule 'forum, 'quiz', etc.
 340 * @param int $iteminstance id of the item module
 341 * @param int $userid_or_ids optional id of the graded user or array of ids; if userid not used, returns only information about grade_item
 342 * @return array of grade information objects (scaleid, name, grade and locked status, etc.) indexed with itemnumbers
 343 */
 344function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) {
 345    global $CFG;
 346
 347    $return = new object();
 348    $return->items    = array();
 349    $return->outcomes = array();
 350
 351    $course_item = grade_item::fetch_course_item($courseid);
 352    $needsupdate = array();
 353    if ($course_item->needsupdate) {
 354        $result = grade_regrade_final_grades($courseid);
 355        if ($result !== true) {
 356            $needsupdate = array_keys($result);
 357        }
 358    }
 359
 360    if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) {
 361        foreach ($grade_items as $grade_item) {
 362            $decimalpoints = null;
 363
 364            if (empty($grade_item->outcomeid)) {
 365                // prepare information about grade item
 366                $item = new object();
 367                $item->itemnumber = $grade_item->itemnumber;
 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 object();
 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 object();
 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 object();
 466                $outcome->itemnumber = $grade_item->itemnumber;
 467                $outcome->scaleid    = $grade_outcome->scaleid;
 468                $outcome->name       = $grade_outcome->get_name();
 469                $outcome->locked     = $grade_item->is_locked();
 470                $outcome->hidden     = $grade_item->is_hidden();
 471
 472                if (empty($userid_or_ids)) {
 473                    $userids = array();
 474                } else if (is_array($userid_or_ids)) {
 475                    $userids = $userid_or_ids;
 476                } else {
 477                    $userids = array($userid_or_ids);
 478                }
 479
 480                if ($userids) {
 481                    $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true);
 482                    foreach ($userids as $userid) {
 483                        $grade_grades[$userid]->grade_item =& $grade_item;
 484
 485                        $grade = new object();
 486                        $grade->grade          = $grade_grades[$userid]->finalgrade;
 487                        $grade->locked         = $grade_grades[$userid]->is_locked();
 488                        $grade->hidden         = $grade_grades[$userid]->is_hidden();
 489                        $grade->feedback       = $grade_grades[$userid]->feedback;
 490                        $grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
 491                        $grade->usermodified   = $grade_grades[$userid]->usermodified;
 492
 493                        // create text representation of grade
 494                        if (in_array($grade_item->id, $needsupdate)) {
 495                            $grade->grade     = false;
 496                            $grade->str_grade = get_string('error');
 497
 498                        } else if (is_null($grade->grade)) {
 499                            $grade->grade = 0;
 500                            $grade->str_grade = get_string('nooutcome', 'grades');
 501
 502                        } else {
 503                            $grade->grade = (int)$grade->grade;
 504                            $scale = $grade_item->load_scale();
 505                            $grade->str_grade = format_string($scale->scale_items[(int)$grade->grade-1]);
 506                        }
 507
 508                        // create html representation of feedback
 509                        if (is_null($grade->feedback)) {
 510                            $grade->str_feedback = '';
 511                        } else {
 512                            $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat);
 513                        }
 514
 515                        $outcome->grades[$userid] = $grade;
 516                    }
 517                }
 518
 519                if (isset($return->outcomes[$grade_item->itemnumber])) {
 520                    // itemnumber duplicates - lets fix them!
 521                    $newnumber = $grade_item->itemnumber + 1;
 522                    while(grade_item::fetch(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid, 'itemnumber'=>$newnumber))) {
 523                        $newnumber++;
 524                    }
 525                    $outcome->itemnumber    = $newnumber;
 526                    $grade_item->itemnumber = $newnumber;
 527                    $grade_item->update('system');
 528                }
 529
 530                $return->outcomes[$grade_item->itemnumber] = $outcome;
 531
 532            }
 533        }
 534    }
 535
 536    // sort results using itemnumbers
 537    ksort($return->items, SORT_NUMERIC);
 538    ksort($return->outcomes, SORT_NUMERIC);
 539
 540    return $return;
 541}
 542
 543///////////////////////////////////////////////////////////////////
 544///// End of public API for communication with modules/blocks /////
 545///////////////////////////////////////////////////////////////////
 546
 547
 548
 549///////////////////////////////////////////////////////////////////
 550///// Internal API: used by gradebook plugins and Moodle core /////
 551///////////////////////////////////////////////////////////////////
 552
 553/**
 554 * Returns course gradebook setting
 555 * @param int $courseid
 556 * @param string $name of setting, maybe null if reset only
 557 * @param bool $resetcache force reset of internal static cache
 558 * @return string value, NULL if no setting
 559 */
 560function grade_get_setting($courseid, $name, $default=null, $resetcache=false) {
 561    static $cache = array();
 562
 563    if ($resetcache or !array_key_exists($courseid, $cache)) {
 564        $cache[$courseid] = array();
 565
 566    } else if (is_null($name)) {
 567        return null;
 568
 569    } else if (array_key_exists($name, $cache[$courseid])) {
 570        return $cache[$courseid][$name];
 571    }
 572
 573    if (!$data = get_record('grade_settings', 'courseid', $courseid, 'name', addslashes($name))) {
 574        $result = null;
 575    } else {
 576        $result = $data->value;
 577    }
 578
 579    if (is_null($result)) {
 580        $result = $default;
 581    }
 582
 583    $cache[$courseid][$name] = $result;
 584    return $result;
 585}
 586
 587/**
 588 * Returns all course gradebook settings as object properties
 589 * @param int $courseid
 590 * @return object
 591 */
 592function grade_get_settings($courseid) {
 593     $settings = new object();
 594     $settings->id = $courseid;
 595
 596    if ($records = get_records('grade_settings', 'courseid', $courseid)) {
 597        foreach ($records as $record) {
 598            $settings->{$record->name} = $record->value;
 599        }
 600    }
 601
 602    return $settings;
 603}
 604
 605/**
 606 * Add/update course gradebook setting
 607 * @param int $courseid
 608 * @param string $name of setting
 609 * @param string value, NULL means no setting==remove
 610 * @return void
 611 */
 612function grade_set_setting($courseid, $name, $value) {
 613    if (is_null($value)) {
 614        delete_records('grade_settings', 'courseid', $courseid, 'name', addslashes($name));
 615
 616    } else if (!$existing = get_record('grade_settings', 'courseid', $courseid, 'name', addslashes($name))) {
 617        $data = new object();
 618        $data->courseid = $courseid;
 619        $data->name     = addslashes($name);
 620        $data->value    = addslashes($value);
 621        insert_record('grade_settings', $data);
 622
 623    } else {
 624        $data = new object();
 625        $data->id       = $existing->id;
 626        $data->value    = addslashes($value);
 627        update_record('grade_settings', $data);
 628    }
 629
 630    grade_get_setting($courseid, null, null, true); // reset the cache
 631}
 632
 633/**
 634 * Returns string representation of grade value
 635 * @param float $value grade value
 636 * @param object $grade_item - by reference to prevent scale reloading
 637 * @param bool $localized use localised decimal separator
 638 * @param int $displaytype type of display - GRADE_DISPLAY_TYPE_REAL, GRADE_DISPLAY_TYPE_PERCENTAGE, GRADE_DISPLAY_TYPE_LETTER
 639 * @param int $decimalplaces number of decimal places when displaying float values
 640 * @return string
 641 */
 642function grade_format_gradevalue($value, &$grade_item, $localized=true, $displaytype=null, $decimals=null) {
 643    if ($grade_item->gradetype == GRADE_TYPE_NONE or $grade_item->gradetype == GRADE_TYPE_TEXT) {
 644        return '';
 645    }
 646
 647    // no grade yet?
 648    if (is_null($value)) {
 649        return '-';
 650    }
 651
 652    if ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) {
 653        //unknown type??
 654        return '';
 655    }
 656
 657    if (is_null($displaytype)) {
 658        $displaytype = $grade_item->get_displaytype();
 659    }
 660
 661    if (is_null($decimals)) {
 662        $decimals = $grade_item->get_decimals();
 663    }
 664
 665    switch ($displaytype) {
 666        case GRADE_DISPLAY_TYPE_REAL:
 667            return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized);
 668
 669        case GRADE_DISPLAY_TYPE_PERCENTAGE:
 670            return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized);
 671
 672        case GRADE_DISPLAY_TYPE_LETTER:
 673            return grade_format_gradevalue_letter($value, $grade_item);
 674
 675        case GRADE_DISPLAY_TYPE_REAL_PERCENTAGE:
 676            return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' .
 677                    grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')';
 678
 679        case GRADE_DISPLAY_TYPE_REAL_LETTER:
 680            return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' .
 681                    grade_format_gradevalue_letter($value, $grade_item) . ')';
 682
 683        case GRADE_DISPLAY_TYPE_PERCENTAGE_REAL:
 684            return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' .
 685                    grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')';
 686
 687        case GRADE_DISPLAY_TYPE_LETTER_REAL:
 688            return grade_format_gradevalue_letter($value, $grade_item) . ' (' .
 689                    grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')';
 690
 691        case GRADE_DISPLAY_TYPE_LETTER_PERCENTAGE:
 692            return grade_format_gradevalue_letter($value, $grade_item) . ' (' .
 693                    grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')';
 694
 695        case GRADE_DISPLAY_TYPE_PERCENTAGE_LETTER:
 696            return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' .
 697                    grade_format_gradevalue_letter($value, $grade_item) . ')';
 698        default:
 699            return '';
 700    }
 701}
 702
 703function grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) {
 704    if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
 705        if (!$scale = $grade_item->load_scale()) {
 706            return get_string('error');
 707        }
 708
 709        $value = $grade_item->bounded_grade($value);
 710        return format_string($scale->scale_items[$value-1]);
 711
 712    } else {
 713        return format_float($value, $decimals, $localized);
 714    }
 715}
 716
 717function grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) {
 718    $min = $grade_item->grademin;
 719    $max = $grade_item->grademax;
 720    if ($min == $max) {
 721        return '';
 722    }
 723    $value = $grade_item->bounded_grade($value);
 724    $percentage = (($value-$min)*100)/($max-$min);
 725    return format_float($percentage, $decimals, $localized).' %';
 726}
 727
 728function grade_format_gradevalue_letter($value, $grade_item) {
 729    $context = get_context_instance(CONTEXT_COURSE, $grade_item->courseid);
 730    if (!$letters = grade_get_letters($context)) {
 731        return ''; // no letters??
 732    }
 733
 734    if (is_null($value)) {
 735        return '-';
 736    }
 737
 738    $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100);
 739    $value = bounded_number(0, $value, 100); // just in case
 740    foreach ($letters as $boundary => $letter) {
 741        if ($value >= $boundary) {
 742            return format_string($letter);
 743        }
 744    }
 745    return '-'; // no match? maybe '' would be more correct
 746}
 747
 748
 749/**
 750 * Returns grade options for gradebook category menu
 751 * @param int $courseid
 752 * @param bool $includenew include option for new category (-1)
 753 * @return array of grade categories in course
 754 */
 755function grade_get_categories_menu($courseid, $includenew=false) {
 756    $result = array();
 757    if (!$categories = grade_category::fetch_all(array('courseid'=>$courseid))) {
 758        //make sure course category exists
 759        if (!grade_category::fetch_course_category($courseid)) {
 760            debugging('Can not create course grade category!');
 761            return $result;
 762        }
 763        $categories = grade_category::fetch_all(array('courseid'=>$courseid));
 764    }
 765    foreach ($categories as $key=>$category) {
 766        if ($category->is_course_category()) {
 767            $result[$category->id] = get_string('uncategorised', 'grades');
 768            unset($categories[$key]);
 769        }
 770    }
 771    if ($includenew) {
 772        $result[-1] = get_string('newcategory', 'grades');
 773    }
 774    $cats = array();
 775    foreach ($categories as $category) {
 776        $cats[$category->id] = $category->get_name();
 777    }
 778    asort($cats, SORT_LOCALE_STRING);
 779
 780    return ($result+$cats);
 781}
 782
 783/**
 784 * Returns grade letters array used in context
 785 * @param object $context object or null for defaults
 786 * @return array of grade_boundary=>letter_string
 787 */
 788function grade_get_letters($context=null) {
 789    if (empty($context)) {
 790        //default grading letters
 791        return array('93'=>'A', '90'=>'A-', '87'=>'B+', '83'=>'B', '80'=>'B-', '77'=>'C+', '73'=>'C', '70'=>'C-', '67'=>'D+', '60'=>'D', '0'=>'F');
 792    }
 793
 794    static $cache = array();
 795
 796    if (array_key_exists($context->id, $cache)) {
 797        return $cache[$context->id];
 798    }
 799
 800    if (count($cache) > 100) {
 801        $cache = array(); // cache size limit
 802    }
 803
 804    $letters = array();
 805
 806    $contexts = get_parent_contexts($context);
 807    array_unshift($contexts, $context->id);
 808
 809    foreach ($contexts as $ctxid) {
 810        if ($records = get_records('grade_letters', 'contextid', $ctxid, 'lowerboundary DESC')) {
 811            foreach ($records as $record) {
 812                $letters[$record->lowerboundary] = $record->letter;
 813            }
 814        }
 815
 816        if (!empty($letters)) {
 817            $cache[$context->id] = $letters;
 818            return $letters;
 819        }
 820    }
 821
 822    $letters = grade_get_letters(null);
 823    $cache[$context->id] = $letters;
 824    return $letters;
 825}
 826
 827
 828/**
 829 * Verify new value of idnumber - checks for uniqueness of new idnumbers, old are kept intact
 830 * @param string idnumber string (with magic quotes)
 831 * @param int $courseid - id numbers are course unique only
 832 * @param object $cm used for course module idnumbers and items attached to modules
 833 * @param object $gradeitem is item idnumber
 834 * @return boolean true means idnumber ok
 835 */
 836function grade_verify_idnumber($idnumber, $courseid, $grade_item=null, $cm=null) {
 837    if ($idnumber == '') {
 838        //we allow empty idnumbers
 839        return true;
 840    }
 841
 842    // keep existing even when not unique
 843    if ($cm and $cm->idnumber == $idnumber) {
 844        return true;
 845    } else if ($grade_item and $grade_item->idnumber == $idnumber) {
 846        return true;
 847    }
 848
 849    if (get_records_select('course_modules', "course = $courseid AND idnumber='$idnumber'")) {
 850        return false;
 851    }
 852
 853    if (get_records_select('grade_items', "courseid = $courseid AND idnumber='$idnumber'")) {
 854        return false;
 855    }
 856
 857    return true;
 858}
 859
 860/**
 861 * Force final grade recalculation in all course items
 862 * @param int $courseid
 863 */
 864function grade_force_full_regrading($courseid) {
 865    set_field('grade_items', 'needsupdate', 1, 'courseid', $courseid);
 866}
 867
 868/**
 869 * Forces regrading of all site grades - usualy when chanign site setings
 870 */
 871function grade_force_site_regrading() {
 872    global $CFG;
 873    $sql = "UPDATE {$CFG->prefix}grade_items SET needsupdate=1";
 874    execute_sql($sql, false);
 875}
 876
 877/**
 878 * Updates all final grades in course.
 879 *
 880 * @param int $courseid
 881 * @param int $userid if specified, try to do a quick regrading of grades of this user only
 882 * @param object $updated_item the item in which
 883 * @return boolean true if ok, array of errors if problems found (item id is used as key)
 884 */
 885function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null) {
 886
 887    $course_item = grade_item::fetch_course_item($courseid);
 888
 889    if ($userid) {
 890        // one raw grade updated for one user
 891        if (empty($updated_item)) {
 892            error("updated_item_id can not be null!");
 893        }
 894        if ($course_item->needsupdate) {
 895            $updated_item->force_regrading();
 896            return array($course_item->id =>'Can not do fast regrading after updating of raw grades');
 897        }
 898
 899    } else {
 900        if (!$course_item->needsupdate) {
 901            // nothing to do :-)
 902            return true;
 903        }
 904    }
 905
 906    $grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
 907    $depends_on = array();
 908
 909    // first mark all category and calculated items as needing regrading
 910    // this is slower, but 100% accurate
 911    foreach ($grade_items as $gid=>$gitem) {
 912        if (!empty($updated_item) and $updated_item->id == $gid) {
 913            $grade_items[$gid]->needsupdate = 1;
 914
 915        } else if ($gitem->is_course_item() or $gitem->is_category_item() or $gitem->is_calculated()) {
 916            $grade_items[$gid]->needsupdate = 1;
 917        }
 918
 919        // construct depends_on lookup array
 920        $depends_on[$gid] = $grade_items[$gid]->depends_on();
 921    }
 922
 923    $errors = array();
 924    $finalids = array();
 925    $gids     = array_keys($grade_items);
 926    $failed = 0;
 927
 928    while (count($finalids) < count($gids)) { // work until all grades are final or error found
 929        $count = 0;
 930        foreach ($gids as $gid) {
 931            if (in_array($gid, $finalids)) {
 932                continue; // already final
 933            }
 934
 935            if (!$grade_items[$gid]->needsupdate) {
 936                $finalids[] = $gid; // we can make it final - does not need update
 937                continue;
 938            }
 939
 940            $doupdate = true;
 941            foreach ($depends_on[$gid] as $did) {
 942                if (!in_array($did, $finalids)) {
 943                    $doupdate = false;
 944                    continue; // this item depends on something that is not yet in finals array
 945                }
 946            }
 947
 948            //oki - let's update, calculate or aggregate :-)
 949            if ($doupdate) {
 950                $result = $grade_items[$gid]->regrade_final_grades($userid);
 951
 952                if ($result === true) {
 953                    $grade_items[$gid]->regrading_finished();
 954                    $grade_items[$gid]->check_locktime(); // do the locktime item locking
 955                    $count++;
 956                    $finalids[] = $gid;
 957
 958                } else {
 959                    $grade_items[$gid]->force_regrading();
 960                    $errors[$gid] = $result;
 961                }
 962            }
 963        }
 964
 965        if ($count == 0) {
 966            $failed++;
 967        } else {
 968            $failed = 0;
 969        }
 970
 971        if ($failed > 1) {
 972            foreach($gids as $gid) {
 973                if (in_array($gid, $finalids)) {
 974                    continue; // this one is ok
 975                }
 976                $grade_items[$gid]->force_regrading();
 977                $errors[$grade_items[$gid]->id] = 'Probably circular reference or broken calculation formula'; // TODO: localize
 978            }
 979            break; // oki, found error
 980        }
 981    }
 982
 983    if (count($errors) == 0) {
 984        if (empty($userid)) {
 985            // do the locktime locking of grades, but only when doing full regrading
 986            grade_grade::check_locktime_all($gids);
 987        }
 988        return true;
 989    } else {
 990        return $errors;
 991    }
 992}
 993
 994/**
 995 * For backwards compatibility with old third-party modules, this function can
 996 * be used to import all grades from activities with legacy grading.
 997 * @param int $courseid
 998 */
 999function grade_grab_legacy_grades($courseid) {
1000    global $CFG;
1001
1002    if (!$mods = get_list_of_plugins('mod') ) {
1003        error('No modules installed!');
1004    }
1005
1006    foreach ($mods as $mod) {
1007        if ($mod == 'NEWMODULE') {   // Someone has unzipped the template, ignore it
1008            continue;
1009        }
1010
1011        $fullmod = $CFG->dirroot.'/mod/'.$mod;
1012
1013        // include the module lib once
1014        if (file_exists($fullmod.'/lib.php')) {
1015            include_once($fullmod.'/lib.php');
1016            // look for modname_grades() function - old gradebook pulling function
1017            // if present sync the grades with new grading system
1018            $gradefunc = $mod.'_grades';
1019            if (function_exists($gradefunc)) {
1020                grade_grab_course_grades($courseid, $mod);
1021            }
1022        }
1023    }
1024}
1025
1026/**
1027 * Refetches data from all course activities
1028 * @param int $courseid
1029 * @param string $modname
1030 * @return success
1031 */
1032function grade_grab_course_grades($courseid, $modname=null) {
1033    global $CFG;
1034
1035    if ($modname) {
1036        $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname
1037                  FROM {$CFG->prefix}$modname a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
1038                 WHERE m.name='$modname' AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=$courseid";
1039
1040        if ($modinstances = get_records_sql($sql)) {
1041            foreach ($modinstances as $modinstance) {
1042                grade_update_mod_grades($modinstance);
1043            }
1044        }
1045        return;
1046    }
1047
1048    if (!$mods = get_list_of_plugins('mod') ) {
1049        error('No modules installed!');
1050    }
1051
1052    foreach ($mods as $mod) {
1053        if ($mod == 'NEWMODULE') {   // Someone has unzipped the template, ignore it
1054            continue;
1055        }
1056
1057        $fullmod = $CFG->dirroot.'/mod/'.$mod;
1058
1059        // include the module lib once
1060        if (file_exists($fullmod.'/lib.php')) {
1061            // get all instance of the activity
1062            $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname
1063                      FROM {$CFG->prefix}$mod a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
1064                     WHERE m.name='$mod' AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=$courseid";
1065
1066            if ($modinstances = get_records_sql($sql)) {
1067                foreach ($modinstances as $modinstance) {
1068                    grade_update_mod_grades($modinstance);
1069                }
1070            }
1071        }
1072    }
1073}
1074
1075/**
1076 * Force full update of module grades in central gradebook - works for both legacy and converted activities.
1077 * @param object $modinstance object with extra cmidnumber and modname property
1078 * @return boolean success
1079 */
1080function grade_update_mod_grades($modinstance, $userid=0) {
1081    global $CFG;
1082
1083    $fullmod = $CFG->dirroot.'/mod/'.$modinstance->modname;
1084    if (!file_exists($fullmod.'/lib.php')) {
1085        debugging('missing lib.php file in module ' . $modinstance->modname);
1086        return false;
1087    }
1088    include_once($fullmod.'/lib.php');
1089
1090    // does it use legacy grading?
1091    $gradefunc        = $modinstance->modname.'_grades';
1092    $updategradesfunc = $modinstance->modname.'_update_grades';
1093    $updateitemfunc   = $modinstance->modname.'_grade_item_update';
1094
1095    if (function_exists($gradefunc)) {
1096
1097        // legacy module - not yet converted
1098        if ($oldgrades = $gradefunc($modinstance->id)) {
1099
1100            $grademax = $oldgrades->maxgrade;
1101            $scaleid = NULL;
1102            if (!is_numeric($grademax)) {
1103                // scale name is provided as a string, try to find it
1104                if (!$scale = get_record('scale', 'name', $grademax)) {
1105                    debugging('Incorrect scale name! name:'.$grademax);
1106                    return false;
1107                }
1108                $scaleid = $scale->id;
1109            }
1110
1111            if (!$grade_item = grade_get_legacy_grade_item($modinstance, $grademax, $scaleid)) {
1112                debugging('Can not get/create legacy grade item!');
1113                return false;
1114            }
1115
1116            if (!empty($oldgrades->grades)) {
1117                $grades = array();
1118
1119                foreach ($oldgrades->grades as $uid=>$usergrade) {
1120                    if ($userid and $uid != $userid) {
1121                        continue;
1122                    }
1123                    $grade = new object();
1124                    $grade->userid = $uid;
1125
1126                    if ($usergrade == '-') {
1127                        // no grade
1128                        $grade->rawgrade = null;
1129
1130                    } else if ($scaleid) {
1131                        // scale in use, words used
1132                        $gradescale = explode(",", $scale->scale);
1133                        $grade->rawgrade = array_search($usergrade, $gradescale) + 1;
1134
1135                    } else {
1136                        // good old numeric value
1137                        $grade->rawgrade = $usergrade;
1138                    }
1139                    $grades[$uid] = $grade;
1140                }
1141
1142                grade_update('legacygrab', $grade_item->courseid, $grade_item->itemtype, $grade_item->itemmodule,
1143                             $grade_item->iteminstance, $grade_item->itemnumber, $grades);
1144            }
1145        }
1146
1147    } else if (function_exists($updategradesfunc) and function_exists($updateitemfunc)) {
1148        //new grading supported, force updating of grades
1149        $updateitemfunc($modinstance);
1150        $updategradesfunc($modinstance, $userid);
1151
1152    } else {
1153        // mudule does not support grading??
1154    }
1155
1156    return true;
1157}
1158
1159/**
1160 * Returns list of currently used mods with legacy grading in course
1161 * @param $courseid int
1162 * @return array of modname=>modulenamestring mods with legacy grading
1163 */
1164function grade_get_legacy_modules($courseid) {
1165    global $CFG;
1166
1167    if (!$mods = get_course_mods($courseid)) {
1168        return array();
1169    }
1170    $legacy = array();
1171
1172    foreach ($mods as $mod) {
1173        $modname = $mod->modname;
1174
1175        $modlib = "$CFG->dirroot/mod/$modname/lib.php";
1176        if (!$modlib) {
1177            continue;
1178        }
1179        include_once($modlib);
1180        $gradefunc = $modname.'_grades';
1181        if (!function_exists($gradefunc)) {
1182            continue;
1183        }
1184        $legacy[$modname] = get_string('modulename', $modname);
1185    }
1186
1187    return $legacy;
1188}
1189
1190/**
1191 * Get and update/create grade item for legacy modules.
1192 */
1193function grade_get_legacy_grade_item($modinstance, $grademax, $scaleid) {
1194
1195    // does it already exist?
1196    if ($grade_items = grade_item::fetch_all(array('courseid'=>$modinstance->course, 'itemtype'=>'mod', 'itemmodule'=>$modinstance->modname, 'iteminstance'=>$modinstance->id, 'itemnumber'=>0))) {
1197        if (count($grade_items) > 1) {
1198            debugging('Multiple legacy grade_items found.');
1199            return false;
1200        }
1201
1202        $grade_item = reset($grade_items);
1203
1204        if (is_null($grademax) and is_null($scaleid)) {
1205           $grade_item->gradetype  = GRADE_TYPE_NONE;
1206
1207        } else if ($scaleid) {
1208            $grade_item->gradetype = GRADE_TYPE_SCALE;
1209            $grade_item->scaleid   = $scaleid;
1210            $grade_item->grademin  = 1;
1211
1212        } else {
1213            $grade_item->gradetype  = GRADE_TYPE_VALUE;
1214            $grade_item->grademax   = $grademax;
1215            $grade_item->grademin   = 0;
1216        }
1217
1218        $grade_item->itemname = $modinstance->name;
1219        $grade_item->idnumber = $modinstance->cmidnumber;
1220
1221        $grade_item->update();
1222
1223        return $grade_item;
1224    }
1225
1226    // create new one
1227    $params = array('courseid'    =>$modinstance->course,
1228                    'itemtype'    =>'mod',
1229                    'itemmodule'  =>$modinstance->modname,
1230                    'iteminstance'=>$modinstance->id,
1231                    'itemnumber'  =>0,
1232                    'itemname'    =>$modinstance->name,
1233                    'idnumber'    =>$modinstance->cmidnumber);
1234
1235    if (is_null($grademax) and is_null($scaleid)) {
1236        $params['gradetype'] = GRADE_TYPE_NONE;
1237
1238    } else if ($scaleid) {
1239        $params['gradetype'] = GRADE_TYPE_SCALE;
1240        $params['scaleid']   = $scaleid;
1241        $grade_item->grademin  = 1;
1242    } else {
1243        $params['gradetype'] = GRADE_TYPE_VALUE;
1244        $params['grademax']  = $grademax;
1245        $params['grademin']  = 0;
1246    }
1247
1248    $grade_item = new grade_item($params);
1249    $grade_item->insert();
1250
1251    return $grade_item;
1252}
1253
1254/**
1255 * Remove grade letters for given context
1256 * @param object $context
1257 */
1258function remove_grade_letters($context, $showfeedback) {
1259    $strdeleted = get_string('deleted');
1260
1261    delete_records('grade_letters', 'contextid', $context->id);
1262    if ($showfeedback) {
1263        notify($strdeleted.' - '.get_string('letters', 'grades'));
1264    }
1265}
1266/**
1267 * Remove all grade related course data - history is kept
1268 * @param int $courseid
1269 * @param bool $showfeedback print feedback
1270 */
1271function remove_course_grades($courseid, $showfeedback) {
1272    $strdeleted = get_string('deleted');
1273
1274    $course_category = grade_category::fetch_course_category($courseid);
1275    $course_category->delete('coursedelete');
1276    if ($showfeedback) {
1277        notify($strdeleted.' - '.get_string('grades', 'grades').', '.get_string('items', 'grades').', '.get_string('categories', 'grades'));
1278    }
1279
1280    if ($outcomes = grade_outcome::fetch_all(array('courseid'=>$courseid))) {
1281        foreach ($outcomes as $outcome) {
1282            $outcome->delete('coursedelete');
1283        }
1284    }
1285    delete_records('grade_outcomes_courses', 'courseid', $courseid);
1286    if ($showfeedback) {
1287        notify($strdeleted.' - '.get_string('outcomes', 'grades'));
1288    }
1289
1290    if ($scales = grade_scale::fetch_all(array('courseid'=>$courseid))) {
1291        foreach ($scales as $scale) {
1292            $scale->delete('coursedelete');
1293        }
1294    }
1295    if ($showfeedback) {
1296        notify($strdeleted.' - '.get_string('scales'));
1297    }
1298
1299    delete_records('grade_settings', 'courseid', $courseid);
1300    if ($showfeedback) {
1301        notify($strdeleted.' - '.get_string('settings', 'grades'));
1302    }
1303}
1304
1305/**
1306 * Called when course category deleted - cleanup gradebook
1307 * @param int $categoryid course category id
1308 * @param int $newparentid empty means everything deleted, otherwise id of category where content moved
1309 * @param bool $showfeedback print feedback
1310 */
1311function grade_course_category_delete($categoryid, $newparentid, $showfeedback) {
1312    $context = get_context_instance(CONTEXT_COURSECAT, $categoryid);
1313    delete_records('grade_letters', 'contextid', $context->id);
1314}
1315
1316/**
1317 * Does gradebook cleanup when module uninstalled.
1318 */
1319function grade_uninstalled_module($modname) {
1320    global $CFG;
1321
1322    $sql = "SELECT *
1323              FROM {$CFG->prefix}grade_items
1324             WHERE itemtype='mod' AND itemmodule='$modname'";
1325
1326    // go all items for this module and delete them including the grades
1327    if ($rs = get_recordset_sql($sql)) {
1328        while ($item = rs_fetch_next_record($rs)) {
1329            $grade_item = new grade_item($item, false);
1330            $grade_item->delete('moduninstall');
1331        }
1332        rs_close($rs);
1333    }
1334}
1335
1336/**
1337 * Grading cron job
1338 */
1339function grade_cron() {
1340    global $CFG;
1341
1342    $now = time();
1343
1344    $sql = "SELECT i.*
1345              FROM {$CFG->prefix}grade_items i
1346             WHERE i.locked = 0 AND i.locktime > 0 AND i.locktime < $now AND EXISTS (
1347                SELECT 'x' FROM {$CFG->prefix}grade_items c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
1348
1349    // go through all courses that have proper final grades and lock them if needed
1350    if ($rs = get_recordset_sql($sql)) {
1351        while ($item = rs_fetch_next_record($rs)) {
1352            $grade_item = new grade_item($item, false);
1353            $grade_item->locked = $now;
1354            $grade_item->update('locktime');
1355        }
1356        rs_close($rs);
1357    }
1358
1359    $grade_inst = new grade_grade();
1360    $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
1361
1362    $sql = "SELECT $fields
1363              FROM {$CFG->prefix}grade_grades g, {$CFG->prefix}grade_items i
1364             WHERE g.locked = 0 AND g.locktime > 0 AND g.locktime < $now AND g.itemid=i.id AND EXISTS (
1365                SELECT 'x' FROM {$CFG->prefix}grade_items c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
1366
1367    // go through all courses that have proper final grades and lock them if needed
1368    if ($rs = get_recordset_sql($sql)) {
1369        while ($grade = rs_fetch_next_record($rs)) {
1370            $grade_grade = new grade_grade($grade, false);
1371            $grade_grade->locked = $now;
1372            $grade_grade->update('locktime');
1373        }
1374        rs_close($rs);
1375    }
1376
1377    //TODO: do not run this cleanup every cron invocation
1378    // cleanup history tables
1379    if (!empty($CFG->gradehistorylifetime)) {  // value in days
1380        $histlifetime = $now - ($CFG->gradehistorylifetime * 3600 * 24);
1381        $tables = array('grade_outcomes_history', 'grade_categories_history', 'grade_items_hist…

Large files files are truncated, but you can click here to view the full file