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