PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/MoodleWebRole/lib/gradelib.php

#
PHP | 1440 lines | 936 code | 232 blank | 272 comment | 234 complexity | 9b5f63f77b233f841a5f56753a3f3e74 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, LGPL-2.0, GPL-2.0

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file