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