2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Compontent definition of a gradeitem.
20 * @package core_grades
21 * @copyright Andrew Nicols <andrew@nicols.co.uk>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 declare(strict_types = 1);
27 namespace core_grades;
30 use gradingform_controller;
31 use gradingform_instance;
34 use grade_item as core_gradeitem;
38 * Compontent definition of a gradeitem.
40 * @package core_grades
41 * @copyright Andrew Nicols <andrew@nicols.co.uk>
42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 abstract class component_gradeitem {
46 /** @var array The scale data for the current grade item */
49 /** @var string The component */
52 /** @var context The context for this activity */
55 /** @var string The item name */
58 /** @var int The grade itemnumber */
59 protected $itemnumber;
61 final protected function __construct(string $component, context $context, string $itemname) {
62 $this->component = $component;
63 $this->context = $context;
64 $this->itemname = $itemname;
65 $this->itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
69 * Fetch an instance of a specific component_gradeitem.
71 * @param string $component
72 * @param context $context
73 * @param string $itemname
76 public static function instance(string $component, context $context, string $itemname): self {
77 $itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
79 $classname = "{$component}\\grades\\{$itemname}_gradeitem";
80 if (!class_exists($classname)) {
81 throw new coding_exception("Unknown gradeitem {$itemname} for component {$classname}");
84 return $classname::load_from_context($context);
88 * Load an instance of the current component_gradeitem based on context.
90 * @param context $context
93 abstract public static function load_from_context(context $context): self;
96 * The table name used for grading.
100 abstract protected function get_table_name(): string;
103 * Get the itemid for the current gradeitem.
107 public function get_grade_itemid(): int {
108 return component_gradeitems::get_itemnumber_from_itemname($this->component, $this->itemname);
112 * Whether grading is enabled for this item.
116 abstract public function is_grading_enabled(): bool;
119 * Get the grade value for this instance.
120 * The itemname is translated to the relevant grade field for the activity.
124 abstract protected function get_gradeitem_value(): ?int;
127 * Whether the grader can grade the gradee.
129 * @param stdClass $gradeduser The user being graded
130 * @param stdClass $grader The user who is grading
133 abstract public function user_can_grade(stdClass $gradeduser, stdClass $grader): bool;
136 * Require that the user can grade, throwing an exception if not.
138 * @param stdClass $gradeduser The user being graded
139 * @param stdClass $grader The user who is grading
140 * @throws required_capability_exception
142 abstract public function require_user_can_grade(stdClass $gradeduser, stdClass $grader): void;
145 * Get the scale if a scale is being used.
149 protected function get_scale(): ?stdClass {
152 $gradetype = $this->get_gradeitem_value();
153 if ($gradetype > 0) {
158 if (null === $this->scale) {
159 $this->scale = $DB->get_record('scale', ['id' => -1 * $gradetype]);
166 * Check whether a scale is being used for this grade item.
170 public function is_using_scale(): bool {
171 $gradetype = $this->get_gradeitem_value();
173 return $gradetype < 0;
177 * Whether this grade item is configured to use direct grading.
181 public function is_using_direct_grading(): bool {
182 if ($this->is_using_scale()) {
186 if ($this->get_advanced_grading_controller()) {
194 * Whether this grade item is configured to use advanced grading.
198 public function is_using_advanced_grading(): bool {
199 if ($this->is_using_scale()) {
203 if ($this->get_advanced_grading_controller()) {
211 * Get the name of the advanced grading method.
215 public function get_advanced_grading_method(): ?string {
216 $gradingmanager = $this->get_grading_manager();
218 if (empty($gradingmanager)) {
222 return $gradingmanager->get_active_method();
226 * Get the name of the component responsible for grading this gradeitem.
230 public function get_grading_component_name(): ?string {
231 if (!$this->is_grading_enabled()) {
235 if ($method = $this->get_advanced_grading_method()) {
236 return "gradingform_{$method}";
239 return 'core_grades';
243 * Get the name of the component subtype responsible for grading this gradeitem.
247 public function get_grading_component_subtype(): ?string {
248 if (!$this->is_grading_enabled()) {
252 if ($method = $this->get_advanced_grading_method()) {
256 if ($this->is_using_scale()) {
264 * Whether decimals are allowed.
268 protected function allow_decimals(): bool {
269 return $this->get_gradeitem_value() > 0;
273 * Get the grading manager for this advanced grading definition.
275 * @return grading_manager
277 protected function get_grading_manager(): ?grading_manager {
278 require_once(__DIR__ . '/../grading/lib.php');
279 return get_grading_manager($this->context, $this->component, $this->itemname);
284 * Get the advanced grading controller if advanced grading is enabled.
286 * @return gradingform_controller
288 protected function get_advanced_grading_controller(): ?gradingform_controller {
289 $gradingmanager = $this->get_grading_manager();
291 if (empty($gradingmanager)) {
295 if ($gradingmethod = $gradingmanager->get_active_method()) {
296 return $gradingmanager->get_controller($gradingmethod);
303 * Get the list of available grade items.
307 public function get_grade_menu(): array {
308 return make_grades_menu($this->get_gradeitem_value());
312 * Check whether the supplied grade is valid and throw an exception if not.
314 * @param float $grade The value being checked
315 * @throws moodle_exception
318 public function check_grade_validity(?float $grade): bool {
319 $grade = grade_floatval(unformat_float($grade));
321 if ($this->is_using_scale()) {
322 // Fetch all options for this scale.
323 $scaleoptions = make_menu_from_list($this->get_scale()->scale);
325 if ($grade != -1 && !array_key_exists((int) $grade, $scaleoptions)) {
326 // The selected option did not exist.
327 throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
328 'maxgrade' => count($scaleoptions),
333 $maxgrade = $this->get_gradeitem_value();
334 if ($grade > $maxgrade) {
335 // The grade is greater than the maximum possible value.
336 throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
337 'maxgrade' => $maxgrade,
340 } else if ($grade < 0) {
341 // Negative grades are not supported.
342 throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
343 'maxgrade' => $maxgrade,
354 * Create an empty row in the grade for the specified user and grader.
356 * @param stdClass $gradeduser The user being graded
357 * @param stdClass $grader The user who is grading
358 * @return stdClass The newly created grade record
360 abstract public function create_empty_grade(stdClass $gradeduser, stdClass $grader): stdClass;
363 * Get the grade record for the specified grade id.
365 * @param int $gradeid
368 public function get_grade(int $gradeid): stdClass {
371 $grade = $DB->get_record($this->get_table_name(), ['id' => $gradeid]);
373 return $grade ?: null;
377 * Get the grade for the specified user.
379 * @param stdClass $gradeduser The user being graded
380 * @param stdClass $grader The user who is grading
381 * @return stdClass The grade value
383 abstract public function get_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass;
386 * Get grades for all users for the specified gradeitem.
388 * @param int $itemnumber The specific grade item to fetch for the user
389 * @return stdClass[] The grades
391 abstract public function get_all_grades(): array;
394 * Create or update the grade.
396 * @param stdClass $grade
397 * @return bool Success
399 abstract protected function store_grade(stdClass $grade): bool;
402 * Create or update the grade.
404 * @param stdClass $gradeduser The user being graded
405 * @param stdClass $grader The user who is grading
406 * @param stdClass $formdata The data submitted
407 * @return bool Success
409 public function store_grade_from_formdata(stdClass $gradeduser, stdClass $grader, stdClass $formdata): bool {
410 // Require gradelib for grade_floatval.
411 require_once(__DIR__ . '/../../lib/gradelib.php');
412 $grade = $this->get_grade_for_user($gradeduser, $grader);
414 if ($this->is_using_advanced_grading()) {
415 $instanceid = $formdata->instanceid;
416 $gradinginstance = $this->get_advanced_grading_instance($grader, $grade, (int) $instanceid);
417 $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id);
419 // Handle the case when grade is set to No Grade.
420 if (isset($formdata->grade)) {
421 $grade->grade = grade_floatval(unformat_float($formdata->grade));
425 return $this->store_grade($grade);
429 * Get the advanced grading instance for the specified grade entry.
431 * @param stdClass $grader The user who is grading
432 * @param stdClass $grade The row from the grade table.
433 * @param int $instanceid The instanceid of the advanced grading form
434 * @return gradingform_instance
436 public function get_advanced_grading_instance(stdClass $grader, stdClass $grade, int $instanceid = null): ?gradingform_instance {
437 $controller = $this->get_advanced_grading_controller($this->itemname);
439 if (empty($controller)) {
440 // Advanced grading not enabeld for this item.
444 if (!$controller->is_form_available()) {
445 // The form is not available for this item.
449 // Fetch the instance for the specified graderid/itemid.
450 $gradinginstance = $controller->fetch_instance(
456 // Set the allowed grade range.
457 $gradinginstance->get_controller()->set_grade_range(
458 $this->get_grade_menu(),
459 $this->allow_decimals()
462 return $gradinginstance;