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