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