PageRenderTime 83ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/gradelib.php

https://bitbucket.org/ciceidev/cicei_moodle_conditional_activities
PHP | 1444 lines | 939 code | 232 blank | 273 comment | 235 complexity | bfbf9435ca15fb660e6f4ee960f95a7d MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause

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

  1. <?php // $Id$
  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$
  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. * @return boolean returns true if grade items were found and updated successfully
  274. */
  275. function grade_update_outcomes($source, $courseid, $itemtype, $itemmodule, $iteminstance, $userid, $data) {
  276. if ($items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) {
  277. $result = true;
  278. foreach ($items as $item) {
  279. if (!array_key_exists($item->itemnumber, $data)) {
  280. continue;
  281. }
  282. $grade = $data[$item->itemnumber] < 1 ? null : $data[$item->itemnumber];
  283. $result = ($item->update_final_grade($userid, $grade, $source) && $result);
  284. }
  285. return $result;
  286. }
  287. return false; //grade items not found
  288. }
  289. /**
  290. * Returns grading information for given activity - optionally with users grades
  291. * Manual, course or category items can not be queried.
  292. * @public
  293. * @param int $courseid id of course
  294. * @param string $itemtype 'mod', 'block'
  295. * @param string $itemmodule 'forum, 'quiz', etc.
  296. * @param int $iteminstance id of the item module
  297. * @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
  298. * @return array of grade information objects (scaleid, name, grade and locked status, etc.) indexed with itemnumbers
  299. */
  300. function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) {
  301. global $CFG;
  302. $return = new object();
  303. $return->items = array();
  304. $return->outcomes = array();
  305. $course_item = grade_item::fetch_course_item($courseid);
  306. $needsupdate = array();
  307. if ($course_item->needsupdate) {
  308. $result = grade_regrade_final_grades($courseid);
  309. if ($result !== true) {
  310. $needsupdate = array_keys($result);
  311. }
  312. }
  313. if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) {
  314. foreach ($grade_items as $grade_item) {
  315. $decimalpoints = null;
  316. if (empty($grade_item->outcomeid)) {
  317. // prepare information about grade item
  318. $item = new object();
  319. $item->itemnumber = $grade_item->itemnumber;
  320. $item->scaleid = $grade_item->scaleid;
  321. $item->name = $grade_item->get_name();
  322. $item->grademin = $grade_item->grademin;
  323. $item->grademax = $grade_item->grademax;
  324. $item->gradepass = $grade_item->gradepass;
  325. $item->locked = $grade_item->is_locked();
  326. $item->hidden = $grade_item->is_hidden();
  327. $item->grades = array();
  328. switch ($grade_item->gradetype) {
  329. case GRADE_TYPE_NONE:
  330. continue;
  331. case GRADE_TYPE_VALUE:
  332. $item->scaleid = 0;
  333. break;
  334. case GRADE_TYPE_TEXT:
  335. $item->scaleid = 0;
  336. $item->grademin = 0;
  337. $item->grademax = 0;
  338. $item->gradepass = 0;
  339. break;
  340. }
  341. if (empty($userid_or_ids)) {
  342. $userids = array();
  343. } else if (is_array($userid_or_ids)) {
  344. $userids = $userid_or_ids;
  345. } else {
  346. $userids = array($userid_or_ids);
  347. }
  348. if ($userids) {
  349. $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true);
  350. foreach ($userids as $userid) {
  351. $grade_grades[$userid]->grade_item =& $grade_item;
  352. $grade = new object();
  353. $grade->grade = $grade_grades[$userid]->finalgrade;
  354. $grade->locked = $grade_grades[$userid]->is_locked();
  355. $grade->hidden = $grade_grades[$userid]->is_hidden();
  356. $grade->overridden = $grade_grades[$userid]->overridden;
  357. $grade->feedback = $grade_grades[$userid]->feedback;
  358. $grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
  359. $grade->usermodified = $grade_grades[$userid]->usermodified;
  360. $grade->datesubmitted = $grade_grades[$userid]->get_datesubmitted();
  361. $grade->dategraded = $grade_grades[$userid]->get_dategraded();
  362. // create text representation of grade
  363. if ($grade_item->gradetype == GRADE_TYPE_TEXT or $grade_item->gradetype == GRADE_TYPE_NONE) {
  364. $grade->grade = null;
  365. $grade->str_grade = '-';
  366. $grade->str_long_grade = $grade->str_grade;
  367. } else if (in_array($grade_item->id, $needsupdate)) {
  368. $grade->grade = false;
  369. $grade->str_grade = get_string('error');
  370. $grade->str_long_grade = $grade->str_grade;
  371. } else if (is_null($grade->grade)) {
  372. $grade->str_grade = '-';
  373. $grade->str_long_grade = $grade->str_grade;
  374. } else {
  375. $grade->str_grade = grade_format_gradevalue($grade->grade, $grade_item);
  376. if ($grade_item->gradetype == GRADE_TYPE_SCALE or $grade_item->get_displaytype() != GRADE_DISPLAY_TYPE_REAL) {
  377. $grade->str_long_grade = $grade->str_grade;
  378. } else {
  379. $a = new object();
  380. $a->grade = $grade->str_grade;
  381. $a->max = grade_format_gradevalue($grade_item->grademax, $grade_item);
  382. $grade->str_long_grade = get_string('gradelong', 'grades', $a);
  383. }
  384. }
  385. // create html representation of feedback
  386. if (is_null($grade->feedback)) {
  387. $grade->str_feedback = '';
  388. } else {
  389. $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat);
  390. }
  391. $item->grades[$userid] = $grade;
  392. }
  393. }
  394. $return->items[$grade_item->itemnumber] = $item;
  395. } else {
  396. if (!$grade_outcome = grade_outcome::fetch(array('id'=>$grade_item->outcomeid))) {
  397. debugging('Incorect outcomeid found');
  398. continue;
  399. }
  400. // outcome info
  401. $outcome = new object();
  402. $outcome->itemnumber = $grade_item->itemnumber;
  403. $outcome->scaleid = $grade_outcome->scaleid;
  404. $outcome->name = $grade_outcome->get_name();
  405. $outcome->locked = $grade_item->is_locked();
  406. $outcome->hidden = $grade_item->is_hidden();
  407. if (empty($userid_or_ids)) {
  408. $userids = array();
  409. } else if (is_array($userid_or_ids)) {
  410. $userids = $userid_or_ids;
  411. } else {
  412. $userids = array($userid_or_ids);
  413. }
  414. if ($userids) {
  415. $grade_grades = grade_grade::fetch_users_grades($grade_item, $userids, true);
  416. foreach ($userids as $userid) {
  417. $grade_grades[$userid]->grade_item =& $grade_item;
  418. $grade = new object();
  419. $grade->grade = $grade_grades[$userid]->finalgrade;
  420. $grade->locked = $grade_grades[$userid]->is_locked();
  421. $grade->hidden = $grade_grades[$userid]->is_hidden();
  422. $grade->feedback = $grade_grades[$userid]->feedback;
  423. $grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
  424. $grade->usermodified = $grade_grades[$userid]->usermodified;
  425. // create text representation of grade
  426. if (in_array($grade_item->id, $needsupdate)) {
  427. $grade->grade = false;
  428. $grade->str_grade = get_string('error');
  429. } else if (is_null($grade->grade)) {
  430. $grade->grade = 0;
  431. $grade->str_grade = get_string('nooutcome', 'grades');
  432. } else {
  433. $grade->grade = (int)$grade->grade;
  434. $scale = $grade_item->load_scale();
  435. $grade->str_grade = format_string($scale->scale_items[(int)$grade->grade-1]);
  436. }
  437. // create html representation of feedback
  438. if (is_null($grade->feedback)) {
  439. $grade->str_feedback = '';
  440. } else {
  441. $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat);
  442. }
  443. $outcome->grades[$userid] = $grade;
  444. }
  445. }
  446. if (isset($return->outcomes[$grade_item->itemnumber])) {
  447. // itemnumber duplicates - lets fix them!
  448. $newnumber = $grade_item->itemnumber + 1;
  449. while(grade_item::fetch(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid, 'itemnumber'=>$newnumber))) {
  450. $newnumber++;
  451. }
  452. $outcome->itemnumber = $newnumber;
  453. $grade_item->itemnumber = $newnumber;
  454. $grade_item->update('system');
  455. }
  456. $return->outcomes[$grade_item->itemnumber] = $outcome;
  457. }
  458. }
  459. }
  460. // sort results using itemnumbers
  461. ksort($return->items, SORT_NUMERIC);
  462. ksort($return->outcomes, SORT_NUMERIC);
  463. return $return;
  464. }
  465. ///////////////////////////////////////////////////////////////////
  466. ///// End of public API for communication with modules/blocks /////
  467. ///////////////////////////////////////////////////////////////////
  468. ///////////////////////////////////////////////////////////////////
  469. ///// Internal API: used by gradebook plugins and Moodle core /////
  470. ///////////////////////////////////////////////////////////////////
  471. /**
  472. * Returns course gradebook setting
  473. * @param int $courseid
  474. * @param string $name of setting, maybe null if reset only
  475. * @param bool $resetcache force reset of internal static cache
  476. * @return string value, NULL if no setting
  477. */
  478. function grade_get_setting($courseid, $name, $default=null, $resetcache=false) {
  479. static $cache = array();
  480. if ($resetcache or !array_key_exists($courseid, $cache)) {
  481. $cache[$courseid] = array();
  482. } else if (is_null($name)) {
  483. return null;
  484. } else if (array_key_exists($name, $cache[$courseid])) {
  485. return $cache[$courseid][$name];
  486. }
  487. if (!$data = get_record('grade_settings', 'courseid', $courseid, 'name', addslashes($name))) {
  488. $result = null;
  489. } else {
  490. $result = $data->value;
  491. }
  492. if (is_null($result)) {
  493. $result = $default;
  494. }
  495. $cache[$courseid][$name] = $result;
  496. return $result;
  497. }
  498. /**
  499. * Returns all course gradebook settings as object properties
  500. * @param int $courseid
  501. * @return object
  502. */
  503. function grade_get_settings($courseid) {
  504. $settings = new object();
  505. $settings->id = $courseid;
  506. if ($records = get_records('grade_settings', 'courseid', $courseid)) {
  507. foreach ($records as $record) {
  508. $settings->{$record->name} = $record->value;
  509. }
  510. }
  511. return $settings;
  512. }
  513. /**
  514. * Add/update course gradebook setting
  515. * @param int $courseid
  516. * @param string $name of setting
  517. * @param string value, NULL means no setting==remove
  518. * @return void
  519. */
  520. function grade_set_setting($courseid, $name, $value) {
  521. if (is_null($value)) {
  522. delete_records('grade_settings', 'courseid', $courseid, 'name', addslashes($name));
  523. } else if (!$existing = get_record('grade_settings', 'courseid', $courseid, 'name', addslashes($name))) {
  524. $data = new object();
  525. $data->courseid = $courseid;
  526. $data->name = addslashes($name);
  527. $data->value = addslashes($value);
  528. insert_record('grade_settings', $data);
  529. } else {
  530. $data = new object();
  531. $data->id = $existing->id;
  532. $data->value = addslashes($value);
  533. update_record('grade_settings', $data);
  534. }
  535. grade_get_setting($courseid, null, null, true); // reset the cache
  536. }
  537. /**
  538. * Returns string representation of grade value
  539. * @param float $value grade value
  540. * @param object $grade_item - by reference to prevent scale reloading
  541. * @param bool $localized use localised decimal separator
  542. * @param int $displaytype type of display - GRADE_DISPLAY_TYPE_REAL, GRADE_DISPLAY_TYPE_PERCENTAGE, GRADE_DISPLAY_TYPE_LETTER
  543. * @param int $decimalplaces number of decimal places when displaying float values
  544. * @return string
  545. */
  546. function grade_format_gradevalue($value, &$grade_item, $localized=true, $displaytype=null, $decimals=null) {
  547. if ($grade_item->gradetype == GRADE_TYPE_NONE or $grade_item->gradetype == GRADE_TYPE_TEXT) {
  548. return '';
  549. }
  550. // no grade yet?
  551. if (is_null($value)) {
  552. return '-';
  553. }
  554. if ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) {
  555. //unknown type??
  556. return '';
  557. }
  558. if (is_null($displaytype)) {
  559. $displaytype = $grade_item->get_displaytype();
  560. }
  561. if (is_null($decimals)) {
  562. $decimals = $grade_item->get_decimals();
  563. }
  564. switch ($displaytype) {
  565. case GRADE_DISPLAY_TYPE_REAL:
  566. return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized);
  567. case GRADE_DISPLAY_TYPE_PERCENTAGE:
  568. return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized);
  569. case GRADE_DISPLAY_TYPE_LETTER:
  570. return grade_format_gradevalue_letter($value, $grade_item);
  571. case GRADE_DISPLAY_TYPE_REAL_PERCENTAGE:
  572. return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' .
  573. grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')';
  574. case GRADE_DISPLAY_TYPE_REAL_LETTER:
  575. return grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ' (' .
  576. grade_format_gradevalue_letter($value, $grade_item) . ')';
  577. case GRADE_DISPLAY_TYPE_PERCENTAGE_REAL:
  578. return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' .
  579. grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')';
  580. case GRADE_DISPLAY_TYPE_LETTER_REAL:
  581. return grade_format_gradevalue_letter($value, $grade_item) . ' (' .
  582. grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) . ')';
  583. case GRADE_DISPLAY_TYPE_LETTER_PERCENTAGE:
  584. return grade_format_gradevalue_letter($value, $grade_item) . ' (' .
  585. grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ')';
  586. case GRADE_DISPLAY_TYPE_PERCENTAGE_LETTER:
  587. return grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) . ' (' .
  588. grade_format_gradevalue_letter($value, $grade_item) . ')';
  589. default:
  590. return '';
  591. }
  592. }
  593. function grade_format_gradevalue_real($value, $grade_item, $decimals, $localized) {
  594. if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
  595. if (!$scale = $grade_item->load_scale()) {
  596. return get_string('error');
  597. }
  598. $value = $grade_item->bounded_grade($value);
  599. return format_string($scale->scale_items[$value-1]);
  600. } else {
  601. return format_float($value, $decimals, $localized);
  602. }
  603. }
  604. function grade_format_gradevalue_percentage($value, $grade_item, $decimals, $localized) {
  605. $min = $grade_item->grademin;
  606. $max = $grade_item->grademax;
  607. if ($min == $max) {
  608. return '';
  609. }
  610. $value = $grade_item->bounded_grade($value);
  611. $percentage = (($value-$min)*100)/($max-$min);
  612. return format_float($percentage, $decimals, $localized).' %';
  613. }
  614. function grade_format_gradevalue_letter($value, $grade_item) {
  615. $context = get_context_instance(CONTEXT_COURSE, $grade_item->courseid);
  616. if (!$letters = grade_get_letters($context)) {
  617. return ''; // no letters??
  618. }
  619. if (is_null($value)) {
  620. return '-';
  621. }
  622. $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100);
  623. $value = bounded_number(0, $value, 100); // just in case
  624. foreach ($letters as $boundary => $letter) {
  625. if ($value >= $boundary) {
  626. return format_string($letter);
  627. }
  628. }
  629. return '-'; // no match? maybe '' would be more correct
  630. }
  631. /**
  632. * Returns grade options for gradebook category menu
  633. * @param int $courseid
  634. * @param bool $includenew include option for new category (-1)
  635. * @return array of grade categories in course
  636. */
  637. function grade_get_categories_menu($courseid, $includenew=false) {
  638. $result = array();
  639. if (!$categories = grade_category::fetch_all(array('courseid'=>$courseid))) {
  640. //make sure course category exists
  641. if (!grade_category::fetch_course_category($courseid)) {
  642. debugging('Can not create course grade category!');
  643. return $result;
  644. }
  645. $categories = grade_category::fetch_all(array('courseid'=>$courseid));
  646. }
  647. foreach ($categories as $key=>$category) {
  648. if ($category->is_course_category()) {
  649. $result[$category->id] = get_string('uncategorised', 'grades');
  650. unset($categories[$key]);
  651. }
  652. }
  653. if ($includenew) {
  654. $result[-1] = get_string('newcategory', 'grades');
  655. }
  656. $cats = array();
  657. foreach ($categories as $category) {
  658. $cats[$category->id] = $category->get_name();
  659. }
  660. asort($cats, SORT_LOCALE_STRING);
  661. return ($result+$cats);
  662. }
  663. /**
  664. * Returns grade letters array used in context
  665. * @param object $context object or null for defaults
  666. * @return array of grade_boundary=>letter_string
  667. */
  668. function grade_get_letters($context=null) {
  669. if (empty($context)) {
  670. //default grading letters
  671. return array('93'=>'A', '90'=>'A-', '87'=>'B+', '83'=>'B', '80'=>'B-', '77'=>'C+', '73'=>'C', '70'=>'C-', '67'=>'D+', '60'=>'D', '0'=>'F');
  672. }
  673. static $cache = array();
  674. if (array_key_exists($context->id, $cache)) {
  675. return $cache[$context->id];
  676. }
  677. if (count($cache) > 100) {
  678. $cache = array(); // cache size limit
  679. }
  680. $letters = array();
  681. $contexts = get_parent_contexts($context);
  682. array_unshift($contexts, $context->id);
  683. foreach ($contexts as $ctxid) {
  684. if ($records = get_records('grade_letters', 'contextid', $ctxid, 'lowerboundary DESC')) {
  685. foreach ($records as $record) {
  686. $letters[$record->lowerboundary] = $record->letter;
  687. }
  688. }
  689. if (!empty($letters)) {
  690. $cache[$context->id] = $letters;
  691. return $letters;
  692. }
  693. }
  694. $letters = grade_get_letters(null);
  695. $cache[$context->id] = $letters;
  696. return $letters;
  697. }
  698. /**
  699. * Verify new value of idnumber - checks for uniqueness of new idnumbers, old are kept intact
  700. * @param string idnumber string (with magic quotes)
  701. * @param int $courseid - id numbers are course unique only
  702. * @param object $cm used for course module idnumbers and items attached to modules
  703. * @param object $gradeitem is item idnumber
  704. * @return boolean true means idnumber ok
  705. */
  706. function grade_verify_idnumber($idnumber, $courseid, $grade_item=null, $cm=null) {
  707. if ($idnumber == '') {
  708. //we allow empty idnumbers
  709. return true;
  710. }
  711. // keep existing even when not unique
  712. if ($cm and $cm->idnumber == $idnumber) {
  713. return true;
  714. } else if ($grade_item and $grade_item->idnumber == $idnumber) {
  715. return true;
  716. }
  717. if (get_records_select('course_modules', "course = $courseid AND idnumber='$idnumber'")) {
  718. return false;
  719. }
  720. if (get_records_select('grade_items', "courseid = $courseid AND idnumber='$idnumber'")) {
  721. return false;
  722. }
  723. return true;
  724. }
  725. /**
  726. * Force final grade recalculation in all course items
  727. * @param int $courseid
  728. */
  729. function grade_force_full_regrading($courseid) {
  730. set_field('grade_items', 'needsupdate', 1, 'courseid', $courseid);
  731. }
  732. /**
  733. * Forces regrading of all site grades - usualy when chanign site setings
  734. */
  735. function grade_force_site_regrading() {
  736. global $CFG;
  737. $sql = "UPDATE {$CFG->prefix}grade_items SET needsupdate=1";
  738. execute_sql($sql, false);
  739. }
  740. /**
  741. * Updates all final grades in course.
  742. *
  743. * @param int $courseid
  744. * @param int $userid if specified, try to do a quick regrading of grades of this user only
  745. * @param object $updated_item the item in which
  746. * @return boolean true if ok, array of errors if problems found (item id is used as key)
  747. */
  748. function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null) {
  749. $course_item = grade_item::fetch_course_item($courseid);
  750. if ($userid) {
  751. // one raw grade updated for one user
  752. if (empty($updated_item)) {
  753. error("updated_item_id can not be null!");
  754. }
  755. if ($course_item->needsupdate) {
  756. $updated_item->force_regrading();
  757. return array($course_item->id =>'Can not do fast regrading after updating of raw grades');
  758. }
  759. } else {
  760. if (!$course_item->needsupdate) {
  761. // nothing to do :-)
  762. return true;
  763. }
  764. }
  765. $grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
  766. $depends_on = array();
  767. // first mark all category and calculated items as needing regrading
  768. // this is slower, but 100% accurate
  769. foreach ($grade_items as $gid=>$gitem) {
  770. if (!empty($updated_item) and $updated_item->id == $gid) {
  771. $grade_items[$gid]->needsupdate = 1;
  772. } else if ($gitem->is_course_item() or $gitem->is_category_item() or $gitem->is_calculated()) {
  773. $grade_items[$gid]->needsupdate = 1;
  774. }
  775. // construct depends_on lookup array
  776. $depends_on[$gid] = $grade_items[$gid]->depends_on();
  777. }
  778. $errors = array();
  779. $finalids = array();
  780. $gids = array_keys($grade_items);
  781. $failed = 0;
  782. while (count($finalids) < count($gids)) { // work until all grades are final or error found
  783. $count = 0;
  784. foreach ($gids as $gid) {
  785. if (in_array($gid, $finalids)) {
  786. continue; // already final
  787. }
  788. if (!$grade_items[$gid]->needsupdate) {
  789. $finalids[] = $gid; // we can make it final - does not need update
  790. continue;
  791. }
  792. $doupdate = true;
  793. foreach ($depends_on[$gid] as $did) {
  794. if (!in_array($did, $finalids)) {
  795. $doupdate = false;
  796. continue; // this item depends on something that is not yet in finals array
  797. }
  798. }
  799. //oki - let's update, calculate or aggregate :-)
  800. if ($doupdate) {
  801. $result = $grade_items[$gid]->regrade_final_grades($userid);
  802. if ($result === true) {
  803. $grade_items[$gid]->regrading_finished();
  804. $grade_items[$gid]->check_locktime(); // do the locktime item locking
  805. $count++;
  806. $finalids[] = $gid;
  807. } else {
  808. $grade_items[$gid]->force_regrading();
  809. $errors[$gid] = $result;
  810. }
  811. }
  812. }
  813. if ($count == 0) {
  814. $failed++;
  815. } else {
  816. $failed = 0;
  817. }
  818. if ($failed > 1) {
  819. foreach($gids as $gid) {
  820. if (in_array($gid, $finalids)) {
  821. continue; // this one is ok
  822. }
  823. $grade_items[$gid]->force_regrading();
  824. $errors[$grade_items[$gid]->id] = 'Probably circular reference or broken calculation formula'; // TODO: localize
  825. }
  826. break; // oki, found error
  827. }
  828. }
  829. if (count($errors) == 0) {
  830. if (empty($userid)) {
  831. // do the locktime locking of grades, but only when doing full regrading
  832. grade_grade::check_locktime_all($gids);
  833. }
  834. return true;
  835. } else {
  836. return $errors;
  837. }
  838. }
  839. /**
  840. * For backwards compatibility with old third-party modules, this function can
  841. * be used to import all grades from activities with legacy grading.
  842. * @param int $courseid
  843. */
  844. function grade_grab_legacy_grades($courseid) {
  845. global $CFG;
  846. if (!$mods = get_list_of_plugins('mod') ) {
  847. error('No modules installed!');
  848. }
  849. foreach ($mods as $mod) {
  850. if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it
  851. continue;
  852. }
  853. $fullmod = $CFG->dirroot.'/mod/'.$mod;
  854. // include the module lib once
  855. if (file_exists($fullmod.'/lib.php')) {
  856. include_once($fullmod.'/lib.php');
  857. // look for modname_grades() function - old gradebook pulling function
  858. // if present sync the grades with new grading system
  859. $gradefunc = $mod.'_grades';
  860. if (function_exists($gradefunc)) {
  861. grade_grab_course_grades($courseid, $mod);
  862. }
  863. }
  864. }
  865. }
  866. /**
  867. * Refetches data from all course activities
  868. * @param int $courseid
  869. * @param string $modname
  870. * @return success
  871. */
  872. function grade_grab_course_grades($courseid, $modname=null) {
  873. global $CFG;
  874. if ($modname) {
  875. $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname
  876. FROM {$CFG->prefix}$modname a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
  877. WHERE m.name='$modname' AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=$courseid";
  878. if ($modinstances = get_records_sql($sql)) {
  879. foreach ($modinstances as $modinstance) {
  880. grade_update_mod_grades($modinstance);
  881. }
  882. }
  883. return;
  884. }
  885. if (!$mods = get_list_of_plugins('mod') ) {
  886. error('No modules installed!');
  887. }
  888. foreach ($mods as $mod) {
  889. if ($mod == 'NEWMODULE') { // Someone has unzipped the template, ignore it
  890. continue;
  891. }
  892. $fullmod = $CFG->dirroot.'/mod/'.$mod;
  893. // include the module lib once
  894. if (file_exists($fullmod.'/lib.php')) {
  895. // get all instance of the activity
  896. $sql = "SELECT a.*, cm.idnumber as cmidnumber, m.name as modname
  897. FROM {$CFG->prefix}$mod a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
  898. WHERE m.name='$mod' AND m.visible=1 AND m.id=cm.module AND cm.instance=a.id AND cm.course=$courseid";
  899. if ($modinstances = get_records_sql($sql)) {
  900. foreach ($modinstances as $modinstance) {
  901. grade_update_mod_grades($modinstance);
  902. }
  903. }
  904. }
  905. }
  906. }
  907. /**
  908. * Force full update of module grades in central gradebook - works for both legacy and converted activities.
  909. * @param object $modinstance object with extra cmidnumber and modname property
  910. * @return boolean success
  911. */
  912. function grade_update_mod_grades($modinstance, $userid=0) {
  913. global $CFG;
  914. $fullmod = $CFG->dirroot.'/mod/'.$modinstance->modname;
  915. if (!file_exists($fullmod.'/lib.php')) {
  916. debugging('missing lib.php file in module ' . $modinstance->modname);
  917. return false;
  918. }
  919. include_once($fullmod.'/lib.php');
  920. // does it use legacy grading?
  921. $gradefunc = $modinstance->modname.'_grades';
  922. $updategradesfunc = $modinstance->modname.'_update_grades';
  923. $updateitemfunc = $modinstance->modname.'_grade_item_update';
  924. if (function_exists($gradefunc)) {
  925. // legacy module - not yet converted
  926. if ($oldgrades = $gradefunc($modinstance->id)) {
  927. $grademax = $oldgrades->maxgrade;
  928. $scaleid = NULL;
  929. if (!is_numeric($grademax)) {
  930. // scale name is provided as a string, try to find it
  931. if (!$scale = get_record('scale', 'name', $grademax)) {
  932. debugging('Incorrect scale name! name:'.$grademax);
  933. return false;
  934. }
  935. $scaleid = $scale->id;
  936. }
  937. if (!$grade_item = grade_get_legacy_grade_item($modinstance, $grademax, $scaleid)) {
  938. debugging('Can not get/create legacy grade item!');
  939. return false;
  940. }
  941. if (!empty($oldgrades->grades)) {
  942. $grades = array();
  943. foreach ($oldgrades->grades as $uid=>$usergrade) {
  944. if ($userid and $uid != $userid) {
  945. continue;
  946. }
  947. $grade = new object();
  948. $grade->userid = $uid;
  949. if ($usergrade == '-') {
  950. // no grade
  951. $grade->rawgrade = null;
  952. } else if ($scaleid) {
  953. // scale in use, words used
  954. $gradescale = explode(",", $scale->scale);
  955. $grade->rawgrade = array_search($usergrade, $gradescale) + 1;
  956. } else {
  957. // good old numeric value
  958. $grade->rawgrade = $usergrade;
  959. }
  960. $grades[$uid] = $grade;
  961. }
  962. grade_update('legacygrab', $grade_item->courseid, $grade_item->itemtype, $grade_item->itemmodule,
  963. $grade_item->iteminstance, $grade_item->itemnumber, $grades);
  964. }
  965. }
  966. } else if (function_exists($updategradesfunc) and function_exists($updateitemfunc)) {
  967. //new grading supported, force updating of grades
  968. $updateitemfunc($modinstance);
  969. $updategradesfunc($modinstance, $userid);
  970. } else {
  971. // mudule does not support grading??
  972. }
  973. return true;
  974. }
  975. /**
  976. * Returns list of currently used mods with legacy grading in course
  977. * @param $courseid int
  978. * @return array of modname=>modulenamestring mods with legacy grading
  979. */
  980. function grade_get_legacy_modules($courseid) {
  981. global $CFG;
  982. if (!$mods = get_course_mods($courseid)) {
  983. return array();
  984. }
  985. $legacy = array();
  986. foreach ($mods as $mod) {
  987. $modname = $mod->modname;
  988. $modlib = "$CFG->dirroot/mod/$modname/lib.php";
  989. if (!$modlib) {
  990. continue;
  991. }
  992. include_once($modlib);
  993. $gradefunc = $modname.'_grades';
  994. if (!function_exists($gradefunc)) {
  995. continue;
  996. }
  997. $legacy[$modname] = get_string('modulename', $modname);
  998. }
  999. return $legacy;
  1000. }
  1001. /**
  1002. * Get and update/create grade item for legacy modules.
  1003. */
  1004. function grade_get_legacy_grade_item($modinstance, $grademax, $scaleid) {
  1005. // does it already exist?
  1006. if ($grade_items = grade_item::fetch_all(array('courseid'=>$modinstance->course, 'itemtype'=>'mod', 'itemmodule'=>$modinstance->modname, 'iteminstance'=>$modinstance->id, 'itemnumber'=>0))) {
  1007. if (count($grade_items) > 1) {
  1008. debugging('Multiple legacy grade_items found.');
  1009. return false;
  1010. }
  1011. $grade_item = reset($grade_items);
  1012. if (is_null($grademax) and is_null($scaleid)) {
  1013. $grade_item->gradetype = GRADE_TYPE_NONE;
  1014. } else if ($scaleid) {
  1015. $grade_item->gradetype = GRADE_TYPE_SCALE;
  1016. $grade_item->scaleid = $scaleid;
  1017. $grade_item->grademin = 1;
  1018. } else {
  1019. $grade_item->gradetype = GRADE_TYPE_VALUE;
  1020. $grade_item->grademax = $grademax;
  1021. $grade_item->grademin = 0;
  1022. }
  1023. $grade_item->itemname = $modinstance->name;
  1024. $grade_item->idnumber = $modinstance->cmidnumber;
  1025. $grade_item->update();
  1026. return $grade_item;
  1027. }
  1028. // create new one
  1029. $params = array('courseid' =>$modinstance->course,
  1030. 'itemtype' =>'mod',
  1031. 'itemmodule' =>$modinstance->modname,
  1032. 'iteminstance'=>$modinstance->id,
  1033. 'itemnumber' =>0,
  1034. 'itemname' =>$modinstance->name,
  1035. 'idnumber' =>$modinstance->cmidnumber);
  1036. if (is_null($grademax) and is_null($scaleid)) {
  1037. $params['gradetype'] = GRADE_TYPE_NONE;
  1038. } else if ($scaleid) {
  1039. $params['gradetype'] = GRADE_TYPE_SCALE;
  1040. $params['scaleid'] = $scaleid;
  1041. $grade_item->grademin = 1;
  1042. } else {
  1043. $params['gradetype'] = GRADE_TYPE_VALUE;
  1044. $params['grademax'] = $grademax;
  1045. $params['grademin'] = 0;
  1046. }
  1047. $grade_item = new grade_item($params);
  1048. $grade_item->insert();
  1049. return $grade_item;
  1050. }
  1051. /**
  1052. * Remove grade letters for given context
  1053. * @param object $context
  1054. */
  1055. function remove_grade_letters($context, $showfeedback) {
  1056. $strdeleted = get_string('deleted');
  1057. delete_records('grade_letters', 'contextid', $context->id);
  1058. if ($showfeedback) {
  1059. notify($strdeleted.' - '.get_string('letters', 'grades'));
  1060. }
  1061. }
  1062. /**
  1063. * Remove all grade related course data - history is kept
  1064. * @param int $courseid
  1065. * @param bool $showfeedback print feedback
  1066. */
  1067. function remove_course_grades($courseid, $showfeedback) {
  1068. $strdeleted = get_string('deleted');
  1069. $course_category = grade_category::fetch_course_category($courseid);
  1070. $course_category->delete('coursedelete');
  1071. if ($showfeedback) {
  1072. notify($strdeleted.' - '.get_string('grades', 'grades').', '.get_string('items', 'grades').', '.get_string('categories', 'grades'));
  1073. }
  1074. if ($outcomes = grade_outcome::fetch_all(array('courseid'=>$courseid))) {
  1075. foreach ($outcomes as $outcome) {
  1076. $outcome->delete('coursedelete');
  1077. }
  1078. }
  1079. delete_records('grade_outcomes_courses', 'courseid', $courseid);
  1080. if ($showfeedback) {
  1081. notify($strdeleted.' - '.get_string('outcomes', 'grades'));
  1082. }
  1083. if ($scales = grade_scale::fetch_all(array('courseid'=>$courseid))) {
  1084. foreach ($scales as $scale) {
  1085. $scale->delete('coursedelete');
  1086. }
  1087. }
  1088. if ($showfeedback) {
  1089. notify($strdeleted.' - '.get_string('scales'));
  1090. }
  1091. delete_records('grade_settings', 'courseid', $courseid);
  1092. if ($showfeedback) {
  1093. notify($strdeleted.' - '.get_string('settings', 'grades'));
  1094. }
  1095. }
  1096. /**
  1097. * Called when course category deleted - cleanup gradebook
  1098. * @param int $categoryid course category id
  1099. * @param int $newparentid empty means everything deleted, otherwise id of category where content moved
  1100. * @param bool $showfeedback print feedback
  1101. */
  1102. function grade_course_category_delete($categoryid, $newparentid, $showfeedback) {
  1103. $context = get_context_instance(CONTEXT_COURSECAT, $categoryid);
  1104. delete_records('grade_letters', 'contextid', $context->id);
  1105. }
  1106. /**
  1107. * Does gradebook cleanup when module uninstalled.
  1108. */
  1109. function grade_uninstalled_module($modname) {
  1110. global $CFG;
  1111. $sql = "SELECT *
  1112. FROM {$CFG->prefix}grade_items
  1113. WHERE itemtype='mod' AND itemmodule='$modname'";
  1114. // go all items for this module and delete them including the grades
  1115. if ($rs = get_recordset_sql($sql)) {
  1116. while ($item = rs_fetch_next_record($rs)) {
  1117. $grade_item = new grade_item($item, false);
  1118. $grade_item->delete('moduninstall');
  1119. }
  1120. rs_close($rs);
  1121. }
  1122. }
  1123. /**
  1124. * Grading cron job
  1125. */
  1126. function grade_cron() {
  1127. global $CFG;
  1128. $now = time();
  1129. $sql = "SELECT i.*
  1130. FROM {$CFG->prefix}grade_items i
  1131. WHERE i.locked = 0 AND i.locktime > 0 AND i.locktime < $now AND EXISTS (
  1132. SELECT 'x' FROM {$CFG->prefix}grade_items c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
  1133. // go through all courses that have proper final grades and lock them if needed
  1134. if ($rs = get_recordset_sql($sql)) {
  1135. while ($item = rs_fetch_next_record($rs)) {
  1136. $grade_item = new grade_item($item, false);
  1137. $grade_item->locked = $now;
  1138. $grade_item->update('locktime');
  1139. }
  1140. rs_close($rs);
  1141. }
  1142. $grade_inst = new grade_grade();
  1143. $fields = 'g.'.implode(',g.', $grade_inst->required_fields);
  1144. $sql = "SELECT $fields
  1145. FROM {$CFG->prefix}grade_grades g, {$CFG->prefix}grade_items i
  1146. WHERE g.locked = 0 AND g.locktime > 0 AND g.locktime < $now AND g.itemid=i.id AND EXISTS (
  1147. SELECT 'x' FROM {$CFG->prefix}grade_items c WHERE c.itemtype='course' AND c.needsupdate=0 AND c.courseid=i.courseid)";
  1148. // go through all courses that have proper final grades and lock them if needed
  1149. if ($rs = get_recordset_sql($sql)) {
  1150. while ($grade = rs_fetch_next_record($rs)) {
  1151. $grade_grade = new grade_grade($grade, false);
  1152. $grade_grade->locked = $now;
  1153. $grade_grade->update('locktime');
  1154. }
  1155. rs_close($rs);
  1156. }
  1157. //TODO: do not run this cleanup every cron invocation
  1158. // cleanup history tables
  1159. if (!empty($CFG->gradehistorylifetime)) { // value in days
  1160. $histlifetime = $now - ($CFG->gradehistorylifetime * 3600 * 24);
  1161. $tables = arr

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