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/>.

/**
 * Compontent definition of a gradeitem.
 *
 * @package   core_grades
 * @copyright Andrew Nicols <andrew@nicols.co.uk>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

declare(strict_types = 1);

namespace core_grades;

use context;
use gradingform_controller;
use gradingform_instance;
use moodle_exception;
use stdClass;
use grade_item as core_gradeitem;
use grading_manager;

/**
 * Compontent definition of a gradeitem.
 *
 * @package   core_grades
 * @copyright Andrew Nicols <andrew@nicols.co.uk>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
abstract class component_gradeitem {

    /** @var array The scale data for the current grade item */
    protected $scale;

    /** @var string The component */
    protected $component;

    /** @var context The context for this activity */
    protected $context;

    /** @var string The item name */
    protected $itemname;

    /** @var int The grade itemnumber */
    protected $itemnumber;

    /**
     * component_gradeitem constructor.
     *
     * @param string $component
     * @param context $context
     * @param string $itemname
     * @throws \coding_exception
     */
    final protected function __construct(string $component, context $context, string $itemname) {
        $this->component = $component;
        $this->context = $context;
        $this->itemname = $itemname;
        $this->itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
    }

    /**
     * Fetch an instance of a specific component_gradeitem.
     *
     * @param string $component
     * @param context $context
     * @param string $itemname
     * @return self
     */
    public static function instance(string $component, context $context, string $itemname): self {
        $itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);

        $classname = "{$component}\\grades\\{$itemname}_gradeitem";
        if (!class_exists($classname)) {
< throw new coding_exception("Unknown gradeitem {$itemname} for component {$classname}");
> throw new \coding_exception("Unknown gradeitem {$itemname} for component {$classname}");
} return $classname::load_from_context($context); } /** * Load an instance of the current component_gradeitem based on context. * * @param context $context * @return self */ abstract public static function load_from_context(context $context): self; /** * The table name used for grading. * * @return string */ abstract protected function get_table_name(): string; /** * Get the itemid for the current gradeitem. * * @return int */ public function get_grade_itemid(): int { return component_gradeitems::get_itemnumber_from_itemname($this->component, $this->itemname); } /** * Whether grading is enabled for this item. * * @return bool */ abstract public function is_grading_enabled(): bool; /** * Get the grade value for this instance. * The itemname is translated to the relevant grade field for the activity. * * @return int */ abstract protected function get_gradeitem_value(): ?int; /** * Whether the grader can grade the gradee. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @return bool */ abstract public function user_can_grade(stdClass $gradeduser, stdClass $grader): bool; /** * Require that the user can grade, throwing an exception if not. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading
< * @throws required_capability_exception
> * @throws \required_capability_exception
*/ abstract public function require_user_can_grade(stdClass $gradeduser, stdClass $grader): void; /** * Get the scale if a scale is being used. * * @return stdClass */ protected function get_scale(): ?stdClass { global $DB; $gradetype = $this->get_gradeitem_value(); if ($gradetype > 0) { return null; } // This is a scale. if (null === $this->scale) { $this->scale = $DB->get_record('scale', ['id' => -1 * $gradetype]); } return $this->scale; } /** * Check whether a scale is being used for this grade item. * * @return bool */ public function is_using_scale(): bool { $gradetype = $this->get_gradeitem_value(); return $gradetype < 0; } /** * Whether this grade item is configured to use direct grading. * * @return bool */ public function is_using_direct_grading(): bool { if ($this->is_using_scale()) { return false; } if ($this->get_advanced_grading_controller()) { return false; } return true; } /** * Whether this grade item is configured to use advanced grading. * * @return bool */ public function is_using_advanced_grading(): bool { if ($this->is_using_scale()) { return false; } if ($this->get_advanced_grading_controller()) { return true; } return false; } /** * Get the name of the advanced grading method. * * @return string */ public function get_advanced_grading_method(): ?string { $gradingmanager = $this->get_grading_manager(); if (empty($gradingmanager)) { return null; } return $gradingmanager->get_active_method(); } /** * Get the name of the component responsible for grading this gradeitem. * * @return string */ public function get_grading_component_name(): ?string { if (!$this->is_grading_enabled()) { return null; } if ($method = $this->get_advanced_grading_method()) { return "gradingform_{$method}"; } return 'core_grades'; } /** * Get the name of the component subtype responsible for grading this gradeitem. * * @return string */ public function get_grading_component_subtype(): ?string { if (!$this->is_grading_enabled()) { return null; } if ($method = $this->get_advanced_grading_method()) { return null; } if ($this->is_using_scale()) { return 'scale'; } return 'point'; } /** * Whether decimals are allowed. * * @return bool */ protected function allow_decimals(): bool { return $this->get_gradeitem_value() > 0; } /** * Get the grading manager for this advanced grading definition. * * @return grading_manager */ protected function get_grading_manager(): ?grading_manager { require_once(__DIR__ . '/../grading/lib.php'); return get_grading_manager($this->context, $this->component, $this->itemname); } /** * Get the advanced grading controller if advanced grading is enabled. * * @return gradingform_controller */ protected function get_advanced_grading_controller(): ?gradingform_controller { $gradingmanager = $this->get_grading_manager(); if (empty($gradingmanager)) { return null; } if ($gradingmethod = $gradingmanager->get_active_method()) { return $gradingmanager->get_controller($gradingmethod); } return null; } /** * Get the list of available grade items. * * @return array */ public function get_grade_menu(): array { return make_grades_menu($this->get_gradeitem_value()); } /** * Check whether the supplied grade is valid and throw an exception if not. * * @param float $grade The value being checked * @throws moodle_exception * @return bool */ public function check_grade_validity(?float $grade): bool { $grade = grade_floatval(unformat_float($grade)); if ($grade) { if ($this->is_using_scale()) { // Fetch all options for this scale. $scaleoptions = make_menu_from_list($this->get_scale()->scale); if ($grade != -1 && !array_key_exists((int) $grade, $scaleoptions)) { // The selected option did not exist. throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 'maxgrade' => count($scaleoptions), 'grade' => $grade, ]); } } else if ($grade) { $maxgrade = $this->get_gradeitem_value(); if ($grade > $maxgrade) { // The grade is greater than the maximum possible value. throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 'maxgrade' => $maxgrade, 'grade' => $grade, ]); } else if ($grade < 0) { // Negative grades are not supported. throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 'maxgrade' => $maxgrade, 'grade' => $grade, ]); } } } return true; } /** * Create an empty row in the grade for the specified user and grader. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @return stdClass The newly created grade record */ abstract public function create_empty_grade(stdClass $gradeduser, stdClass $grader): stdClass; /** * Get the grade record for the specified grade id. * * @param int $gradeid * @return stdClass * @throws \dml_exception */ public function get_grade(int $gradeid): stdClass { global $DB;
< $grade = $DB->get_record($this->get_table_name(), ['id' => $gradeid]); < < return $grade ?: null;
> return $DB->get_record($this->get_table_name(), ['id' => $gradeid]);
} /** * Get the grade for the specified user. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @return stdClass The grade value */ abstract public function get_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass;
> > /** /** > * Returns the grade that should be displayed to the user. * Get the grade status for the specified user. > * * If the user has a grade as defined by the implementor return true else return false. > * The grade does not necessarily return a float value, this method takes grade settings into considering so * > * the correct value be shown, eg. a float vs a letter. * @param stdClass $gradeduser The user being graded > * * @return bool The grade status > * @param stdClass $gradeduser */ > * @param stdClass $grader abstract public function user_has_grade(stdClass $gradeduser): bool; > * @return stdClass|null > */ /** > public function get_formatted_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass { * Get grades for all users for the specified gradeitem. > global $DB; * > * @return stdClass[] The grades > if ($grade = $this->get_grade_for_user($gradeduser, $grader)) { */ > $gradeitem = $this->get_grade_item(); abstract public function get_all_grades(): array; > if (!$this->is_using_scale()) { > $grade->grade = !is_null($grade->grade) ? (float)$grade->grade : null; // Cast non-null values, keeping nulls. /** > $grade->usergrade = grade_format_gradevalue($grade->grade, $gradeitem); * Get the grade item instance id. > $grade->maxgrade = format_float($gradeitem->grademax, $gradeitem->get_decimals()); * > // If displaying the raw grade, also display the total value. * This is typically the cmid in the case of an activity, and relates to the iteminstance field in the grade_items > if ($gradeitem->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) { * table. > $grade->usergrade .= ' / ' . $grade->maxgrade; * > } * @return int > } else { */ > $grade->usergrade = '-'; abstract public function get_grade_instance_id(): int; > if ($scale = $DB->get_record('scale', ['id' => $gradeitem->scaleid])) { > $options = make_menu_from_list($scale->scale); /** > * Get the core grade item from the current component grade item. > $gradeint = (int) $grade->grade; * This is mainly used to access the max grade for a gradeitem > if (isset($options[$gradeint])) { * > $grade->usergrade = $options[$gradeint]; * @return \grade_item The grade item > } */ > } public function get_grade_item(): \grade_item { > global $CFG; > $grade->maxgrade = format_float($gradeitem->grademax, $gradeitem->get_decimals()); require_once("{$CFG->libdir}/gradelib.php"); > } > [$itemtype, $itemmodule] = \core_component::normalize_component($this->component); > return $grade; $gradeitem = \grade_item::fetch([ > } 'itemtype' => $itemtype, > 'itemmodule' => $itemmodule, > return null; 'itemnumber' => $this->itemnumber, > }
'iteminstance' => $this->get_grade_instance_id(), ]); return $gradeitem; } /** * Create or update the grade. * * @param stdClass $grade * @return bool Success */ abstract protected function store_grade(stdClass $grade): bool; /** * Create or update the grade. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading * @param stdClass $formdata The data submitted * @return bool Success */ public function store_grade_from_formdata(stdClass $gradeduser, stdClass $grader, stdClass $formdata): bool { // Require gradelib for grade_floatval. require_once(__DIR__ . '/../../lib/gradelib.php'); $grade = $this->get_grade_for_user($gradeduser, $grader); if ($this->is_using_advanced_grading()) { $instanceid = $formdata->instanceid; $gradinginstance = $this->get_advanced_grading_instance($grader, $grade, (int) $instanceid); $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); if ($grade->grade == -1) { // In advanced grading, a value of -1 means no data. return false; } } else { // Handle the case when grade is set to No Grade. if (isset($formdata->grade)) { $grade->grade = grade_floatval(unformat_float($formdata->grade)); } } return $this->store_grade($grade); } /** * Get the advanced grading instance for the specified grade entry. * * @param stdClass $grader The user who is grading * @param stdClass $grade The row from the grade table. * @param int $instanceid The instanceid of the advanced grading form * @return gradingform_instance */ public function get_advanced_grading_instance(stdClass $grader, stdClass $grade, int $instanceid = null): ?gradingform_instance { $controller = $this->get_advanced_grading_controller($this->itemname); if (empty($controller)) { // Advanced grading not enabeld for this item. return null; } if (!$controller->is_form_available()) { // The form is not available for this item. return null; } // Fetch the instance for the specified graderid/itemid. $gradinginstance = $controller->fetch_instance( (int) $grader->id, (int) $grade->id, $instanceid ); // Set the allowed grade range. $gradinginstance->get_controller()->set_grade_range( $this->get_grade_menu(), $this->allow_decimals() ); return $gradinginstance; } /** * Sends a notification about the item being graded for the student. * * @param stdClass $gradeduser The user being graded * @param stdClass $grader The user who is grading */ public function send_student_notification(stdClass $gradeduser, stdClass $grader): void { $contextname = $this->context->get_context_name(); $eventdata = new \core\message\message(); $eventdata->courseid = $this->context->get_course_context()->instanceid; $eventdata->component = 'moodle'; $eventdata->name = 'gradenotifications'; $eventdata->userfrom = $grader; $eventdata->userto = $gradeduser; $eventdata->subject = get_string('gradenotificationsubject', 'grades'); $eventdata->fullmessage = get_string('gradenotificationmessage', 'grades', $contextname); $eventdata->contexturl = $this->context->get_url(); $eventdata->contexturlname = $contextname; $eventdata->fullmessageformat = FORMAT_HTML; $eventdata->fullmessagehtml = ''; $eventdata->smallmessage = ''; $eventdata->notification = 1; message_send($eventdata); } }