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