PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/availability/condition/grade/classes/condition.php

https://bitbucket.org/moodle/moodle
PHP | 319 lines | 191 code | 21 blank | 107 comment | 60 complexity | 478fef068e0c5911a9f8356b8bf24ba0 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  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. * Condition on grades of current user.
  18. *
  19. * @package availability_grade
  20. * @copyright 2014 The Open University
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. namespace availability_grade;
  24. defined('MOODLE_INTERNAL') || die();
  25. /**
  26. * Condition on grades of current user.
  27. *
  28. * @package availability_grade
  29. * @copyright 2014 The Open University
  30. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31. */
  32. class condition extends \core_availability\condition {
  33. /** @var int Grade item id */
  34. private $gradeitemid;
  35. /** @var float|null Min grade (must be >= this) or null if none */
  36. private $min;
  37. /** @var float|null Max grade (must be < this) or null if none */
  38. private $max;
  39. /**
  40. * Constructor.
  41. *
  42. * @param \stdClass $structure Data structure from JSON decode
  43. * @throws \coding_exception If invalid data structure.
  44. */
  45. public function __construct($structure) {
  46. // Get grade item id.
  47. if (isset($structure->id) && is_int($structure->id)) {
  48. $this->gradeitemid = $structure->id;
  49. } else {
  50. throw new \coding_exception('Missing or invalid ->id for grade condition');
  51. }
  52. // Get min and max.
  53. if (!property_exists($structure, 'min')) {
  54. $this->min = null;
  55. } else if (is_float($structure->min) || is_int($structure->min)) {
  56. $this->min = $structure->min;
  57. } else {
  58. throw new \coding_exception('Missing or invalid ->min for grade condition');
  59. }
  60. if (!property_exists($structure, 'max')) {
  61. $this->max = null;
  62. } else if (is_float($structure->max) || is_int($structure->max)) {
  63. $this->max = $structure->max;
  64. } else {
  65. throw new \coding_exception('Missing or invalid ->max for grade condition');
  66. }
  67. }
  68. public function save() {
  69. $result = (object)array('type' => 'grade', 'id' => $this->gradeitemid);
  70. if (!is_null($this->min)) {
  71. $result->min = $this->min;
  72. }
  73. if (!is_null($this->max)) {
  74. $result->max = $this->max;
  75. }
  76. return $result;
  77. }
  78. /**
  79. * Returns a JSON object which corresponds to a condition of this type.
  80. *
  81. * Intended for unit testing, as normally the JSON values are constructed
  82. * by JavaScript code.
  83. *
  84. * @param int $gradeitemid Grade item id
  85. * @param number|null $min Min grade (or null if no min)
  86. * @param number|null $max Max grade (or null if no max)
  87. * @return stdClass Object representing condition
  88. */
  89. public static function get_json($gradeitemid, $min = null, $max = null) {
  90. $result = (object)array('type' => 'grade', 'id' => (int)$gradeitemid);
  91. if (!is_null($min)) {
  92. $result->min = $min;
  93. }
  94. if (!is_null($max)) {
  95. $result->max = $max;
  96. }
  97. return $result;
  98. }
  99. public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
  100. $course = $info->get_course();
  101. $score = $this->get_cached_grade_score($this->gradeitemid, $course->id, $grabthelot, $userid);
  102. $allow = $score !== false &&
  103. (is_null($this->min) || $score >= $this->min) &&
  104. (is_null($this->max) || $score < $this->max);
  105. if ($not) {
  106. $allow = !$allow;
  107. }
  108. return $allow;
  109. }
  110. public function get_description($full, $not, \core_availability\info $info) {
  111. $course = $info->get_course();
  112. // String depends on type of requirement. We are coy about
  113. // the actual numbers, in case grades aren't released to
  114. // students.
  115. if (is_null($this->min) && is_null($this->max)) {
  116. $string = 'any';
  117. } else if (is_null($this->max)) {
  118. $string = 'min';
  119. } else if (is_null($this->min)) {
  120. $string = 'max';
  121. } else {
  122. $string = 'range';
  123. }
  124. if ($not) {
  125. // The specific strings don't make as much sense with 'not'.
  126. if ($string === 'any') {
  127. $string = 'notany';
  128. } else {
  129. $string = 'notgeneral';
  130. }
  131. }
  132. // We cannot get the name at this point because it requires format_string which is not
  133. // allowed here. Instead, get it later with the callback function below.
  134. $name = $this->description_callback([$this->gradeitemid]);
  135. return get_string('requires_' . $string, 'availability_grade', $name);
  136. }
  137. /**
  138. * Gets the grade name at display time.
  139. *
  140. * @param \course_modinfo $modinfo Modinfo
  141. * @param \context $context Context
  142. * @param string[] $params Parameters (just grade item id)
  143. * @return string Text value
  144. */
  145. public static function get_description_callback_value(
  146. \course_modinfo $modinfo, \context $context, array $params): string {
  147. if (count($params) !== 1 || !is_number($params[0])) {
  148. return '<!-- Invalid grade description callback -->';
  149. }
  150. $gradeitemid = (int)$params[0];
  151. return self::get_cached_grade_name($modinfo->get_course_id(), $gradeitemid);
  152. }
  153. protected function get_debug_string() {
  154. $out = '#' . $this->gradeitemid;
  155. if (!is_null($this->min)) {
  156. $out .= ' >= ' . sprintf('%.5f', $this->min);
  157. }
  158. if (!is_null($this->max)) {
  159. if (!is_null($this->min)) {
  160. $out .= ',';
  161. }
  162. $out .= ' < ' . sprintf('%.5f', $this->max);
  163. }
  164. return $out;
  165. }
  166. /**
  167. * Obtains the name of a grade item, also checking that it exists. Uses a
  168. * cache. The name returned is suitable for display.
  169. *
  170. * @param int $courseid Course id
  171. * @param int $gradeitemid Grade item id
  172. * @return string Grade name or empty string if no grade with that id
  173. */
  174. private static function get_cached_grade_name($courseid, $gradeitemid) {
  175. global $DB, $CFG;
  176. require_once($CFG->libdir . '/gradelib.php');
  177. // Get all grade item names from cache, or using db query.
  178. $cache = \cache::make('availability_grade', 'items');
  179. if (($cacheditems = $cache->get($courseid)) === false) {
  180. // We cache the whole items table not the name; the format_string
  181. // call for the name might depend on current user (e.g. multilang)
  182. // and this is a shared cache.
  183. $cacheditems = $DB->get_records('grade_items', array('courseid' => $courseid));
  184. $cache->set($courseid, $cacheditems);
  185. }
  186. // Return name from cached item or a lang string.
  187. if (!array_key_exists($gradeitemid, $cacheditems)) {
  188. return get_string('missing', 'availability_grade');
  189. }
  190. $gradeitemobj = $cacheditems[$gradeitemid];
  191. $item = new \grade_item;
  192. \grade_object::set_properties($item, $gradeitemobj);
  193. return $item->get_name();
  194. }
  195. /**
  196. * Obtains a grade score. Note that this score should not be displayed to
  197. * the user, because gradebook rules might prohibit that. It may be a
  198. * non-final score subject to adjustment later.
  199. *
  200. * @param int $gradeitemid Grade item ID we're interested in
  201. * @param int $courseid Course id
  202. * @param bool $grabthelot If true, grabs all scores for current user on
  203. * this course, so that later ones come from cache
  204. * @param int $userid Set if requesting grade for a different user (does
  205. * not use cache)
  206. * @return float Grade score as a percentage in range 0-100 (e.g. 100.0
  207. * or 37.21), or false if user does not have a grade yet
  208. */
  209. protected static function get_cached_grade_score($gradeitemid, $courseid,
  210. $grabthelot=false, $userid=0) {
  211. global $USER, $DB;
  212. if (!$userid) {
  213. $userid = $USER->id;
  214. }
  215. $cache = \cache::make('availability_grade', 'scores');
  216. if (($cachedgrades = $cache->get($userid)) === false) {
  217. $cachedgrades = array();
  218. }
  219. if (!array_key_exists($gradeitemid, $cachedgrades)) {
  220. if ($grabthelot) {
  221. // Get all grades for the current course.
  222. $rs = $DB->get_recordset_sql('
  223. SELECT
  224. gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
  225. FROM
  226. {grade_items} gi
  227. LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
  228. WHERE
  229. gi.courseid = ?', array($userid, $courseid));
  230. foreach ($rs as $record) {
  231. // This function produces division by zero error warnings when rawgrademax and rawgrademin
  232. // are equal. Below change does not affect function behavior, just avoids the warning.
  233. if (is_null($record->finalgrade) || $record->rawgrademax == $record->rawgrademin) {
  234. // No grade = false.
  235. $cachedgrades[$record->id] = false;
  236. } else {
  237. // Otherwise convert grade to percentage.
  238. $cachedgrades[$record->id] =
  239. (($record->finalgrade - $record->rawgrademin) * 100) /
  240. ($record->rawgrademax - $record->rawgrademin);
  241. }
  242. }
  243. $rs->close();
  244. // And if it's still not set, well it doesn't exist (eg
  245. // maybe the user set it as a condition, then deleted the
  246. // grade item) so we call it false.
  247. if (!array_key_exists($gradeitemid, $cachedgrades)) {
  248. $cachedgrades[$gradeitemid] = false;
  249. }
  250. } else {
  251. // Just get current grade.
  252. $record = $DB->get_record('grade_grades', array(
  253. 'userid' => $userid, 'itemid' => $gradeitemid));
  254. // This function produces division by zero error warnings when rawgrademax and rawgrademin
  255. // are equal. Below change does not affect function behavior, just avoids the warning.
  256. if ($record && !is_null($record->finalgrade) && $record->rawgrademax != $record->rawgrademin) {
  257. $score = (($record->finalgrade - $record->rawgrademin) * 100) /
  258. ($record->rawgrademax - $record->rawgrademin);
  259. } else {
  260. // Treat the case where row exists but is null, same as
  261. // case where row doesn't exist.
  262. $score = false;
  263. }
  264. $cachedgrades[$gradeitemid] = $score;
  265. }
  266. $cache->set($userid, $cachedgrades);
  267. }
  268. return $cachedgrades[$gradeitemid];
  269. }
  270. public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name) {
  271. global $DB;
  272. $rec = \restore_dbops::get_backup_ids_record($restoreid, 'grade_item', $this->gradeitemid);
  273. if (!$rec || !$rec->newitemid) {
  274. // If we are on the same course (e.g. duplicate) then we can just
  275. // use the existing one.
  276. if ($DB->record_exists('grade_items',
  277. array('id' => $this->gradeitemid, 'courseid' => $courseid))) {
  278. return false;
  279. }
  280. // Otherwise it's a warning.
  281. $this->gradeitemid = 0;
  282. $logger->process('Restored item (' . $name .
  283. ') has availability condition on grade that was not restored',
  284. \backup::LOG_WARNING);
  285. } else {
  286. $this->gradeitemid = (int)$rec->newitemid;
  287. }
  288. return true;
  289. }
  290. public function update_dependency_id($table, $oldid, $newid) {
  291. if ($table === 'grade_items' && (int)$this->gradeitemid === (int)$oldid) {
  292. $this->gradeitemid = $newid;
  293. return true;
  294. } else {
  295. return false;
  296. }
  297. }
  298. }