PageRenderTime 55ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/gradelib.php

https://bitbucket.org/ngmares/moodle
PHP | 1447 lines | 892 code | 218 blank | 337 comment | 204 complexity | e2cf733dec64974f359dc2979a4ea4fc MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, MPL-2.0-no-copyleft-exception, GPL-3.0, Apache-2.0, BSD-3-Clause

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

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

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