weekly release 2.3dev
[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) {
cf717dc2 211 list($uids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED, $start='uid');
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 }
d609d962 801 collatorlib::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
2293a504
PS
945 $sql = "SELECT gg.id
946 FROM {grade_grades} gg
947 JOIN {grade_items} gi ON gi.id = gg.itemid
948 WHERE gi.courseid = :courseid AND gg.userid = :userid";
13ba9036
AD
949 $params = array('userid' => $userid, 'courseid' => $courseid);
950 if ($DB->record_exists_sql($sql, $params)) {
951 debugging('Attempting to recover the grades of a user who already has grades. Skipping recover.');
952 return false;
953 } else {
954 //Retrieve the user's old grades
955 //have history ID as first column to guarantee we a unique first column
2293a504
PS
956 $sql = "SELECT h.id, gi.itemtype, gi.itemmodule, gi.iteminstance as iteminstance, gi.itemnumber, h.source, h.itemid, h.userid, h.rawgrade, h.rawgrademax,
957 h.rawgrademin, h.rawscaleid, h.usermodified, h.finalgrade, h.hidden, h.locked, h.locktime, h.exported, h.overridden, h.excluded, h.feedback,
958 h.feedbackformat, h.information, h.informationformat, h.timemodified, itemcreated.tm AS timecreated
13ba9036 959 FROM {grade_grades_history} h
2293a504
PS
960 JOIN (SELECT itemid, MAX(id) AS id
961 FROM {grade_grades_history}
962 WHERE userid = :userid1
963 GROUP BY itemid) maxquery ON h.id = maxquery.id AND h.itemid = maxquery.itemid
13ba9036 964 JOIN {grade_items} gi ON gi.id = h.itemid
2293a504
PS
965 JOIN (SELECT itemid, MAX(timemodified) AS tm
966 FROM {grade_grades_history}
967 WHERE userid = :userid2 AND action = :insertaction
968 GROUP BY itemid) itemcreated ON itemcreated.itemid = h.itemid
13ba9036
AD
969 WHERE gi.courseid = :courseid";
970 $params = array('userid1' => $userid, 'userid2' => $userid , 'insertaction' => GRADE_HISTORY_INSERT, 'courseid' => $courseid);
971 $oldgrades = $DB->get_records_sql($sql, $params);
972
973 //now move the old grades to the grade_grades table
974 foreach ($oldgrades as $oldgrade) {
975 unset($oldgrade->id);
976
977 $grade = new grade_grade($oldgrade, false);//2nd arg false as dont want to try and retrieve a record from the DB
978 $grade->insert($oldgrade->source);
979
980 //dont include default empty grades created when activities are created
2293a504 981 if (!is_null($oldgrade->finalgrade) || !is_null($oldgrade->feedback)) {
13ba9036
AD
982 $recoveredgrades = true;
983 }
984 }
985 }
986
987 //Some activities require manual grade synching (moving grades from the activity into the gradebook)
988 //If the student was deleted when synching was done they may have grades in the activity that haven't been moved across
989 grade_grab_course_grades($courseid, null, $userid);
990
991 return $recoveredgrades;
992}
993
5834dcdb 994/**
ac9b0805 995 * Updates all final grades in course.
a8995b34 996 *
997 * @param int $courseid
f8e6e4db 998 * @param int $userid if specified, try to do a quick regrading of grades of this user only
999 * @param object $updated_item the item in which
1000 * @return boolean true if ok, array of errors if problems found (item id is used as key)
a8995b34 1001 */
c86caae7 1002function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null) {
b8ff92b6 1003
514a3467 1004 $course_item = grade_item::fetch_course_item($courseid);
f04873a9 1005
f8e6e4db 1006 if ($userid) {
1007 // one raw grade updated for one user
1008 if (empty($updated_item)) {
2f137aa1 1009 print_error("cannotbenull", 'debug', '', "updated_item");
f8e6e4db 1010 }
1011 if ($course_item->needsupdate) {
1012 $updated_item->force_regrading();
b45d8391 1013 return array($course_item->id =>'Can not do fast regrading after updating of raw grades');
a8995b34 1014 }
772ddfbf 1015
f8e6e4db 1016 } else {
1017 if (!$course_item->needsupdate) {
1018 // nothing to do :-)
b8ff92b6 1019 return true;
b8ff92b6 1020 }
a8995b34 1021 }
1022
f8e6e4db 1023 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
1024 $depends_on = array();
1025
1026 // first mark all category and calculated items as needing regrading
fb0e3570 1027 // this is slower, but 100% accurate
f8e6e4db 1028 foreach ($grade_items as $gid=>$gitem) {
fb46b5b6 1029 if (!empty($updated_item) and $updated_item->id == $gid) {
f8e6e4db 1030 $grade_items[$gid]->needsupdate = 1;
1031
eacd3700 1032 } else if ($gitem->is_course_item() or $gitem->is_category_item() or $gitem->is_calculated()) {
f8e6e4db 1033 $grade_items[$gid]->needsupdate = 1;
1034 }
2e53372c 1035
f8e6e4db 1036 // construct depends_on lookup array
1037 $depends_on[$gid] = $grade_items[$gid]->depends_on();
1038 }
2e53372c 1039
d14ae855 1040 $errors = array();
b8ff92b6 1041 $finalids = array();
d14ae855 1042 $gids = array_keys($grade_items);
eacd3700 1043 $failed = 0;
d14ae855 1044
1045 while (count($finalids) < count($gids)) { // work until all grades are final or error found
1046 $count = 0;
1047 foreach ($gids as $gid) {
1048 if (in_array($gid, $finalids)) {
1049 continue; // already final
1050 }
1051
1052 if (!$grade_items[$gid]->needsupdate) {
1053 $finalids[] = $gid; // we can make it final - does not need update
b8ff92b6 1054 continue;
1055 }
1056
b8ff92b6 1057 $doupdate = true;
f8e6e4db 1058 foreach ($depends_on[$gid] as $did) {
b8ff92b6 1059 if (!in_array($did, $finalids)) {
1060 $doupdate = false;
d14ae855 1061 continue; // this item depends on something that is not yet in finals array
b8ff92b6 1062 }
1063 }
1064
1065 //oki - let's update, calculate or aggregate :-)
1066 if ($doupdate) {
d14ae855 1067 $result = $grade_items[$gid]->regrade_final_grades($userid);
f8e6e4db 1068
1069 if ($result === true) {
d14ae855 1070 $grade_items[$gid]->regrading_finished();
fb0e3570 1071 $grade_items[$gid]->check_locktime(); // do the locktime item locking
f8e6e4db 1072 $count++;
b8ff92b6 1073 $finalids[] = $gid;
fb0e3570 1074
f8e6e4db 1075 } else {
d14ae855 1076 $grade_items[$gid]->force_regrading();
f8e6e4db 1077 $errors[$gid] = $result;
b8ff92b6 1078 }
1079 }
1080 }
1081
1082 if ($count == 0) {
eacd3700 1083 $failed++;
1084 } else {
1085 $failed = 0;
1086 }
1087
1088 if ($failed > 1) {
d14ae855 1089 foreach($gids as $gid) {
1090 if (in_array($gid, $finalids)) {
1091 continue; // this one is ok
1092 }
1093 $grade_items[$gid]->force_regrading();
1094 $errors[$grade_items[$gid]->id] = 'Probably circular reference or broken calculation formula'; // TODO: localize
b8ff92b6 1095 }
d14ae855 1096 break; // oki, found error
b8ff92b6 1097 }
1098 }
1099
1100 if (count($errors) == 0) {
fb0e3570 1101 if (empty($userid)) {
1102 // do the locktime locking of grades, but only when doing full regrading
fed7cdc9 1103 grade_grade::check_locktime_all($gids);
fb0e3570 1104 }
b8ff92b6 1105 return true;
1106 } else {
1107 return $errors;
1108 }
a8995b34 1109}
967f222f 1110
ac9b0805 1111/**
f0362b5d 1112 * Refetches data from all course activities
13ba9036
AD
1113 * @param int $courseid the course ID
1114 * @param string $modname limit the grade fetch to a single module type
1115 * @param int $userid limit the grade fetch to a single user
ba21c9d4 1116 * @return void
ac9b0805 1117 */
13ba9036 1118function grade_grab_course_grades($courseid, $modname=null, $userid=0) {
9718765e 1119 global $CFG, $DB;
ac9b0805 1120
f0362b5d 1121 if ($modname) {
1122 $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname
9718765e 1123 FROM {".$modname."} a, {course_modules} cm, {modules} m
1124 WHERE m.name=:modname AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid";
1125 $params = array('modname'=>$modname, 'courseid'=>$courseid);
f0362b5d 1126
9718765e 1127 if ($modinstances = $DB->get_records_sql($sql, $params)) {
f0362b5d 1128 foreach ($modinstances as $modinstance) {
13ba9036 1129 grade_update_mod_grades($modinstance, $userid);
f0362b5d 1130 }
1131 }
1132 return;
1133 }
1134
17da2e6f 1135 if (!$mods = get_plugin_list('mod') ) {
2f137aa1 1136 print_error('nomodules', 'debug');
ac9b0805 1137 }
1138
17da2e6f 1139 foreach ($mods as $mod => $fullmod) {
ac9b0805 1140 if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it
1141 continue;
1142 }
1143
ac9b0805 1144 // include the module lib once
1145 if (file_exists($fullmod.'/lib.php')) {
f0362b5d 1146 // get all instance of the activity
1147 $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname
9718765e 1148 FROM {".$mod."} a, {course_modules} cm, {modules} m
1149 WHERE m.name=:mod AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=:courseid";
1150 $params = array('mod'=>$mod, 'courseid'=>$courseid);
f0362b5d 1151
9718765e 1152 if ($modinstances = $DB->get_records_sql($sql, $params)) {
f0362b5d 1153 foreach ($modinstances as $modinstance) {
13ba9036 1154 grade_update_mod_grades($modinstance, $userid);
f0362b5d 1155 }
ac9b0805 1156 }
1157 }
1158 }
1159}
1160
d185c3ee 1161/**
398a160d 1162 * Force full update of module grades in central gradebook
ba21c9d4 1163 *
1164 * @global object
1165 * @global object
d185c3ee 1166 * @param object $modinstance object with extra cmidnumber and modname property
ba21c9d4 1167 * @param int $userid
d185c3ee 1168 * @return boolean success
1169 */
2b0f65e2 1170function grade_update_mod_grades($modinstance, $userid=0) {
9718765e 1171 global $CFG, $DB;
d185c3ee 1172
1173 $fullmod = $CFG->dirroot.'/mod/'.$modinstance->modname;
1174 if (!file_exists($fullmod.'/lib.php')) {
653a8648 1175 debugging('missing lib.php file in module ' . $modinstance->modname);
d185c3ee 1176 return false;
1177 }
1178 include_once($fullmod.'/lib.php');
1179
d185c3ee 1180 $updategradesfunc = $modinstance->modname.'_update_grades';
1181 $updateitemfunc = $modinstance->modname.'_grade_item_update';
1182
398a160d 1183 if (function_exists($updategradesfunc) and function_exists($updateitemfunc)) {
d185c3ee 1184 //new grading supported, force updating of grades
1185 $updateitemfunc($modinstance);
2b0f65e2 1186 $updategradesfunc($modinstance, $userid);
d185c3ee 1187
1188 } else {
2b0f65e2 1189 // mudule does not support grading??
d185c3ee 1190 }
1191
1192 return true;
1193}
de420c11 1194
b51ece5b 1195/**
1196 * Remove grade letters for given context
ba21c9d4 1197 *
16ef46e7 1198 * @param context $context
ba21c9d4 1199 * @param bool $showfeedback
b51ece5b 1200 */
1201function remove_grade_letters($context, $showfeedback) {
aa9a6867 1202 global $DB, $OUTPUT;
9718765e 1203
b51ece5b 1204 $strdeleted = get_string('deleted');
1205
9718765e 1206 $DB->delete_records('grade_letters', array('contextid'=>$context->id));
b51ece5b 1207 if ($showfeedback) {
16ef46e7 1208 echo $OUTPUT->notification($strdeleted.' - '.get_string('letters', 'grades'), 'notifysuccess');
b51ece5b 1209 }
1210}
16ef46e7 1211
f615fbab 1212/**
1213 * Remove all grade related course data - history is kept
ba21c9d4 1214 *
f615fbab 1215 * @param int $courseid
e2b347e9 1216 * @param bool $showfeedback print feedback
f615fbab 1217 */
1218function remove_course_grades($courseid, $showfeedback) {
aa9a6867 1219 global $DB, $OUTPUT;
9718765e 1220
16ef46e7 1221 $fs = get_file_storage();
f615fbab 1222 $strdeleted = get_string('deleted');
1223
1224 $course_category = grade_category::fetch_course_category($courseid);
1225 $course_category->delete('coursedelete');
16ef46e7 1226 $fs->delete_area_files(get_context_instance(CONTEXT_COURSE, $courseid)->id, 'grade', 'feedback');
f615fbab 1227 if ($showfeedback) {
16ef46e7 1228 echo $OUTPUT->notification($strdeleted.' - '.get_string('grades', 'grades').', '.get_string('items', 'grades').', '.get_string('categories', 'grades'), 'notifysuccess');
f615fbab 1229 }
1230
1231 if ($outcomes = grade_outcome::fetch_all(array('courseid'=>$courseid))) {
1232 foreach ($outcomes as $outcome) {
1233 $outcome->delete('coursedelete');
1234 }
1235 }
9718765e 1236 $DB->delete_records('grade_outcomes_courses', array('courseid'=>$courseid));
f615fbab 1237 if ($showfeedback) {
16ef46e7 1238 echo $OUTPUT->notification($strdeleted.' - '.get_string('outcomes', 'grades'), 'notifysuccess');
f615fbab 1239 }
1240
1241 if ($scales = grade_scale::fetch_all(array('courseid'=>$courseid))) {
1242 foreach ($scales as $scale) {
1243 $scale->delete('coursedelete');
1244 }
1245 }
1246 if ($showfeedback) {
16ef46e7 1247 echo $OUTPUT->notification($strdeleted.' - '.get_string('scales'), 'notifysuccess');
f615fbab 1248 }
b51ece5b 1249
9718765e 1250 $DB->delete_records('grade_settings', array('courseid'=>$courseid));
b51ece5b 1251 if ($showfeedback) {
16ef46e7 1252 echo $OUTPUT->notification($strdeleted.' - '.get_string('settings', 'grades'), 'notifysuccess');
b51ece5b 1253 }
f615fbab 1254}
bfe7297e 1255
e2b347e9 1256/**
1257 * Called when course category deleted - cleanup gradebook
ba21c9d4 1258 *
1259 * @global object
e2b347e9 1260 * @param int $categoryid course category id
1261 * @param int $newparentid empty means everything deleted, otherwise id of category where content moved
1262 * @param bool $showfeedback print feedback
1263 */
1264function grade_course_category_delete($categoryid, $newparentid, $showfeedback) {
9718765e 1265 global $DB;
1266
e2b347e9 1267 $context = get_context_instance(CONTEXT_COURSECAT, $categoryid);
9718765e 1268 $DB->delete_records('grade_letters', array('contextid'=>$context->id));
e2b347e9 1269}
1270
8a0a6046 1271/**
1272 * Does gradebook cleanup when module uninstalled.
ba21c9d4 1273 *
1274 * @global object
1275 * @global object
1276 * @param string $modname
8a0a6046 1277 */
1278function grade_uninstalled_module($modname) {
9718765e 1279 global $CFG, $DB;
8a0a6046 1280
1281 $sql = "SELECT *
9718765e 1282 FROM {grade_items}
1283 WHERE itemtype='mod' AND itemmodule=?";
8a0a6046 1284
1285 // go all items for this module and delete them including the grades
b967c541
EL
1286 $rs = $DB->get_recordset_sql($sql, array($modname));
1287 foreach ($rs as $item) {
1288 $grade_item = new grade_item($item, false);
1289 $grade_item->delete('moduninstall');
8a0a6046 1290 }
b967c541 1291 $rs->close();
8a0a6046 1292}
1293
df997f84
PS
1294/**
1295 * Deletes all user data from gradebook.
1296 * @param $userid
1297 */
1298function grade_user_delete($userid) {
1299 if ($grades = grade_grade::fetch_all(array('userid'=>$userid))) {
1300 foreach ($grades as $grade) {
1301 $grade->delete('userdelete');
1302 }
1303 }
1304}
1305
1306/**
1307 * Purge course data when user unenrolled.
1308 * @param $userid
1309 */
1310function grade_user_unenrol($courseid, $userid) {
1311 if ($items = grade_item::fetch_all(array('courseid'=>$courseid))) {
1312 foreach ($items as $item) {
1313 if ($grades = grade_grade::fetch_all(array('userid'=>$userid, 'itemid'=>$item->id))) {
1314 foreach ($grades as $grade) {
1315 $grade->delete('userdelete');
1316 }
1317 }
1318 }
1319 }
1320}
1321
2650c51e 1322/**
1323 * Grading cron job
ba21c9d4 1324 *
1325 * @global object
1326 * @global object
2650c51e 1327 */
1328function grade_cron() {
9718765e 1329 global $CFG, $DB;
26101be8 1330
1331 $now = time();
1332
1333 $sql = "SELECT i.*
9718765e 1334 FROM {grade_items} i
1335 WHERE i.locked = 0 AND i.locktime > 0 AND i.locktime < ? AND EXISTS (
1336 SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
26101be8 1337
2650c51e 1338 // go through all courses that have proper final grades and lock them if needed
b967c541
EL
1339 $rs = $DB->get_recordset_sql($sql, array($now));
1340 foreach ($rs as $item) {
1341 $grade_item = new grade_item($item, false);
1342 $grade_item->locked = $now;
1343 $grade_item->update('locktime');
2650c51e 1344 }
b967c541 1345 $rs->close();
26101be8 1346
fcac8e51 1347 $grade_inst = new grade_grade();
1348 $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
1349
1350 $sql = "SELECT $fields
9718765e 1351 FROM {grade_grades} g, {grade_items} i
1352 WHERE g.locked = 0 AND g.locktime > 0 AND g.locktime < ? AND g.itemid=i.id AND EXISTS (
1353 SELECT 'x' FROM {grade_items} c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
26101be8 1354
1355 // go through all courses that have proper final grades and lock them if needed
b967c541
EL
1356 $rs = $DB->get_recordset_sql($sql, array($now));
1357 foreach ($rs as $grade) {
1358 $grade_grade = new grade_grade($grade, false);
1359 $grade_grade->locked = $now;
1360 $grade_grade->update('locktime');
26101be8 1361 }
b967c541 1362 $rs->close();
26101be8 1363
1ee0df06 1364 //TODO: do not run this cleanup every cron invocation
1365 // cleanup history tables
f0362b5d 1366 if (!empty($CFG->gradehistorylifetime)) { // value in days
1367 $histlifetime = $now - ($CFG->gradehistorylifetime * 3600 * 24);
1368 $tables = array('grade_outcomes_history', 'grade_categories_history', 'grade_items_history', 'grade_grades_history', 'scale_history');
1369 foreach ($tables as $table) {
9718765e 1370 if ($DB->delete_records_select($table, "timemodified < ?", array($histlifetime))) {
f0362b5d 1371 mtrace(" Deleted old grade history records from '$table'");
1ee0df06 1372 }
1373 }
f0362b5d 1374 }
1375}
1376
1377/**
1378 * Resel all course grades
ba21c9d4 1379 *
f0362b5d 1380 * @param int $courseid
ba21c9d4 1381 * @return bool success
f0362b5d 1382 */
1383function grade_course_reset($courseid) {
1384
1385 // no recalculations
1386 grade_force_full_regrading($courseid);
1387
1388 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
1389 foreach ($grade_items as $gid=>$grade_item) {
1390 $grade_item->delete_all_grades('reset');
1391 }
1ee0df06 1392
f0362b5d 1393 //refetch all grades
1394 grade_grab_course_grades($courseid);
1ee0df06 1395
f0362b5d 1396 // recalculate all grades
1397 grade_regrade_final_grades($courseid);
1398 return true;
2650c51e 1399}
1400
b45d8391 1401/**
f162c15a 1402 * Convert number to 5 decimalfloat, empty string or null db compatible format
66690b69 1403 * (we need this to decide if db value changed)
ba21c9d4 1404 *
1405 * @param mixed $number
b45d8391 1406 * @return mixed float or null
1407 */
1408function grade_floatval($number) {
66690b69 1409 if (is_null($number) or $number === '') {
b45d8391 1410 return null;
1411 }
66690b69 1412 // we must round to 5 digits to get the same precision as in 10,5 db fields
25bcd908 1413 // note: db rounding for 10,5 is different from php round() function
66690b69 1414 return round($number, 5);
b45d8391 1415}
25bcd908 1416
1417/**
1418 * Compare two float numbers safely. Uses 5 decimals php precision. Nulls accepted too.
1419 * Used for skipping of db updates
ba21c9d4 1420 *
25bcd908 1421 * @param float $f1
1422 * @param float $f2
ba21c9d4 1423 * @return bool true if different
25bcd908 1424 */
1425function grade_floats_different($f1, $f2) {
1426 // note: db rounding for 10,5 is different from php round() function
1427 return (grade_floatval($f1) !== grade_floatval($f2));
1428}
1429
f162c15a 1430/**
1431 * Compare two float numbers safely. Uses 5 decimals php precision.
1432 *
1433 * Do not use rounding for 10,5 at the database level as the results may be
1434 * different from php round() function.
1435 *
1436 * @since 2.0
1437 * @param float $f1
1438 * @param float $f2
1439 * @return bool true if the values should be considered as the same grades
1440 */
1441function grade_floats_equal($f1, $f2) {
1442 return (grade_floatval($f1) === grade_floatval($f2));
1443}