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