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