Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
<?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';
            }
        }
< $name = self::get_cached_grade_name($course->id, $this->gradeitemid);
> // 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);
> } } > > /** protected function get_debug_string() { > * Gets the grade name at display time. $out = '#' . $this->gradeitemid; > * if (!is_null($this->min)) { > * @param \course_modinfo $modinfo Modinfo $out .= ' >= ' . sprintf('%.5f', $this->min); > * @param \context $context Context } > * @param string[] $params Parameters (just grade item id) if (!is_null($this->max)) { > * @return string Text value if (!is_null($this->min)) { > */ $out .= ','; > public static function get_description_callback_value( } > \course_modinfo $modinfo, \context $context, array $params): string { $out .= ' < ' . sprintf('%.5f', $this->max); > if (count($params) !== 1 || !is_number($params[0])) { } > return '<!-- Invalid grade description callback -->'; return $out; > } } > $gradeitemid = (int)$params[0]; > return self::get_cached_grade_name($modinfo->get_course_id(), $gradeitemid);
/** * 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; } } }