PageRenderTime 74ms CodeModel.GetById 25ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/gradelib.php

http://github.com/moodle/moodle
PHP | 1642 lines | 1008 code | 236 blank | 398 comment | 230 complexity | a74bea8d95abf0c97ad50453151eb0b9 MD5 | raw file

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

   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
  25defined('MOODLE_INTERNAL') || die();
  26
  27global $CFG;
  28
  29/** Include essential files */
  30require_once($CFG->libdir . '/grade/constants.php');
  31
  32require_once($CFG->libdir . '/grade/grade_category.php');
  33require_once($CFG->libdir . '/grade/grade_item.php');
  34require_once($CFG->libdir . '/grade/grade_grade.php');
  35require_once($CFG->libdir . '/grade/grade_scale.php');
  36require_once($CFG->libdir . '/grade/grade_outcome.php');
  37
  38/////////////////////////////////////////////////////////////////////
  39///// Start of public API for communication with modules/blocks /////
  40/////////////////////////////////////////////////////////////////////
  41
  42/**
  43 * Submit new or update grade; update/create grade_item definition. Grade must have userid specified,
  44 * rawgrade and feedback with format are optional. rawgrade NULL means 'Not graded'.
  45 * Missing property or key means does not change the existing value.
  46 *
  47 * Only following grade item properties can be changed 'itemname', 'idnumber', 'gradetype', 'grademax',
  48 * 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted' and 'hidden'. 'reset' means delete all current grades including locked ones.
  49 *
  50 * Manual, course or category items can not be updated by this function.
  51 *
  52 * @category grade
  53 * @param string $source Source of the grade such as 'mod/assignment'
  54 * @param int    $courseid ID of course
  55 * @param string $itemtype Type of grade item. For example, mod or block
  56 * @param string $itemmodule More specific then $itemtype. For example, assignment or forum. May be NULL for some item types
  57 * @param int    $iteminstance Instance ID of graded item
  58 * @param int    $itemnumber Most probably 0. Modules can use other numbers when having more than one grade for each user
  59 * @param mixed  $grades Grade (object, array) or several grades (arrays of arrays or objects), NULL if updating grade_item definition only
  60 * @param mixed  $itemdetails Object or array describing the grading item, NULL if no change
  61 * @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED
  62 */
  63function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, $itemnumber, $grades=NULL, $itemdetails=NULL) {
  64    global $USER, $CFG, $DB;
  65
  66    // only following grade_item properties can be changed in this function
  67    $allowed = array('itemname', 'idnumber', 'gradetype', 'grademax', 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted', 'hidden');
  68    // list of 10,5 numeric fields
  69    $floats  = array('grademin', 'grademax', 'multfactor', 'plusfactor');
  70
  71    // grade item identification
  72    $params = compact('courseid', 'itemtype', 'itemmodule', 'iteminstance', 'itemnumber');
  73
  74    if (is_null($courseid) or is_null($itemtype)) {
  75        debugging('Missing courseid or itemtype');
  76        return GRADE_UPDATE_FAILED;
  77    }
  78
  79    if (!$grade_items = grade_item::fetch_all($params)) {
  80        // create a new one
  81        $grade_item = false;
  82
  83    } else if (count($grade_items) == 1){
  84        $grade_item = reset($grade_items);
  85        unset($grade_items); //release memory
  86
  87    } else {
  88        debugging('Found more than one grade item');
  89        return GRADE_UPDATE_MULTIPLE;
  90    }
  91
  92    if (!empty($itemdetails['deleted'])) {
  93        if ($grade_item) {
  94            if ($grade_item->delete($source)) {
  95                return GRADE_UPDATE_OK;
  96            } else {
  97                return GRADE_UPDATE_FAILED;
  98            }
  99        }
 100        return GRADE_UPDATE_OK;
 101    }
 102
 103/// Create or update the grade_item if needed
 104
 105    if (!$grade_item) {
 106        if ($itemdetails) {
 107            $itemdetails = (array)$itemdetails;
 108
 109            // grademin and grademax ignored when scale specified
 110            if (array_key_exists('scaleid', $itemdetails)) {
 111                if ($itemdetails['scaleid']) {
 112                    unset($itemdetails['grademin']);
 113                    unset($itemdetails['grademax']);
 114                }
 115            }
 116
 117            foreach ($itemdetails as $k=>$v) {
 118                if (!in_array($k, $allowed)) {
 119                    // ignore it
 120                    continue;
 121                }
 122                if ($k == 'gradetype' and $v == GRADE_TYPE_NONE) {
 123                    // no grade item needed!
 124                    return GRADE_UPDATE_OK;
 125                }
 126                $params[$k] = $v;
 127            }
 128        }
 129        $grade_item = new grade_item($params);
 130        $grade_item->insert();
 131
 132    } else {
 133        if ($grade_item->is_locked()) {
 134            // no notice() here, test returned value instead!
 135            return GRADE_UPDATE_ITEM_LOCKED;
 136        }
 137
 138        if ($itemdetails) {
 139            $itemdetails = (array)$itemdetails;
 140            $update = false;
 141            foreach ($itemdetails as $k=>$v) {
 142                if (!in_array($k, $allowed)) {
 143                    // ignore it
 144                    continue;
 145                }
 146                if (in_array($k, $floats)) {
 147                    if (grade_floats_different($grade_item->{$k}, $v)) {
 148                        $grade_item->{$k} = $v;
 149                        $update = true;
 150                    }
 151
 152                } else {
 153                    if ($grade_item->{$k} != $v) {
 154                        $grade_item->{$k} = $v;
 155                        $update = true;
 156                    }
 157                }
 158            }
 159            if ($update) {
 160                $grade_item->update();
 161            }
 162        }
 163    }
 164
 165/// reset grades if requested
 166    if (!empty($itemdetails['reset'])) {
 167        $grade_item->delete_all_grades('reset');
 168        return GRADE_UPDATE_OK;
 169    }
 170
 171/// Some extra checks
 172    // do we use grading?
 173    if ($grade_item->gradetype == GRADE_TYPE_NONE) {
 174        return GRADE_UPDATE_OK;
 175    }
 176
 177    // no grade submitted
 178    if (empty($grades)) {
 179        return GRADE_UPDATE_OK;
 180    }
 181
 182/// Finally start processing of grades
 183    if (is_object($grades)) {
 184        $grades = array($grades->userid=>$grades);
 185    } else {
 186        if (array_key_exists('userid', $grades)) {
 187            $grades = array($grades['userid']=>$grades);
 188        }
 189    }
 190
 191/// normalize and verify grade array
 192    foreach($grades as $k=>$g) {
 193        if (!is_array($g)) {
 194            $g = (array)$g;
 195            $grades[$k] = $g;
 196        }
 197
 198        if (empty($g['userid']) or $k != $g['userid']) {
 199            debugging('Incorrect grade array index, must be user id! Grade ignored.');
 200            unset($grades[$k]);
 201        }
 202    }
 203
 204    if (empty($grades)) {
 205        return GRADE_UPDATE_FAILED;
 206    }
 207
 208    $count = count($grades);
 209    if ($count > 0 and $count < 200) {
 210        list($uids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED, $start='uid');
 211        $params['gid'] = $grade_item->id;
 212        $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid AND userid $uids";
 213
 214    } else {
 215        $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid";
 216        $params = array('gid'=>$grade_item->id);
 217    }
 218
 219    $rs = $DB->get_recordset_sql($sql, $params);
 220
 221    $failed = false;
 222
 223    while (count($grades) > 0) {
 224        $grade_grade = null;
 225        $grade       = null;
 226
 227        foreach ($rs as $gd) {
 228
 229            $userid = $gd->userid;
 230            if (!isset($grades[$userid])) {
 231                // this grade not requested, continue
 232                continue;
 233            }
 234            // existing grade requested
 235            $grade       = $grades[$userid];
 236            $grade_grade = new grade_grade($gd, false);
 237            unset($grades[$userid]);
 238            break;
 239        }
 240
 241        if (is_null($grade_grade)) {
 242            if (count($grades) == 0) {
 243                // no more grades to process
 244                break;
 245            }
 246
 247            $grade       = reset($grades);
 248            $userid      = $grade['userid'];
 249            $grade_grade = new grade_grade(array('itemid'=>$grade_item->id, 'userid'=>$userid), false);
 250            $grade_grade->load_optional_fields(); // add feedback and info too
 251            unset($grades[$userid]);
 252        }
 253
 254        $rawgrade       = false;
 255        $feedback       = false;
 256        $feedbackformat = FORMAT_MOODLE;
 257        $feedbackfiles = [];
 258        $usermodified   = $USER->id;
 259        $datesubmitted  = null;
 260        $dategraded     = null;
 261
 262        if (array_key_exists('rawgrade', $grade)) {
 263            $rawgrade = $grade['rawgrade'];
 264        }
 265
 266        if (array_key_exists('feedback', $grade)) {
 267            $feedback = $grade['feedback'];
 268        }
 269
 270        if (array_key_exists('feedbackformat', $grade)) {
 271            $feedbackformat = $grade['feedbackformat'];
 272        }
 273
 274        if (array_key_exists('feedbackfiles', $grade)) {
 275            $feedbackfiles = $grade['feedbackfiles'];
 276        }
 277
 278        if (array_key_exists('usermodified', $grade)) {
 279            $usermodified = $grade['usermodified'];
 280        }
 281
 282        if (array_key_exists('datesubmitted', $grade)) {
 283            $datesubmitted = $grade['datesubmitted'];
 284        }
 285
 286        if (array_key_exists('dategraded', $grade)) {
 287            $dategraded = $grade['dategraded'];
 288        }
 289
 290        // update or insert the grade
 291        if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified,
 292                $dategraded, $datesubmitted, $grade_grade, $feedbackfiles)) {
 293            $failed = true;
 294        }
 295    }
 296
 297    if ($rs) {
 298        $rs->close();
 299    }
 300
 301    if (!$failed) {
 302        return GRADE_UPDATE_OK;
 303    } else {
 304        return GRADE_UPDATE_FAILED;
 305    }
 306}
 307
 308/**
 309 * Updates a user's outcomes. Manual outcomes can not be updated.
 310 *
 311 * @category grade
 312 * @param string $source Source of the grade such as 'mod/assignment'
 313 * @param int    $courseid ID of course
 314 * @param string $itemtype Type of grade item. For example, 'mod' or 'block'
 315 * @param string $itemmodule More specific then $itemtype. For example, 'forum' or 'quiz'. May be NULL for some item types
 316 * @param int    $iteminstance Instance ID of graded item. For example the forum ID.
 317 * @param int    $userid ID of the graded user
 318 * @param array  $data Array consisting of grade item itemnumber ({@link grade_update()}) => outcomegrade
 319 * @return bool returns true if grade items were found and updated successfully
 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        $result = true;
 324        foreach ($items as $item) {
 325            if (!array_key_exists($item->itemnumber, $data)) {
 326                continue;
 327            }
 328            $grade = $data[$item->itemnumber] < 1 ? null : $data[$item->itemnumber];
 329            $result = ($item->update_final_grade($userid, $grade, $source) && $result);
 330        }
 331        return $result;
 332    }
 333    return false; //grade items not found
 334}
 335
 336/**
 337 * Return true if the course needs regrading.
 338 *
 339 * @param int $courseid The course ID
 340 * @return bool true if course grades need updating.
 341 */
 342function grade_needs_regrade_final_grades($courseid) {
 343    $course_item = grade_item::fetch_course_item($courseid);
 344    return $course_item->needsupdate;
 345}
 346
 347/**
 348 * Return true if the regrade process is likely to be time consuming and
 349 * will therefore require the progress bar.
 350 *
 351 * @param int $courseid The course ID
 352 * @return bool Whether the regrade process is likely to be time consuming
 353 */
 354function grade_needs_regrade_progress_bar($courseid) {
 355    global $DB;
 356    $grade_items = grade_item::fetch_all(array('courseid' => $courseid));
 357
 358    list($sql, $params) = $DB->get_in_or_equal(array_keys($grade_items), SQL_PARAMS_NAMED, 'gi');
 359    $gradecount = $DB->count_records_select('grade_grades', 'itemid ' . $sql, $params);
 360
 361    // This figure may seem arbitrary, but after analysis it seems that 100 grade_grades can be calculated in ~= 0.5 seconds.
 362    // Any longer than this and we want to show the progress bar.
 363    return $gradecount > 100;
 364}
 365
 366/**
 367 * Check whether regarding of final grades is required and, if so, perform the regrade.
 368 *
 369 * If the regrade is expected to be time consuming (see grade_needs_regrade_progress_bar), then this
 370 * function will output the progress bar, and redirect to the current PAGE->url after regrading
 371 * completes. Otherwise the regrading will happen immediately and the page will be loaded as per
 372 * normal.
 373 *
 374 * A callback may be specified, which is called if regrading has taken place.
 375 * The callback may optionally return a URL which will be redirected to when the progress bar is present.
 376 *
 377 * @param stdClass $course The course to regrade
 378 * @param callable $callback A function to call if regrading took place
 379 * @return moodle_url The URL to redirect to if redirecting
 380 */
 381function grade_regrade_final_grades_if_required($course, callable $callback = null) {
 382    global $PAGE, $OUTPUT;
 383
 384    if (!grade_needs_regrade_final_grades($course->id)) {
 385        return false;
 386    }
 387
 388    if (grade_needs_regrade_progress_bar($course->id)) {
 389        $PAGE->set_heading($course->fullname);
 390        echo $OUTPUT->header();
 391        echo $OUTPUT->heading(get_string('recalculatinggrades', 'grades'));
 392        $progress = new \core\progress\display(true);
 393        $status = grade_regrade_final_grades($course->id, null, null, $progress);
 394
 395        // Show regrade errors and set the course to no longer needing regrade (stop endless loop).
 396        if (is_array($status)) {
 397            foreach ($status as $error) {
 398                $errortext = new \core\output\notification($error, \core\output\notification::NOTIFY_ERROR);
 399                echo $OUTPUT->render($errortext);
 400            }
 401            $courseitem = grade_item::fetch_course_item($course->id);
 402            $courseitem->regrading_finished();
 403        }
 404
 405        if ($callback) {
 406            //
 407            $url = call_user_func($callback);
 408        }
 409
 410        if (empty($url)) {
 411            $url = $PAGE->url;
 412        }
 413
 414        echo $OUTPUT->continue_button($url);
 415        echo $OUTPUT->footer();
 416        die();
 417    } else {
 418        $result = grade_regrade_final_grades($course->id);
 419        if ($callback) {
 420            call_user_func($callback);
 421        }
 422        return $result;
 423    }
 424}
 425
 426/**
 427 * Returns grading information for given activity, optionally with user grades
 428 * Manual, course or category items can not be queried.
 429 *
 430 * @category grade
 431 * @param int    $courseid ID of course
 432 * @param string $itemtype Type of grade item. For example, 'mod' or 'block'
 433 * @param string $itemmodule More specific then $itemtype. For example, 'forum' or 'quiz'. May be NULL for some item types
 434 * @param int    $iteminstance ID of the item module
 435 * @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
 436 * @return array Array of grade information objects (scaleid, name, grade and locked status, etc.) indexed with itemnumbers
 437 */
 438function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) {
 439    global $CFG;
 440
 441    $return = new stdClass();
 442    $return->items    = array();
 443    $return->outcomes = array();
 444
 445    $course_item = grade_item::fetch_course_item($courseid);
 446    $needsupdate = array();
 447    if ($course_item->needsupdate) {
 448        $result = grade_regrade_final_grades($courseid);
 449        if ($result !== true) {
 450            $needsupdate = array_keys($result);
 451        }
 452    }
 453
 454    if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) {
 455        foreach ($grade_items as $grade_item) {
 456            $decimalpoints = null;
 457
 458            if (empty($grade_item->outcomeid)) {
 459                // prepare information about grade item
 460                $item = new stdClass();
 461                $item->id = $grade_item->id;
 462                $item->itemnumber = $grade_item->itemnumber;
 463                $item->itemtype  = $grade_item->itemtype;
 464                $item->itemmodule = $grade_item->itemmodule;
 465                $item->iteminstance = $grade_item->iteminstance;
 466                $item->scaleid    = $grade_item->scaleid;
 467                $item->name       = $grade_item->get_name();
 468                $item->grademin   = $grade_item->grademin;
 469                $item->grademax   = $grade_item->grademax;
 470                $item->gradepass  = $grade_item->gradepass;
 471                $item->locked     = $grade_item->is_locked();
 472                $item->hidden     = $grade_item->is_hidden();
 473                $item->grades     = array();
 474
 475                switch ($grade_item->gradetype) {
 476                    case GRADE_TYPE_NONE:
 477                        break;
 478
 479                    case GRADE_TYPE_VALUE:
 480                        $item->scaleid = 0;
 481                        break;
 482
 483                    case GRADE_TYPE_TEXT:
 484                        $item->scaleid   = 0;
 485                        $item->grademin   = 0;
 486                        $item->grademax   = 0;
 487                        $item->gradepass  = 0;
 488                        break;
 489                }
 490
 491                if (empty($userid_or_ids)) {
 492                    $userids = array();
 493
 494                } else if (is_array($userid_or_ids)) {
 495                    $userids = $userid_or_ids;
 496
 497                } else {
 498                    $userids = array($userid_or_ids);
 499                }
 500
 501                if ($userids) {
 502                    $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true);
 503                    foreach ($userids as $userid) {
 504                        $grade_grades[$userid]->grade_item =& $grade_item;
 505
 506                        $grade = new stdClass();
 507                        $grade->grade          = $grade_grades[$userid]->finalgrade;
 508                        $grade->locked         = $grade_grades[$userid]->is_locked();
 509                        $grade->hidden         = $grade_grades[$userid]->is_hidden();
 510                        $grade->overridden     = $grade_grades[$userid]->overridden;
 511                        $grade->feedback       = $grade_grades[$userid]->feedback;
 512                        $grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
 513                        $grade->usermodified   = $grade_grades[$userid]->usermodified;
 514                        $grade->datesubmitted  = $grade_grades[$userid]->get_datesubmitted();
 515                        $grade->dategraded     = $grade_grades[$userid]->get_dategraded();
 516
 517                        // create text representation of grade
 518                        if ($grade_item->gradetype == GRADE_TYPE_TEXT or $grade_item->gradetype == GRADE_TYPE_NONE) {
 519                            $grade->grade          = null;
 520                            $grade->str_grade      = '-';
 521                            $grade->str_long_grade = $grade->str_grade;
 522
 523                        } else if (in_array($grade_item->id, $needsupdate)) {
 524                            $grade->grade          = false;
 525                            $grade->str_grade      = get_string('error');
 526                            $grade->str_long_grade = $grade->str_grade;
 527
 528                        } else if (is_null($grade->grade)) {
 529                            $grade->str_grade      = '-';
 530                            $grade->str_long_grade = $grade->str_grade;
 531
 532                        } else {
 533                            $grade->str_grade = grade_format_gradevalue($grade->grade, $grade_item);
 534                            if ($grade_item->gradetype == GRADE_TYPE_SCALE or $grade_item->get_displaytype() != GRADE_DISPLAY_TYPE_REAL) {
 535                                $grade->str_long_grade = $grade->str_grade;
 536                            } else {
 537                                $a = new stdClass();
 538                                $a->grade = $grade->str_grade;
 539                                $a->max   = grade_format_gradevalue($grade_item->grademax, $grade_item);
 540                                $grade->str_long_grade = get_string('gradelong', 'grades', $a);
 541                            }
 542                        }
 543
 544                        // create html representation of feedback
 545                        if (is_null($grade->feedback)) {
 546                            $grade->str_feedback = '';
 547                        } else {
 548                            $feedback = file_rewrite_pluginfile_urls(
 549                                $grade->feedback,
 550                                'pluginfile.php',
 551                                $grade_grades[$userid]->get_context()->id,
 552                                GRADE_FILE_COMPONENT,
 553                                GRADE_FEEDBACK_FILEAREA,
 554                                $grade_grades[$userid]->id
 555                            );
 556
 557                            $grade->str_feedback = format_text($feedback, $grade->feedbackformat,
 558                                ['context' => $grade_grades[$userid]->get_context()]);
 559                        }
 560
 561                        $item->grades[$userid] = $grade;
 562                    }
 563                }
 564                $return->items[$grade_item->itemnumber] = $item;
 565
 566            } else {
 567                if (!$grade_outcome = grade_outcome::fetch(array('id'=>$grade_item->outcomeid))) {
 568                    debugging('Incorect outcomeid found');
 569                    continue;
 570                }
 571
 572                // outcome info
 573                $outcome = new stdClass();
 574                $outcome->id = $grade_item->id;
 575                $outcome->itemnumber = $grade_item->itemnumber;
 576                $outcome->itemtype   = $grade_item->itemtype;
 577                $outcome->itemmodule = $grade_item->itemmodule;
 578                $outcome->iteminstance = $grade_item->iteminstance;
 579                $outcome->scaleid    = $grade_outcome->scaleid;
 580                $outcome->name       = $grade_outcome->get_name();
 581                $outcome->locked     = $grade_item->is_locked();
 582                $outcome->hidden     = $grade_item->is_hidden();
 583
 584                if (empty($userid_or_ids)) {
 585                    $userids = array();
 586                } else if (is_array($userid_or_ids)) {
 587                    $userids = $userid_or_ids;
 588                } else {
 589                    $userids = array($userid_or_ids);
 590                }
 591
 592                if ($userids) {
 593                    $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true);
 594                    foreach ($userids as $userid) {
 595                        $grade_grades[$userid]->grade_item =& $grade_item;
 596
 597                        $grade = new stdClass();
 598                        $grade->grade          = $grade_grades[$userid]->finalgrade;
 599                        $grade->locked         = $grade_grades[$userid]->is_locked();
 600                        $grade->hidden         = $grade_grades[$userid]->is_hidden();
 601                        $grade->feedback       = $grade_grades[$userid]->feedback;
 602                        $grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
 603                        $grade->usermodified   = $grade_grades[$userid]->usermodified;
 604                        $grade->datesubmitted  = $grade_grades[$userid]->get_datesubmitted();
 605                        $grade->dategraded     = $grade_grades[$userid]->get_dategraded();
 606
 607                        // create text representation of grade
 608                        if (in_array($grade_item->id, $needsupdate)) {
 609                            $grade->grade     = false;
 610                            $grade->str_grade = get_string('error');
 611
 612                        } else if (is_null($grade->grade)) {
 613                            $grade->grade = 0;
 614                            $grade->str_grade = get_string('nooutcome', 'grades');
 615
 616                        } else {
 617                            $grade->grade = (int)$grade->grade;
 618                            $scale = $grade_item->load_scale();
 619                            $grade->str_grade = format_string($scale->scale_items[(int)$grade->grade-1]);
 620                        }
 621
 622                        // create html representation of feedback
 623                        if (is_null($grade->feedback)) {
 624                            $grade->str_feedback = '';
 625                        } else {
 626                            $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat);
 627                        }
 628
 629                        $outcome->grades[$userid] = $grade;
 630                    }
 631                }
 632
 633                if (isset($return->outcomes[$grade_item->itemnumber])) {
 634                    // itemnumber duplicates - lets fix them!
 635                    $newnumber = $grade_item->itemnumber + 1;
 636                    while(grade_item::fetch(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid, 'itemnumber'=>$newnumber))) {
 637                        $newnumber++;
 638                    }
 639                    $outcome->itemnumber    = $newnumber;
 640                    $grade_item->itemnumber = $newnumber;
 641                    $grade_item->update('system');
 642                }
 643
 644                $return->outcomes[$grade_item->itemnumber] = $outcome;
 645
 646            }
 647        }
 648    }
 649
 650    // sort results using itemnumbers
 651    ksort($return->items, SORT_NUMERIC);
 652    ksort($return->outcomes, SORT_NUMERIC);
 653
 654    return $return;
 655}
 656
 657///////////////////////////////////////////////////////////////////
 658///// End of public API for communication with modules/blocks /////
 659///////////////////////////////////////////////////////////////////
 660
 661
 662
 663///////////////////////////////////////////////////////////////////
 664///// Internal API: used by gradebook plugins and Moodle core /////
 665///////////////////////////////////////////////////////////////////
 666
 667/**
 668 * Returns a  course gradebook setting
 669 *
 670 * @param int $courseid
 671 * @param string $name of setting, maybe null if reset only
 672 * @param string $default value to return if setting is not found
 673 * @param bool $resetcache force reset of internal static cache
 674 * @return string value of the setting, $default if setting not found, NULL if supplied $name is null
 675 */
 676function grade_get_setting($courseid, $name, $default=null, $resetcache=false) {
 677    global $DB;
 678
 679    static $cache = array();
 680
 681    if ($resetcache or !array_key_exists($courseid, $cache)) {
 682        $cache[$courseid] = array();
 683
 684    } else if (is_null($name)) {
 685        return null;
 686
 687    } else if (array_key_exists($name, $cache[$courseid])) {
 688        return $cache[$courseid][$name];
 689    }
 690
 691    if (!$data = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) {
 692        $result = null;
 693    } else {
 694        $result = $data->value;
 695    }
 696
 697    if (is_null($result)) {
 698        $result = $default;
 699    }
 700
 701    $cache[$courseid][$name] = $result;
 702    return $result;
 703}
 704
 705/**
 706 * Returns all course gradebook settings as object properties
 707 *
 708 * @param int $courseid
 709 * @return object
 710 */
 711function grade_get_settings($courseid) {
 712    global $DB;
 713
 714     $settings = new stdClass();
 715     $settings->id = $courseid;
 716
 717    if ($records = $DB->get_records('grade_settings', array('courseid'=>$courseid))) {
 718        foreach ($records as $record) {
 719            $settings->{$record->name} = $record->value;
 720        }
 721    }
 722
 723    return $settings;
 724}
 725
 726/**
 727 * Add, update or delete a course gradebook setting
 728 *
 729 * @param int $courseid The course ID
 730 * @param string $name Name of the setting
 731 * @param string $value Value of the setting. NULL means delete the setting.
 732 */
 733function grade_set_setting($courseid, $name, $value) {
 734    global $DB;
 735
 736    if (is_null($value)) {
 737        $DB->delete_records('grade_settings', array('courseid'=>$courseid, 'name'=>$name));
 738
 739    } else if (!$existing = $DB->get_record('grade_settings', array('courseid'=>$courseid, 'name'=>$name))) {
 740        $data = new stdClass();
 741        $data->courseid = $courseid;
 742        $data->name     = $name;
 743        $data->value    = $value;
 744        $DB->insert_record('grade_settings', $data);
 745
 746    } else {
 747        $data = new stdClass();
 748        $data->id       = $existing->id;
 749        $data->value    = $value;
 750        $DB->update_record('grade_settings', $data);
 751    }
 752
 753    grade_get_setting($courseid, null, null, true); // reset the cache
 754}
 755
 756/**
 757 * Returns string representation of grade value
 758 *
 759 * @param float $value The grade value
 760 * @param object $grade_item Grade item object passed by reference to prevent scale reloading
 761 * @param bool $localized use localised decimal separator
 762 * @param int $displaytype type of display. For example GRADE_DISPLAY_TYPE_REAL, GRADE_DISPLAY_TYPE_PERCENTAGE, GRADE_DISPLAY_TYPE_LETTER
 763 * @param int $decimals The number of decimal places when displaying float values
 764 * @return string
 765 */
 766function grade_format_gradevalue($value, &$grade_item, $localized=true, $displaytype=null, $decimals=null) {
 767    if ($grade_item->gradetype == GRADE_TYPE_NONE or $grade_item->gradetype == GRADE_TYPE_TEXT) {
 768        return '';
 769    }
 770
 771    // no grade yet?
 772    if (is_null($value)) {
 773        return '-';
 774    }
 775
 776    if ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) {
 777        //unknown type??
 778        return '';
 779    }
 780
 781    if (is_null($displaytype)) {
 782        $displaytype = $grade_item->get_displaytype();
 783    }
 784
 785    if (is_null($decimals)) {
 786        $decimals = $grade_item->get_decimals();
 787    }
 788
 789    switch ($displaytype) {
 790        case GRADE_DISPLAY_TYPE_REAL:
 791            return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized);
 792
 793        case GRADE_DISPLAY_TYPE_PERCENTAGE:
 794            return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized);
 795
 796        case GRADE_DISPLAY_TYPE_LETTER:
 797            return grade_format_gradevalue_letter($value, $grade_item);
 798
 799        case GRADE_DISPLAY_TYPE_REAL_PERCENTAGE:
 800            return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' .
 801                    grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')';
 802
 803        case GRADE_DISPLAY_TYPE_REAL_LETTER:
 804            return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' .
 805                    grade_format_gradevalue_letter($value, $grade_item) . ')';
 806
 807        case GRADE_DISPLAY_TYPE_PERCENTAGE_REAL:
 808            return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' .
 809                    grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')';
 810
 811        case GRADE_DISPLAY_TYPE_LETTER_REAL:
 812            return grade_format_gradevalue_letter($value, $grade_item) . ' (' .
 813                    grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')';
 814
 815        case GRADE_DISPLAY_TYPE_LETTER_PERCENTAGE:
 816            return grade_format_gradevalue_letter($value, $grade_item) . ' (' .
 817                    grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')';
 818
 819        case GRADE_DISPLAY_TYPE_PERCENTAGE_LETTER:
 820            return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' .
 821                    grade_format_gradevalue_letter($value, $grade_item) . ')';
 822        default:
 823            return '';
 824    }
 825}
 826
 827/**
 828 * Returns a float representation of a grade value
 829 *
 830 * @param float $value The grade value
 831 * @param object $grade_item Grade item object
 832 * @param int $decimals The number of decimal places
 833 * @param bool $localized use localised decimal separator
 834 * @return string
 835 */
 836function grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) {
 837    if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
 838        if (!$scale = $grade_item->load_scale()) {
 839            return get_string('error');
 840        }
 841
 842        $value = $grade_item->bounded_grade($value);
 843        return format_string($scale->scale_items[$value-1]);
 844
 845    } else {
 846        return format_float($value, $decimals, $localized);
 847    }
 848}
 849
 850/**
 851 * Returns a percentage representation of a grade value
 852 *
 853 * @param float $value The grade value
 854 * @param object $grade_item Grade item object
 855 * @param int $decimals The number of decimal places
 856 * @param bool $localized use localised decimal separator
 857 * @return string
 858 */
 859function grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) {
 860    $min = $grade_item->grademin;
 861    $max = $grade_item->grademax;
 862    if ($min == $max) {
 863        return '';
 864    }
 865    $value = $grade_item->bounded_grade($value);
 866    $percentage = (($value-$min)*100)/($max-$min);
 867    return format_float($percentage, $decimals, $localized).' %';
 868}
 869
 870/**
 871 * Returns a letter grade representation of a grade value
 872 * The array of grade letters used is produced by {@link grade_get_letters()} using the course context
 873 *
 874 * @param float $value The grade value
 875 * @param object $grade_item Grade item object
 876 * @return string
 877 */
 878function grade_format_gradevalue_letter($value, $grade_item) {
 879    global $CFG;
 880    $context = context_course::instance($grade_item->courseid, IGNORE_MISSING);
 881    if (!$letters = grade_get_letters($context)) {
 882        return ''; // no letters??
 883    }
 884
 885    if (is_null($value)) {
 886        return '-';
 887    }
 888
 889    $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100);
 890    $value = bounded_number(0, $value, 100); // just in case
 891
 892    $gradebookcalculationsfreeze = 'gradebook_calculations_freeze_' . $grade_item->courseid;
 893
 894    foreach ($letters as $boundary => $letter) {
 895        if (property_exists($CFG, $gradebookcalculationsfreeze) && (int)$CFG->{$gradebookcalculationsfreeze} <= 20160518) {
 896            // Do nothing.
 897        } else {
 898            // The boundary is a percentage out of 100 so use 0 as the min and 100 as the max.
 899            $boundary = grade_grade::standardise_score($boundary, 0, 100, 0, 100);
 900        }
 901        if ($value >= $boundary) {
 902            return format_string($letter);
 903        }
 904    }
 905    return '-'; // no match? maybe '' would be more correct
 906}
 907
 908
 909/**
 910 * Returns grade options for gradebook grade category menu
 911 *
 912 * @param int $courseid The course ID
 913 * @param bool $includenew Include option for new category at array index -1
 914 * @return array of grade categories in course
 915 */
 916function grade_get_categories_menu($courseid, $includenew=false) {
 917    $result = array();
 918    if (!$categories = grade_category::fetch_all(array('courseid'=>$courseid))) {
 919        //make sure course category exists
 920        if (!grade_category::fetch_course_category($courseid)) {
 921            debugging('Can not create course grade category!');
 922            return $result;
 923        }
 924        $categories = grade_category::fetch_all(array('courseid'=>$courseid));
 925    }
 926    foreach ($categories as $key=>$category) {
 927        if ($category->is_course_category()) {
 928            $result[$category->id] = get_string('uncategorised', 'grades');
 929            unset($categories[$key]);
 930        }
 931    }
 932    if ($includenew) {
 933        $result[-1] = get_string('newcategory', 'grades');
 934    }
 935    $cats = array();
 936    foreach ($categories as $category) {
 937        $cats[$category->id] = $category->get_name();
 938    }
 939    core_collator::asort($cats);
 940
 941    return ($result+$cats);
 942}
 943
 944/**
 945 * Returns the array of grade letters to be used in the supplied context
 946 *
 947 * @param object $context Context object or null for defaults
 948 * @return array of grade_boundary (minimum) => letter_string
 949 */
 950function grade_get_letters($context=null) {
 951    global $DB;
 952
 953    if (empty($context)) {
 954        //default grading letters
 955        return array('93'=>'A', '90'=>'A-', '87'=>'B+', '83'=>'B', '80'=>'B-', '77'=>'C+', '73'=>'C', '70'=>'C-', '67'=>'D+', '60'=>'D', '0'=>'F');
 956    }
 957
 958    static $cache = array();
 959
 960    if (array_key_exists($context->id, $cache)) {
 961        return $cache[$context->id];
 962    }
 963
 964    if (count($cache) > 100) {
 965        $cache = array(); // cache size limit
 966    }
 967
 968    $letters = array();
 969
 970    $contexts = $context->get_parent_context_ids();
 971    array_unshift($contexts, $context->id);
 972
 973    foreach ($contexts as $ctxid) {
 974        if ($records = $DB->get_records('grade_letters', array('contextid'=>$ctxid), 'lowerboundary DESC')) {
 975            foreach ($records as $record) {
 976                $letters[$record->lowerboundary] = $record->letter;
 977            }
 978        }
 979
 980        if (!empty($letters)) {
 981            $cache[$context->id] = $letters;
 982            return $letters;
 983        }
 984    }
 985
 986    $letters = grade_get_letters(null);
 987    $cache[$context->id] = $letters;
 988    return $letters;
 989}
 990
 991
 992/**
 993 * Verify new value of grade item idnumber. Checks for uniqueness of new ID numbers. Old ID numbers are kept intact.
 994 *
 995 * @param string $idnumber string (with magic quotes)
 996 * @param int $courseid ID numbers are course unique only
 997 * @param grade_item $grade_item The grade item this idnumber is associated with
 998 * @param stdClass $cm used for course module idnumbers and items attached to modules
 999 * @return bool true means idnumber ok
1000 */
1001function grade_verify_idnumber($idnumber, $courseid, $grade_item=null, $cm=null) {
1002    global $DB;
1003
1004    if ($idnumber == '') {
1005        //we allow empty idnumbers
1006        return true;
1007    }
1008
1009    // keep existing even when not unique
1010    if ($cm and $cm->idnumber == $idnumber) {
1011        if ($grade_item and $grade_item->itemnumber != 0) {
1012            // grade item with itemnumber > 0 can't have the same idnumber as the main
1013            // itemnumber 0 which is synced with course_modules
1014            return false;
1015        }
1016        return true;
1017    } else if ($grade_item and $grade_item->idnumber == $idnumber) {
1018        return true;
1019    }
1020
1021    if ($DB->record_exists('course_modules', array('course'=>$courseid, 'idnumber'=>$idnumber))) {
1022        return false;
1023    }
1024
1025    if ($DB->record_exists('grade_items', array('courseid'=>$courseid, 'idnumber'=>$idnumber))) {
1026        return false;
1027    }
1028
1029    return true;
1030}
1031
1032/**
1033 * Force final grade recalculation in all course items
1034 *
1035 * @param int $courseid The course ID to recalculate
1036 */
1037function grade_force_full_regrading($courseid) {
1038    global $DB;
1039    $DB->set_field('grade_items', 'needsupdate', 1, array('courseid'=>$courseid));
1040}
1041
1042/**
1043 * Forces regrading of all site grades. Used when changing site setings
1044 */
1045function grade_force_site_regrading() {
1046    global $CFG, $DB;
1047    $DB->set_field('grade_items', 'needsupdate', 1);
1048}
1049
1050/**
1051 * Recover a user's grades from grade_grades_history
1052 * @param int $userid the user ID whose grades we want to recover
1053 * @param int $courseid the relevant course
1054 * @return bool true if successful or false if there was an error or no grades could be recovered
1055 */
1056function grade_recover_history_grades($userid, $courseid) {
1057    global $CFG, $DB;
1058
1059    if ($CFG->disablegradehistory) {
1060        debugging('Attempting to recover grades when grade history is disabled.');
1061        return false;
1062    }
1063
1064    //Were grades recovered? Flag to return.
1065    $recoveredgrades = false;
1066
1067    //Check the user is enrolled in this course
1068    //Dont bother checking if they have a gradeable role. They may get one later so recover
1069    //whatever grades they have now just in case.
1070    $course_context = context_course::instance($courseid);
1071    if (!is_enrolled($course_context, $userid)) {
1072        debugging('Attempting to recover the grades of a user who is deleted or not enrolled. Skipping recover.');
1073        return false;
1074    }
1075
1076    //Check for existing grades for this user in this course
1077    //Recovering grades when the user already has grades can lead to duplicate indexes and bad data
1078    //In the future we could move the existing grades to the history table then recover the grades from before then
1079    $sql = "SELECT gg.id
1080              FROM {grade_grades} gg
1081              JOIN {grade_items} gi ON gi.id = gg.itemid
1082             WHERE gi.courseid = :courseid AND gg.userid = :userid";
1083    $params = array('userid' => $userid, 'courseid' => $courseid);
1084    if ($DB->record_exists_sql($sql, $params)) {
1085        debugging('Attempting to recover the grades of a user who already has grades. Skipping recover.');
1086        return false;
1087    } else {
1088        //Retrieve the user's old grades
1089        //have history ID as first column to guarantee we a unique first column
1090        $sql = "SELECT h.id, gi.itemtype, gi.itemmodule, gi.iteminstance as iteminstance, gi.itemnumber, h.source, h.itemid, h.userid, h.rawgrade, h.rawgrademax,
1091                       h.rawgrademin, h.rawscaleid, h.usermodified, h.finalgrade, h.hidden, h.locked, h.locktime, h.exported, h.overridden, h.excluded, h.feedback,
1092                       h.feedbackformat, h.information, h.informationformat, h.timemodified, itemcreated.tm AS timecreated
1093                  FROM {grade_grades_history} h
1094                  JOIN (SELECT itemid, MAX(id) AS id
1095                          FROM {grade_grades_history}
1096                         WHERE userid = :userid1
1097                      GROUP BY itemid) maxquery ON h.id = maxquery.id AND h.itemid = maxquery.itemid
1098                  JOIN {grade_items} gi ON gi.id = h.itemid
1099                  JOIN (SELECT itemid, MAX(timemodified) AS tm
1100                          FROM {grade_grades_history}
1101                         WHERE userid = :userid2 AND action = :insertaction
1102                      GROUP BY itemid) itemcreated ON itemcreated.itemid = h.itemid
1103                 WHERE gi.courseid = :courseid";
1104        $params = array('userid1' => $userid, 'userid2' => $userid , 'insertaction' => GRADE_HISTORY_INSERT, 'courseid' => $courseid);
1105        $oldgrades = $DB->get_records_sql($sql, $params);
1106
1107        //now move the old grades to the grade_grades table
1108        foreach ($oldgrades as $oldgrade) {
1109            unset($oldgrade->id);
1110
1111            $grade = new grade_grade($oldgrade, false);//2nd arg false as dont want to try and retrieve a record from the DB
1112            $grade->insert($oldgrade->source);
1113
1114            //dont include default empty grades created when activities are created
1115            if (!is_null($oldgrade->finalgrade) || !is_null($oldgrade->feedback)) {
1116                $recoveredgrades = true;
1117            }
1118        }
1119    }
1120
1121    //Some activities require manual grade synching (moving grades from the activity into the gradebook)
1122    //If the student was deleted when synching was done they may have grades in the activity that haven't been moved across
1123    grade_grab_course_grades($courseid, null, $userid);
1124
1125    return $recoveredgrades;
1126}
1127
1128/**
1129 * Updates all final grades in course.
1130 *
1131 * @param int $courseid The course ID
1132 * @param int $userid If specified try to do a quick regrading of the grades of this user only
1133 * @param object $updated_item Optional grade item to be marked for regrading. It is required if $userid is set.
1134 * @param \core\progress\base $progress If provided, will be used to update progress on this long operation.
1135 * @return bool true if ok, array of errors if problems found. Grade item id => error message
1136 */
1137function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null, $progress=null) {
1138    // This may take a very long time and extra memory.
1139    \core_php_time_limit::raise();
1140    raise_memory_limit(MEMORY_EXTRA);
1141
1142    $course_item = grade_item::fetch_course_item($courseid);
1143
1144    if ($progress == null) {
1145        $progress = new \core\progress\none();
1146    }
1147
1148    if ($userid) {
1149        // one raw grade updated for one user
1150        if (empty($updated_item)) {
1151            print_error("cannotbenull", 'debug', '', "updated_item");
1152        }
1153        if ($course_item->needsupdate) {
1154            $updated_item->force_regrading();
1155            return array($course_item->id =>'Can not do fast regrading after updating of raw grades');
1156        }
1157
1158    } else {
1159        if (!$course_item->needsupdate) {
1160            // nothing to do :-)
1161            return true;
1162        }
1163    }
1164
1165    // Categories might have to run some processing before we fetch the grade items.
1166    // This gives them a final opportunity to update and mark their children to be updated.
1167    // We need to work on the children categories up to the parent ones, so that, for instance,
1168    // if a category total is updated it will be reflected in the parent category.
1169    $cats = grade_category::fetch_all(array('courseid' => $courseid));
1170    $flatcattree = array();
1171    foreach ($cats as $cat) {
1172        if (!isset($flatcattree[$cat->depth])) {
1173            $flatcattree[$cat->depth] = array();
1174        }
1175        $flatcattree[$cat->depth][] = $cat;
1176    }
1177    krsort($flatcattree);
1178    foreach ($flatcattree as $depth => $cats) {
1179        foreach ($cats as $cat) {
1180            $cat->pre_regrade_final_grades();
1181        }
1182    }
1183
1184    $progresstotal = 0;
1185    $progresscurrent = 0;
1186
1187    $grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
1188    $depends_on = array();
1189
1190    foreach ($grade_items as $gid=>$gitem) {
1191        if ((!empty($updated_item) and $updated_item->id == $gid) ||
1192                $gitem->is_course_item() || $gitem->is_category_item() || $gitem->is_calculated()) {
1193            $grade_items[$gid]->needsupdate = 1;
1194        }
1195
1196        // We load all dependencies of these items later we can discard some grade_items based on this.
1197        if ($grade_items[$gid]->needsupdate) {
1198            $depends_on[$gid] = $grade_items[$gid]->depends_on();
1199            $progresstotal++;
1200        }
1201    }
1202
1203    $progress->start_progress('regrade_course', $progresstotal);
1204
1205    $errors = array();
1206    $finalids = array();
1207    $updatedids = array();
1208    $gids     = array_keys($grade_items);
1209    $failed = 0;
1210
1211    while (count($finalids) < count($gids)) { // work until all grades are final or error found
1212        $count = 0;
1213        foreach ($gids as $gid) {
1214            if (in_array($gid, $finalids)) {
1215                continue; // already final
1216            }
1217
1218            if (!$grade_items[$gid]->needsupdate) {
1219                $finalids[] = $gid; // we can make it final - does not need update
1220                continue;
1221            }
1222            $thisprogress = $progresstotal;
1223            foreach ($grade_items as $item) {
1224                if ($item->needsupdate) {
1225                    $thisprogress--;
1226                }
1227            }
1228            // Clip between $progresscurrent and $progresstotal.
1229            $thisprogress = max(min($thisprogress, $progresstotal), $progresscurrent);
1230            $progress->progress($thisprogress);
1231            $progresscurrent = $thisprogress;
1232
1233            foreach ($depends_on[$gid] as $did) {
1234                if (!in_array($did, $finalids)) {
1235                    // This item depends on something that is not yet in finals array.
1236                    continue 2;
1237                }
1238            }
1239
1240            // If this grade item has no dependancy with any updated item at all, then remove it from being recalculated.
1241
1242            // When we get here, all of this grade item's decendents are marked as final so they would be marked as updated too
1243            // if they would have been regraded. We don't need to regrade items which dependants (not only the direct ones
1244            // but any dependant in the cascade) have not been updated.
1245
1246            // If $updated_item was specified we discard the grade items that do not depend on it or on any grade item that
1247            // depend on $updated_item.
1248
1249            // Here we check to see if the direct decendants are marked as updated.
1250            if (!empty($updated_item) && $gid != $updated_item->id && !in_array($updated_item->id, $depends_on[$gid])) {
1251
1252                // We need to ensure that none of this item's dependencies have been updated.
1253                // If we find that one of the direct decendants of this grade item is marked as updated then this
1254                // grade item needs to be recalculated and marked as updated.
1255                // Being marked as updated is done further down in the code.
1256
1257                $updateddependencies = false;
1258                foreach ($depends_on[$gid] as $dependency) {
1259                    if (in_array($dependency, $updatedids)) {
1260                        $updateddependencies = true;
1261                        break;
1262                    }
1263                }
1264                if ($updateddependencies === false) {
1265                    // If no direct descendants are marked as updated, then we don't need to update this grade item. We then mark it
1266                    // as final.
1267                    $count++;
1268                    $finalids[] = $gid;
1269                    continue;
1270                }
1271            }
1272
1273            // Let's update, calculate or aggregate.
1274            $result = $grade_items[$gid]->regrade_final_grades($userid);
1275
1276            if ($result === true) {
1277
1278                // We should only update the database if we regraded all users.
1279                if (empty($userid)) {
1280                    $grade_items[$gid]->regrading_finished();
1281                    // Do the locktime item locking.
1282                    $grade_items[$gid]->check_locktime();
1283                } else {
1284                    $gra…

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