/availability/condition/grade/classes/condition.php
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
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * Condition on grades of current user.
- *
- * @package availability_grade
- * @copyright 2014 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- namespace availability_grade;
- defined('MOODLE_INTERNAL') || die();
- /**
- * Condition on grades of current user.
- *
- * @package availability_grade
- * @copyright 2014 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class condition extends \core_availability\condition {
- /** @var int Grade item id */
- private $gradeitemid;
- /** @var float|null Min grade (must be >= this) or null if none */
- private $min;
- /** @var float|null Max grade (must be < this) or null if none */
- private $max;
- /**
- * Constructor.
- *
- * @param \stdClass $structure Data structure from JSON decode
- * @throws \coding_exception If invalid data structure.
- */
- public function __construct($structure) {
- // Get grade item id.
- if (isset($structure->id) && is_int($structure->id)) {
- $this->gradeitemid = $structure->id;
- } else {
- throw new \coding_exception('Missing or invalid ->id for grade condition');
- }
- // Get min and max.
- if (!property_exists($structure, 'min')) {
- $this->min = null;
- } else if (is_float($structure->min) || is_int($structure->min)) {
- $this->min = $structure->min;
- } else {
- throw new \coding_exception('Missing or invalid ->min for grade condition');
- }
- if (!property_exists($structure, 'max')) {
- $this->max = null;
- } else if (is_float($structure->max) || is_int($structure->max)) {
- $this->max = $structure->max;
- } else {
- throw new \coding_exception('Missing or invalid ->max for grade condition');
- }
- }
- public function save() {
- $result = (object)array('type' => 'grade', 'id' => $this->gradeitemid);
- if (!is_null($this->min)) {
- $result->min = $this->min;
- }
- if (!is_null($this->max)) {
- $result->max = $this->max;
- }
- return $result;
- }
- /**
- * Returns a JSON object which corresponds to a condition of this type.
- *
- * Intended for unit testing, as normally the JSON values are constructed
- * by JavaScript code.
- *
- * @param int $gradeitemid Grade item id
- * @param number|null $min Min grade (or null if no min)
- * @param number|null $max Max grade (or null if no max)
- * @return stdClass Object representing condition
- */
- public static function get_json($gradeitemid, $min = null, $max = null) {
- $result = (object)array('type' => 'grade', 'id' => (int)$gradeitemid);
- if (!is_null($min)) {
- $result->min = $min;
- }
- if (!is_null($max)) {
- $result->max = $max;
- }
- return $result;
- }
- public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
- $course = $info->get_course();
- $score = $this->get_cached_grade_score($this->gradeitemid, $course->id, $grabthelot, $userid);
- $allow = $score !== false &&
- (is_null($this->min) || $score >= $this->min) &&
- (is_null($this->max) || $score < $this->max);
- if ($not) {
- $allow = !$allow;
- }
- return $allow;
- }
- public function get_description($full, $not, \core_availability\info $info) {
- $course = $info->get_course();
- // String depends on type of requirement. We are coy about
- // the actual numbers, in case grades aren't released to
- // students.
- if (is_null($this->min) && is_null($this->max)) {
- $string = 'any';
- } else if (is_null($this->max)) {
- $string = 'min';
- } else if (is_null($this->min)) {
- $string = 'max';
- } else {
- $string = 'range';
- }
- if ($not) {
- // The specific strings don't make as much sense with 'not'.
- if ($string === 'any') {
- $string = 'notany';
- } else {
- $string = 'notgeneral';
- }
- }
- // We cannot get the name at this point because it requires format_string which is not
- // allowed here. Instead, get it later with the callback function below.
- $name = $this->description_callback([$this->gradeitemid]);
- return get_string('requires_' . $string, 'availability_grade', $name);
- }
- /**
- * Gets the grade name at display time.
- *
- * @param \course_modinfo $modinfo Modinfo
- * @param \context $context Context
- * @param string[] $params Parameters (just grade item id)
- * @return string Text value
- */
- public static function get_description_callback_value(
- \course_modinfo $modinfo, \context $context, array $params): string {
- if (count($params) !== 1 || !is_number($params[0])) {
- return '<!-- Invalid grade description callback -->';
- }
- $gradeitemid = (int)$params[0];
- return self::get_cached_grade_name($modinfo->get_course_id(), $gradeitemid);
- }
- protected function get_debug_string() {
- $out = '#' . $this->gradeitemid;
- if (!is_null($this->min)) {
- $out .= ' >= ' . sprintf('%.5f', $this->min);
- }
- if (!is_null($this->max)) {
- if (!is_null($this->min)) {
- $out .= ',';
- }
- $out .= ' < ' . sprintf('%.5f', $this->max);
- }
- return $out;
- }
- /**
- * Obtains the name of a grade item, also checking that it exists. Uses a
- * cache. The name returned is suitable for display.
- *
- * @param int $courseid Course id
- * @param int $gradeitemid Grade item id
- * @return string Grade name or empty string if no grade with that id
- */
- private static function get_cached_grade_name($courseid, $gradeitemid) {
- global $DB, $CFG;
- require_once($CFG->libdir . '/gradelib.php');
- // Get all grade item names from cache, or using db query.
- $cache = \cache::make('availability_grade', 'items');
- if (($cacheditems = $cache->get($courseid)) === false) {
- // We cache the whole items table not the name; the format_string
- // call for the name might depend on current user (e.g. multilang)
- // and this is a shared cache.
- $cacheditems = $DB->get_records('grade_items', array('courseid' => $courseid));
- $cache->set($courseid, $cacheditems);
- }
- // Return name from cached item or a lang string.
- if (!array_key_exists($gradeitemid, $cacheditems)) {
- return get_string('missing', 'availability_grade');
- }
- $gradeitemobj = $cacheditems[$gradeitemid];
- $item = new \grade_item;
- \grade_object::set_properties($item, $gradeitemobj);
- return $item->get_name();
- }
- /**
- * Obtains a grade score. Note that this score should not be displayed to
- * the user, because gradebook rules might prohibit that. It may be a
- * non-final score subject to adjustment later.
- *
- * @param int $gradeitemid Grade item ID we're interested in
- * @param int $courseid Course id
- * @param bool $grabthelot If true, grabs all scores for current user on
- * this course, so that later ones come from cache
- * @param int $userid Set if requesting grade for a different user (does
- * not use cache)
- * @return float Grade score as a percentage in range 0-100 (e.g. 100.0
- * or 37.21), or false if user does not have a grade yet
- */
- protected static function get_cached_grade_score($gradeitemid, $courseid,
- $grabthelot=false, $userid=0) {
- global $USER, $DB;
- if (!$userid) {
- $userid = $USER->id;
- }
- $cache = \cache::make('availability_grade', 'scores');
- if (($cachedgrades = $cache->get($userid)) === false) {
- $cachedgrades = array();
- }
- if (!array_key_exists($gradeitemid, $cachedgrades)) {
- if ($grabthelot) {
- // Get all grades for the current course.
- $rs = $DB->get_recordset_sql('
- SELECT
- gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
- FROM
- {grade_items} gi
- LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
- WHERE
- gi.courseid = ?', array($userid, $courseid));
- foreach ($rs as $record) {
- // This function produces division by zero error warnings when rawgrademax and rawgrademin
- // are equal. Below change does not affect function behavior, just avoids the warning.
- if (is_null($record->finalgrade) || $record->rawgrademax == $record->rawgrademin) {
- // No grade = false.
- $cachedgrades[$record->id] = false;
- } else {
- // Otherwise convert grade to percentage.
- $cachedgrades[$record->id] =
- (($record->finalgrade - $record->rawgrademin) * 100) /
- ($record->rawgrademax - $record->rawgrademin);
- }
- }
- $rs->close();
- // And if it's still not set, well it doesn't exist (eg
- // maybe the user set it as a condition, then deleted the
- // grade item) so we call it false.
- if (!array_key_exists($gradeitemid, $cachedgrades)) {
- $cachedgrades[$gradeitemid] = false;
- }
- } else {
- // Just get current grade.
- $record = $DB->get_record('grade_grades', array(
- 'userid' => $userid, 'itemid' => $gradeitemid));
- // This function produces division by zero error warnings when rawgrademax and rawgrademin
- // are equal. Below change does not affect function behavior, just avoids the warning.
- if ($record && !is_null($record->finalgrade) && $record->rawgrademax != $record->rawgrademin) {
- $score = (($record->finalgrade - $record->rawgrademin) * 100) /
- ($record->rawgrademax - $record->rawgrademin);
- } else {
- // Treat the case where row exists but is null, same as
- // case where row doesn't exist.
- $score = false;
- }
- $cachedgrades[$gradeitemid] = $score;
- }
- $cache->set($userid, $cachedgrades);
- }
- return $cachedgrades[$gradeitemid];
- }
- public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name) {
- global $DB;
- $rec = \restore_dbops::get_backup_ids_record($restoreid, 'grade_item', $this->gradeitemid);
- if (!$rec || !$rec->newitemid) {
- // If we are on the same course (e.g. duplicate) then we can just
- // use the existing one.
- if ($DB->record_exists('grade_items',
- array('id' => $this->gradeitemid, 'courseid' => $courseid))) {
- return false;
- }
- // Otherwise it's a warning.
- $this->gradeitemid = 0;
- $logger->process('Restored item (' . $name .
- ') has availability condition on grade that was not restored',
- \backup::LOG_WARNING);
- } else {
- $this->gradeitemid = (int)$rec->newitemid;
- }
- return true;
- }
- public function update_dependency_id($table, $oldid, $newid) {
- if ($table === 'grade_items' && (int)$this->gradeitemid === (int)$oldid) {
- $this->gradeitemid = $newid;
- return true;
- } else {
- return false;
- }
- }
- }