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