MDL-12373 - If you can see your user profile, but don't have moodle/course:viewpartic...
[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
0b5a80a1 1080 if (!empty($oldgrades->grades)) {
1081 $grades = array();
d185c3ee 1082
0b5a80a1 1083 foreach ($oldgrades->grades as $uid=>$usergrade) {
1084 if ($userid and $uid != $userid) {
1085 continue;
1086 }
1087 $grade = new object();
1088 $grade->userid = $uid;
d185c3ee 1089
0b5a80a1 1090 if ($usergrade == '-') {
1091 // no grade
1092 $grade->rawgrade = null;
d185c3ee 1093
0b5a80a1 1094 } else if ($scaleid) {
1095 // scale in use, words used
1096 $gradescale = explode(",", $scale->scale);
1097 $grade->rawgrade = array_search($usergrade, $gradescale) + 1;
1098
1099 } else {
1100 // good old numeric value
1101 $grade->rawgrade = $usergrade;
1102 }
1103 $grades[] = $grade;
d185c3ee 1104 }
d185c3ee 1105
0b5a80a1 1106 grade_update('legacygrab', $grade_item->courseid, $grade_item->itemtype, $grade_item->itemmodule,
1107 $grade_item->iteminstance, $grade_item->itemnumber, $grades);
1108 }
d185c3ee 1109 }
1110
1111 } else if (function_exists($updategradesfunc) and function_exists($updateitemfunc)) {
1112 //new grading supported, force updating of grades
1113 $updateitemfunc($modinstance);
2b0f65e2 1114 $updategradesfunc($modinstance, $userid);
d185c3ee 1115
1116 } else {
2b0f65e2 1117 // mudule does not support grading??
d185c3ee 1118 }
1119
1120 return true;
1121}
de420c11 1122
1123/**
d185c3ee 1124 * Get and update/create grade item for legacy modules.
de420c11 1125 */
1126function grade_get_legacy_grade_item($modinstance, $grademax, $scaleid) {
1127
1128 // does it already exist?
42ff9ce6 1129 if ($grade_items = grade_item::fetch_all(array('courseid'=>$modinstance->course, 'itemtype'=>'mod', 'itemmodule'=>$modinstance->modname, 'iteminstance'=>$modinstance->id, 'itemnumber'=>0))) {
de420c11 1130 if (count($grade_items) > 1) {
d185c3ee 1131 debugging('Multiple legacy grade_items found.');
de420c11 1132 return false;
1133 }
1134
1135 $grade_item = reset($grade_items);
de420c11 1136
d185c3ee 1137 if (is_null($grademax) and is_null($scaleid)) {
1138 $grade_item->gradetype = GRADE_TYPE_NONE;
de420c11 1139
d185c3ee 1140 } else if ($scaleid) {
1141 $grade_item->gradetype = GRADE_TYPE_SCALE;
1142 $grade_item->scaleid = $scaleid;
97d608ba 1143 $grade_item->grademin = 1;
de420c11 1144
d185c3ee 1145 } else {
97d608ba 1146 $grade_item->gradetype = GRADE_TYPE_VALUE;
1147 $grade_item->grademax = $grademax;
1148 $grade_item->grademin = 0;
de420c11 1149 }
1150
d185c3ee 1151 $grade_item->itemname = $modinstance->name;
1152 $grade_item->idnumber = $modinstance->cmidnumber;
de420c11 1153
d185c3ee 1154 $grade_item->update();
de420c11 1155
1156 return $grade_item;
1157 }
612607bd 1158
de420c11 1159 // create new one
d185c3ee 1160 $params = array('courseid' =>$modinstance->course,
de420c11 1161 'itemtype' =>'mod',
1162 'itemmodule' =>$modinstance->modname,
1163 'iteminstance'=>$modinstance->id,
d185c3ee 1164 'itemnumber' =>0,
de420c11 1165 'itemname' =>$modinstance->name,
1166 'idnumber' =>$modinstance->cmidnumber);
1167
d185c3ee 1168 if (is_null($grademax) and is_null($scaleid)) {
1169 $params['gradetype'] = GRADE_TYPE_NONE;
1170
1171 } else if ($scaleid) {
612607bd 1172 $params['gradetype'] = GRADE_TYPE_SCALE;
de420c11 1173 $params['scaleid'] = $scaleid;
b3ac6c3e 1174 $grade_item->grademin = 1;
de420c11 1175 } else {
612607bd 1176 $params['gradetype'] = GRADE_TYPE_VALUE;
de420c11 1177 $params['grademax'] = $grademax;
1178 $params['grademin'] = 0;
1179 }
1180
f70152b7 1181 $grade_item = new grade_item($params);
1182 $grade_item->insert();
de420c11 1183
f70152b7 1184 return $grade_item;
de420c11 1185}
1186
b51ece5b 1187/**
1188 * Remove grade letters for given context
1189 * @param object $context
1190 */
1191function remove_grade_letters($context, $showfeedback) {
1192 $strdeleted = get_string('deleted');
1193
1194 delete_records('grade_letters', 'contextid', $context->id);
1195 if ($showfeedback) {
1196 notify($strdeleted.' - '.get_string('letters', 'grades'));
1197 }
1198}
f615fbab 1199/**
1200 * Remove all grade related course data - history is kept
1201 * @param int $courseid
6b5c722d 1202 * @param bool @showfeedback print feedback
f615fbab 1203 */
1204function remove_course_grades($courseid, $showfeedback) {
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 }
1218 delete_records('grade_outcomes_courses', 'courseid', $courseid);
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
1232 delete_records('grade_settings', 'courseid', $courseid);
1233 if ($showfeedback) {
1234 notify($strdeleted.' - '.get_string('settings', 'grades'));
1235 }
f615fbab 1236}
bfe7297e 1237
32b97bb2 1238/**
1239 * Builds an array of percentages indexed by integers for the purpose of building a select drop-down element.
1240 * @param int $steps The value between each level.
1241 * @param string $order 'asc' for 0-100 and 'desc' for 100-0
1242 * @param int $lowest The lowest value to include
1243 * @param int $highest The highest value to include
1244 */
1245function build_percentages_array($steps=1, $order='desc', $lowest=0, $highest=100) {
1246 // TODO reject or implement
1247}
42ff9ce6 1248
2650c51e 1249/**
1250 * Grading cron job
1251 */
1252function grade_cron() {
26101be8 1253 global $CFG;
1254
1255 $now = time();
1256
1257 $sql = "SELECT i.*
1258 FROM {$CFG->prefix}grade_items i
1259 WHERE i.locked = 0 AND i.locktime > 0 AND i.locktime < $now AND EXISTS (
1f4a0320 1260 SELECT 'x' FROM {$CFG->prefix}grade_items c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
26101be8 1261
2650c51e 1262 // go through all courses that have proper final grades and lock them if needed
26101be8 1263 if ($rs = get_recordset_sql($sql)) {
03cedd62 1264 while ($item = rs_fetch_next_record($rs)) {
1265 $grade_item = new grade_item($item, false);
1266 $grade_item->locked = $now;
1267 $grade_item->update('locktime');
2650c51e 1268 }
1269 rs_close($rs);
1270 }
26101be8 1271
fcac8e51 1272 $grade_inst = new grade_grade();
1273 $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
1274
1275 $sql = "SELECT $fields
26101be8 1276 FROM {$CFG->prefix}grade_grades g, {$CFG->prefix}grade_items i
1277 WHERE g.locked = 0 AND g.locktime > 0 AND g.locktime < $now AND g.itemid=i.id AND EXISTS (
1f4a0320 1278 SELECT 'x' FROM {$CFG->prefix}grade_items c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
26101be8 1279
1280 // go through all courses that have proper final grades and lock them if needed
1281 if ($rs = get_recordset_sql($sql)) {
03cedd62 1282 while ($grade = rs_fetch_next_record($rs)) {
1283 $grade_grade = new grade_grade($grade, false);
1284 $grade_grade->locked = $now;
1285 $grade_grade->update('locktime');
26101be8 1286 }
1287 rs_close($rs);
1288 }
1289
1ee0df06 1290 //TODO: do not run this cleanup every cron invocation
1291 // cleanup history tables
f0362b5d 1292 if (!empty($CFG->gradehistorylifetime)) { // value in days
1293 $histlifetime = $now - ($CFG->gradehistorylifetime * 3600 * 24);
1294 $tables = array('grade_outcomes_history', 'grade_categories_history', 'grade_items_history', 'grade_grades_history', 'scale_history');
1295 foreach ($tables as $table) {
1296 if (delete_records_select($table, "timemodified < $histlifetime")) {
1297 mtrace(" Deleted old grade history records from '$table'");
1ee0df06 1298 }
1299 }
f0362b5d 1300 }
1301}
1302
1303/**
1304 * Resel all course grades
1305 * @param int $courseid
1306 * @return success
1307 */
1308function grade_course_reset($courseid) {
1309
1310 // no recalculations
1311 grade_force_full_regrading($courseid);
1312
1313 $grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
1314 foreach ($grade_items as $gid=>$grade_item) {
1315 $grade_item->delete_all_grades('reset');
1316 }
1ee0df06 1317
f0362b5d 1318 //refetch all grades
1319 grade_grab_course_grades($courseid);
1ee0df06 1320
f0362b5d 1321 // recalculate all grades
1322 grade_regrade_final_grades($courseid);
1323 return true;
2650c51e 1324}
1325
60cf7430 1326?>