MDL-66700 gradingform_guide: Support new grading panel
[moodle.git] / grade / classes / component_gradeitem.php
CommitLineData
f8da1b93
AN
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 * Compontent definition of a gradeitem.
19 *
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
23 */
24
25declare(strict_types = 1);
26
27namespace core_grades;
28
29use context;
30use gradingform_controller;
31use gradingform_instance;
32use moodle_exception;
33use stdClass;
34use grade_item as core_gradeitem;
35use grading_manager;
36
37/**
38 * Compontent definition of a gradeitem.
39 *
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
43 */
44abstract class component_gradeitem {
45
46 /** @var array The scale data for the current grade item */
47 protected $scale;
48
49 /** @var string The component */
50 protected $component;
51
52 /** @var context The context for this activity */
53 protected $context;
54
55 /** @var string The item name */
56 protected $itemname;
57
58 /** @var int The grade itemnumber */
59 protected $itemnumber;
60
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);
66 }
67
68 /**
69 * Fetch an instance of a specific component_gradeitem.
70 *
71 * @param string $component
72 * @param context $context
73 * @param string $itemname
74 * @return self
75 */
76 public static function instance(string $component, context $context, string $itemname): self {
77 $itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname);
78
79 $classname = "{$component}\\grades\\{$itemname}_gradeitem";
80 if (!class_exists($classname)) {
81 throw new coding_exception("Unknown gradeitem {$itemname} for component {$classname}");
82 }
83
84 return $classname::load_from_context($context);
85 }
86
87 /**
88 * Load an instance of the current component_gradeitem based on context.
89 *
90 * @param context $context
91 * @return self
92 */
93 abstract public static function load_from_context(context $context): self;
94
95 /**
96 * The table name used for grading.
97 *
98 * @return string
99 */
100 abstract protected function get_table_name(): string;
101
102 /**
103 * Whether grading is enabled for this item.
104 *
105 * @return bool
106 */
107 abstract public function is_grading_enabled(): bool;
108
109 /**
110 * Get the grade value for this instance.
111 * The itemname is translated to the relevant grade field for the activity.
112 *
113 * @return int
114 */
115 abstract protected function get_gradeitem_value(): ?int;
116
117 /**
118 * Whether the grader can grade the gradee.
119 *
120 * @param stdClass $gradeduser The user being graded
121 * @param stdClass $grader The user who is grading
122 * @return bool
123 */
124 abstract public function user_can_grade(stdClass $gradeduser, stdClass $grader): bool;
125
126 /**
127 * Require that the user can grade, throwing an exception if not.
128 *
129 * @param stdClass $gradeduser The user being graded
130 * @param stdClass $grader The user who is grading
131 * @throws required_capability_exception
132 */
133 abstract public function require_user_can_grade(stdClass $gradeduser, stdClass $grader): void;
134
135 /**
136 * Get the scale if a scale is being used.
137 *
138 * @return stdClass
139 */
140 protected function get_scale(): ?stdClass {
141 $gradetype = $this->get_gradeitem_value();
142 if ($gradetype > 0) {
143 return null;
144 }
145
146 // This is a scale.
147 if (null === $this->scale) {
148 $this->scale = $DB->get_record('scale', ['id' => -1 * $gradetype]);
149 }
150
151 return $this->scale;
152 }
153
154 /**
155 * Check whether a scale is being used for this grade item.
156 *
157 * @return bool
158 */
159 protected function is_using_scale(): bool {
160 $gradetype = $this->get_gradeitem_value();
161
162 return $gradetype < 0;
163 }
164
165 /**
166 * Whether this grade item is configured to use direct grading.
167 *
168 * @return bool
169 */
170 public function is_using_direct_grading(): bool {
171 if ($this->is_using_scale()) {
172 return false;
173 }
174
175 if ($this->get_advanced_grading_controller()) {
176 return false;
177 }
178
179 return true;
180 }
181
182 /**
183 * Whether this grade item is configured to use advanced grading.
184 *
185 * @return bool
186 */
187 public function is_using_advanced_grading(): bool {
188 if ($this->is_using_scale()) {
189 return false;
190 }
191
192 if ($this->get_advanced_grading_controller()) {
193 return true;
194 }
195
196 return false;
197 }
198
199 /**
200 * Get the name of the advanced grading method.
201 *
202 * @return string
203 */
204 public function get_advanced_grading_method(): ?string {
205 $gradingmanager = $this->get_grading_manager();
206
207 if (empty($gradingmanager)) {
208 return null;
209 }
210
211 return $gradingmanager->get_active_method();
212 }
213
214 /**
215 * Get the name of the component responsible for grading this gradeitem.
216 *
217 * @return string
218 */
219 public function get_grading_component_name(): ?string {
220 if (!$this->is_grading_enabled()) {
221 return null;
222 }
223
224 if ($method = $this->get_advanced_grading_method()) {
225 return "gradingform_{$method}";
226 }
227
228 return 'core_grades';
229 }
230
231 /**
232 * Get the name of the component subtype responsible for grading this gradeitem.
233 *
234 * @return string
235 */
236 public function get_grading_component_subtype(): ?string {
237 if (!$this->is_grading_enabled()) {
238 return null;
239 }
240
241 if ($method = $this->get_advanced_grading_method()) {
242 return null;
243 }
244
245 if ($this->is_using_scale()) {
246 return 'scale';
247 }
248
249 return 'point';
250 }
251
252 /**
253 * Whether decimals are allowed.
254 *
255 * @return bool
256 */
257 protected function allow_decimals(): bool {
258 return $this->get_gradeitem_value() > 0;
259 }
260
261 /**
262 * Get the grading manager for this advanced grading definition.
263 *
264 * @return grading_manager
265 */
266 protected function get_grading_manager(): ?grading_manager {
267 require_once(__DIR__ . '/../grading/lib.php');
268 return get_grading_manager($this->context, $this->component, $this->itemname);
269
270 }
271
272 /**
273 * Get the advanced grading controller if advanced grading is enabled.
274 *
275 * @return gradingform_controller
276 */
277 protected function get_advanced_grading_controller(): ?gradingform_controller {
278 $gradingmanager = $this->get_grading_manager();
279
280 if (empty($gradingmanager)) {
281 return null;
282 }
283
284 if ($gradingmethod = $gradingmanager->get_active_method()) {
285 return $gradingmanager->get_controller($gradingmethod);
286 }
287
288 return null;
289 }
290
291 /**
292 * Get the advanced grading menu items.
293 *
294 * @return array
295 */
296 protected function get_advanced_grading_grade_menu(): array {
297 return make_grades_menu($this->get_gradeitem_value());
298 }
299
300 /**
301 * Check whether the supplied grade is valid and throw an exception if not.
302 *
303 * @param float $grade The value being checked
304 * @throws moodle_exception
305 * @throws rating_exception
306 * @return bool
307 */
308 public function check_grade_validity(?float $grade): bool {
309 if ($this->is_using_scale()) {
310 // Fetch all options for this scale.
311 $scaleoptions = make_menu_from_list($this->get_scale());
312 if (!array_key_exists($grade, $scaleoptions)) {
313 // The selected option did not exist.
314 throw new rating_exception('ratinginvalid', 'rating');
315 }
316 } else if ($grade) {
317 $maxgrade = $this->get_gradeitem_value();
318 if ($grade > $maxgrade) {
319 // The grade is greater than the maximum possible value.
320 throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
321 'maxgrade' => $maxgrade,
322 'grade' => $grade,
323 ]);
324 } else if ($grade < 0) {
325 // Negative grades are not supported.
326 throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [
327 'maxgrade' => $maxgrade,
328 'grade' => $grade,
329 ]);
330 }
331 }
332
333 return true;
334 }
335
336 /**
337 * Create an empty row in the grade for the specified user and grader.
338 *
339 * @param stdClass $gradeduser The user being graded
340 * @param stdClass $grader The user who is grading
341 * @return stdClass The newly created grade record
342 */
343 abstract public function create_empty_grade(stdClass $gradeduser, stdClass $grader): stdClass;
344
345 /**
346 * Get the grade record for the specified grade id.
347 *
348 * @param int $gradeid
349 * @return stdClass
350 */
351 public function get_grade(int $gradeid): stdClass {
352 global $DB;
353
354 $grade = $DB->get_record($this->get_table_name(), ['id' => $gradeid]);
355
356 return $grade ?: null;
357 }
358
359 /**
360 * Get the grade for the specified user.
361 *
362 * @param stdClass $gradeduser The user being graded
363 * @param stdClass $grader The user who is grading
364 * @return stdClass The grade value
365 */
366 abstract public function get_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass;
367
368 /**
369 * Get grades for all users for the specified gradeitem.
370 *
371 * @param int $itemnumber The specific grade item to fetch for the user
372 * @return stdClass[] The grades
373 */
374 abstract public function get_all_grades(): array;
375
376 /**
377 * Create or update the grade.
378 *
379 * @param stdClass $grade
380 * @return bool Success
381 */
382 abstract protected function store_grade(stdClass $grade): bool;
383
384 /**
385 * Create or update the grade.
386 *
387 * @param stdClass $gradeduser The user being graded
388 * @param stdClass $grader The user who is grading
389 * @param stdClass $formdata The data submitted
390 * @return bool Success
391 */
392 public function store_grade_from_formdata(stdClass $gradeduser, stdClass $grader, stdClass $formdata): bool {
393 // Require gradelib for grade_floatval.
394 require_once(__DIR__ . '/../../lib/gradelib.php');
395 $grade = $this->get_grade_for_user($gradeduser, $grader);
396
397 if ($this->is_using_advanced_grading()) {
398 $instanceid = $formdata->instanceid;
399 $gradinginstance = $this->get_advanced_grading_instance($grader, $grade, (int) $instanceid);
400 $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id);
401 } else {
402 // Handle the case when grade is set to No Grade.
403 if (isset($formdata->grade)) {
404 $grade->grade = grade_floatval(unformat_float($formdata->grade));
405 }
406 }
407
408 return $this->store_grade($grade);
409 }
410
411 /**
412 * Get the advanced grading instance for the specified grade entry.
413 *
414 * @param stdClass $grader The user who is grading
415 * @param stdClass $grade The row from the grade table.
416 * @param int $instanceid The instanceid of the advanced grading form
417 * @return gradingform_instance
418 */
419 public function get_advanced_grading_instance(stdClass $grader, stdClass $grade, int $instanceid = null): ?gradingform_instance {
420 $controller = $this->get_advanced_grading_controller($this->itemname);
421
422 if (empty($controller)) {
423 // Advanced grading not enabeld for this item.
424 return null;
425 }
426
427 if (!$controller->is_form_available()) {
428 // The form is not available for this item.
429 return null;
430 }
431
432 // Fetch the instance for the specified graderid/itemid.
433 $gradinginstance = $controller->fetch_instance(
434 (int) $grader->id,
435 (int) $grade->id,
436 $instanceid
437 );
438
439 // Set the allowed grade range.
440 $gradinginstance->get_controller()->set_grade_range(
441 $this->get_advanced_grading_grade_menu(),
442 $this->allow_decimals()
443 );
444
445 return $gradinginstance;
446 }
447}