See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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 25 declare(strict_types = 1); 26 27 namespace core_grades; 28 29 use context; 30 use gradingform_controller; 31 use gradingform_instance; 32 use moodle_exception; 33 use stdClass; 34 use grade_item as core_gradeitem; 35 use 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 */ 44 abstract 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 /** 62 * component_gradeitem constructor. 63 * 64 * @param string $component 65 * @param context $context 66 * @param string $itemname 67 * @throws \coding_exception 68 */ 69 final protected function __construct(string $component, context $context, string $itemname) { 70 $this->component = $component; 71 $this->context = $context; 72 $this->itemname = $itemname; 73 $this->itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname); 74 } 75 76 /** 77 * Fetch an instance of a specific component_gradeitem. 78 * 79 * @param string $component 80 * @param context $context 81 * @param string $itemname 82 * @return self 83 */ 84 public static function instance(string $component, context $context, string $itemname): self { 85 $itemnumber = component_gradeitems::get_itemnumber_from_itemname($component, $itemname); 86 87 $classname = "{$component}\\grades\\{$itemname}_gradeitem"; 88 if (!class_exists($classname)) { 89 throw new coding_exception("Unknown gradeitem {$itemname} for component {$classname}"); 90 } 91 92 return $classname::load_from_context($context); 93 } 94 95 /** 96 * Load an instance of the current component_gradeitem based on context. 97 * 98 * @param context $context 99 * @return self 100 */ 101 abstract public static function load_from_context(context $context): self; 102 103 /** 104 * The table name used for grading. 105 * 106 * @return string 107 */ 108 abstract protected function get_table_name(): string; 109 110 /** 111 * Get the itemid for the current gradeitem. 112 * 113 * @return int 114 */ 115 public function get_grade_itemid(): int { 116 return component_gradeitems::get_itemnumber_from_itemname($this->component, $this->itemname); 117 } 118 119 /** 120 * Whether grading is enabled for this item. 121 * 122 * @return bool 123 */ 124 abstract public function is_grading_enabled(): bool; 125 126 /** 127 * Get the grade value for this instance. 128 * The itemname is translated to the relevant grade field for the activity. 129 * 130 * @return int 131 */ 132 abstract protected function get_gradeitem_value(): ?int; 133 134 /** 135 * Whether the grader can grade the gradee. 136 * 137 * @param stdClass $gradeduser The user being graded 138 * @param stdClass $grader The user who is grading 139 * @return bool 140 */ 141 abstract public function user_can_grade(stdClass $gradeduser, stdClass $grader): bool; 142 143 /** 144 * Require that the user can grade, throwing an exception if not. 145 * 146 * @param stdClass $gradeduser The user being graded 147 * @param stdClass $grader The user who is grading 148 * @throws required_capability_exception 149 */ 150 abstract public function require_user_can_grade(stdClass $gradeduser, stdClass $grader): void; 151 152 /** 153 * Get the scale if a scale is being used. 154 * 155 * @return stdClass 156 */ 157 protected function get_scale(): ?stdClass { 158 global $DB; 159 160 $gradetype = $this->get_gradeitem_value(); 161 if ($gradetype > 0) { 162 return null; 163 } 164 165 // This is a scale. 166 if (null === $this->scale) { 167 $this->scale = $DB->get_record('scale', ['id' => -1 * $gradetype]); 168 } 169 170 return $this->scale; 171 } 172 173 /** 174 * Check whether a scale is being used for this grade item. 175 * 176 * @return bool 177 */ 178 public function is_using_scale(): bool { 179 $gradetype = $this->get_gradeitem_value(); 180 181 return $gradetype < 0; 182 } 183 184 /** 185 * Whether this grade item is configured to use direct grading. 186 * 187 * @return bool 188 */ 189 public function is_using_direct_grading(): bool { 190 if ($this->is_using_scale()) { 191 return false; 192 } 193 194 if ($this->get_advanced_grading_controller()) { 195 return false; 196 } 197 198 return true; 199 } 200 201 /** 202 * Whether this grade item is configured to use advanced grading. 203 * 204 * @return bool 205 */ 206 public function is_using_advanced_grading(): bool { 207 if ($this->is_using_scale()) { 208 return false; 209 } 210 211 if ($this->get_advanced_grading_controller()) { 212 return true; 213 } 214 215 return false; 216 } 217 218 /** 219 * Get the name of the advanced grading method. 220 * 221 * @return string 222 */ 223 public function get_advanced_grading_method(): ?string { 224 $gradingmanager = $this->get_grading_manager(); 225 226 if (empty($gradingmanager)) { 227 return null; 228 } 229 230 return $gradingmanager->get_active_method(); 231 } 232 233 /** 234 * Get the name of the component responsible for grading this gradeitem. 235 * 236 * @return string 237 */ 238 public function get_grading_component_name(): ?string { 239 if (!$this->is_grading_enabled()) { 240 return null; 241 } 242 243 if ($method = $this->get_advanced_grading_method()) { 244 return "gradingform_{$method}"; 245 } 246 247 return 'core_grades'; 248 } 249 250 /** 251 * Get the name of the component subtype responsible for grading this gradeitem. 252 * 253 * @return string 254 */ 255 public function get_grading_component_subtype(): ?string { 256 if (!$this->is_grading_enabled()) { 257 return null; 258 } 259 260 if ($method = $this->get_advanced_grading_method()) { 261 return null; 262 } 263 264 if ($this->is_using_scale()) { 265 return 'scale'; 266 } 267 268 return 'point'; 269 } 270 271 /** 272 * Whether decimals are allowed. 273 * 274 * @return bool 275 */ 276 protected function allow_decimals(): bool { 277 return $this->get_gradeitem_value() > 0; 278 } 279 280 /** 281 * Get the grading manager for this advanced grading definition. 282 * 283 * @return grading_manager 284 */ 285 protected function get_grading_manager(): ?grading_manager { 286 require_once (__DIR__ . '/../grading/lib.php'); 287 return get_grading_manager($this->context, $this->component, $this->itemname); 288 289 } 290 291 /** 292 * Get the advanced grading controller if advanced grading is enabled. 293 * 294 * @return gradingform_controller 295 */ 296 protected function get_advanced_grading_controller(): ?gradingform_controller { 297 $gradingmanager = $this->get_grading_manager(); 298 299 if (empty($gradingmanager)) { 300 return null; 301 } 302 303 if ($gradingmethod = $gradingmanager->get_active_method()) { 304 return $gradingmanager->get_controller($gradingmethod); 305 } 306 307 return null; 308 } 309 310 /** 311 * Get the list of available grade items. 312 * 313 * @return array 314 */ 315 public function get_grade_menu(): array { 316 return make_grades_menu($this->get_gradeitem_value()); 317 } 318 319 /** 320 * Check whether the supplied grade is valid and throw an exception if not. 321 * 322 * @param float $grade The value being checked 323 * @throws moodle_exception 324 * @return bool 325 */ 326 public function check_grade_validity(?float $grade): bool { 327 $grade = grade_floatval(unformat_float($grade)); 328 if ($grade) { 329 if ($this->is_using_scale()) { 330 // Fetch all options for this scale. 331 $scaleoptions = make_menu_from_list($this->get_scale()->scale); 332 333 if ($grade != -1 && !array_key_exists((int) $grade, $scaleoptions)) { 334 // The selected option did not exist. 335 throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 336 'maxgrade' => count($scaleoptions), 337 'grade' => $grade, 338 ]); 339 } 340 } else if ($grade) { 341 $maxgrade = $this->get_gradeitem_value(); 342 if ($grade > $maxgrade) { 343 // The grade is greater than the maximum possible value. 344 throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 345 'maxgrade' => $maxgrade, 346 'grade' => $grade, 347 ]); 348 } else if ($grade < 0) { 349 // Negative grades are not supported. 350 throw new moodle_exception('error:notinrange', 'core_grading', '', (object) [ 351 'maxgrade' => $maxgrade, 352 'grade' => $grade, 353 ]); 354 } 355 } 356 } 357 358 return true; 359 } 360 361 /** 362 * Create an empty row in the grade for the specified user and grader. 363 * 364 * @param stdClass $gradeduser The user being graded 365 * @param stdClass $grader The user who is grading 366 * @return stdClass The newly created grade record 367 */ 368 abstract public function create_empty_grade(stdClass $gradeduser, stdClass $grader): stdClass; 369 370 /** 371 * Get the grade record for the specified grade id. 372 * 373 * @param int $gradeid 374 * @return stdClass 375 * @throws \dml_exception 376 */ 377 public function get_grade(int $gradeid): stdClass { 378 global $DB; 379 380 $grade = $DB->get_record($this->get_table_name(), ['id' => $gradeid]); 381 382 return $grade ?: null; 383 } 384 385 /** 386 * Get the grade for the specified user. 387 * 388 * @param stdClass $gradeduser The user being graded 389 * @param stdClass $grader The user who is grading 390 * @return stdClass The grade value 391 */ 392 abstract public function get_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass; 393 394 /** 395 * Get the grade status for the specified user. 396 * If the user has a grade as defined by the implementor return true else return false. 397 * 398 * @param stdClass $gradeduser The user being graded 399 * @return bool The grade status 400 */ 401 abstract public function user_has_grade(stdClass $gradeduser): bool; 402 403 /** 404 * Get grades for all users for the specified gradeitem. 405 * 406 * @return stdClass[] The grades 407 */ 408 abstract public function get_all_grades(): array; 409 410 /** 411 * Get the grade item instance id. 412 * 413 * This is typically the cmid in the case of an activity, and relates to the iteminstance field in the grade_items 414 * table. 415 * 416 * @return int 417 */ 418 abstract public function get_grade_instance_id(): int; 419 420 /** 421 * Get the core grade item from the current component grade item. 422 * This is mainly used to access the max grade for a gradeitem 423 * 424 * @return \grade_item The grade item 425 */ 426 public function get_grade_item(): \grade_item { 427 global $CFG; 428 require_once("{$CFG->libdir}/gradelib.php"); 429 430 [$itemtype, $itemmodule] = \core_component::normalize_component($this->component); 431 $gradeitem = \grade_item::fetch([ 432 'itemtype' => $itemtype, 433 'itemmodule' => $itemmodule, 434 'itemnumber' => $this->itemnumber, 435 'iteminstance' => $this->get_grade_instance_id(), 436 ]); 437 438 return $gradeitem; 439 } 440 441 /** 442 * Create or update the grade. 443 * 444 * @param stdClass $grade 445 * @return bool Success 446 */ 447 abstract protected function store_grade(stdClass $grade): bool; 448 449 /** 450 * Create or update the grade. 451 * 452 * @param stdClass $gradeduser The user being graded 453 * @param stdClass $grader The user who is grading 454 * @param stdClass $formdata The data submitted 455 * @return bool Success 456 */ 457 public function store_grade_from_formdata(stdClass $gradeduser, stdClass $grader, stdClass $formdata): bool { 458 // Require gradelib for grade_floatval. 459 require_once (__DIR__ . '/../../lib/gradelib.php'); 460 $grade = $this->get_grade_for_user($gradeduser, $grader); 461 462 if ($this->is_using_advanced_grading()) { 463 $instanceid = $formdata->instanceid; 464 $gradinginstance = $this->get_advanced_grading_instance($grader, $grade, (int) $instanceid); 465 $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); 466 467 if ($grade->grade == -1) { 468 // In advanced grading, a value of -1 means no data. 469 return false; 470 } 471 } else { 472 // Handle the case when grade is set to No Grade. 473 if (isset($formdata->grade)) { 474 $grade->grade = grade_floatval(unformat_float($formdata->grade)); 475 } 476 } 477 478 return $this->store_grade($grade); 479 } 480 481 /** 482 * Get the advanced grading instance for the specified grade entry. 483 * 484 * @param stdClass $grader The user who is grading 485 * @param stdClass $grade The row from the grade table. 486 * @param int $instanceid The instanceid of the advanced grading form 487 * @return gradingform_instance 488 */ 489 public function get_advanced_grading_instance(stdClass $grader, stdClass $grade, int $instanceid = null): ?gradingform_instance { 490 $controller = $this->get_advanced_grading_controller($this->itemname); 491 492 if (empty($controller)) { 493 // Advanced grading not enabeld for this item. 494 return null; 495 } 496 497 if (!$controller->is_form_available()) { 498 // The form is not available for this item. 499 return null; 500 } 501 502 // Fetch the instance for the specified graderid/itemid. 503 $gradinginstance = $controller->fetch_instance( 504 (int) $grader->id, 505 (int) $grade->id, 506 $instanceid 507 ); 508 509 // Set the allowed grade range. 510 $gradinginstance->get_controller()->set_grade_range( 511 $this->get_grade_menu(), 512 $this->allow_decimals() 513 ); 514 515 return $gradinginstance; 516 } 517 518 /** 519 * Sends a notification about the item being graded for the student. 520 * 521 * @param stdClass $gradeduser The user being graded 522 * @param stdClass $grader The user who is grading 523 */ 524 public function send_student_notification(stdClass $gradeduser, stdClass $grader): void { 525 $contextname = $this->context->get_context_name(); 526 $eventdata = new \core\message\message(); 527 $eventdata->courseid = $this->context->get_course_context()->instanceid; 528 $eventdata->component = 'moodle'; 529 $eventdata->name = 'gradenotifications'; 530 $eventdata->userfrom = $grader; 531 $eventdata->userto = $gradeduser; 532 $eventdata->subject = get_string('gradenotificationsubject', 'grades'); 533 $eventdata->fullmessage = get_string('gradenotificationmessage', 'grades', $contextname); 534 $eventdata->contexturl = $this->context->get_url(); 535 $eventdata->contexturlname = $contextname; 536 $eventdata->fullmessageformat = FORMAT_HTML; 537 $eventdata->fullmessagehtml = ''; 538 $eventdata->smallmessage = ''; 539 $eventdata->notification = 1; 540 message_send($eventdata); 541 } 542 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body