See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]
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 return $DB->get_record($this->get_table_name(), ['id' => $gradeid]); 381 } 382 383 /** 384 * Get the grade for the specified user. 385 * 386 * @param stdClass $gradeduser The user being graded 387 * @param stdClass $grader The user who is grading 388 * @return stdClass The grade value 389 */ 390 abstract public function get_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass; 391 392 /** 393 * Returns the grade that should be displayed to the user. 394 * 395 * The grade does not necessarily return a float value, this method takes grade settings into considering so 396 * the correct value be shown, eg. a float vs a letter. 397 * 398 * @param stdClass $gradeduser 399 * @param stdClass $grader 400 * @return stdClass|null 401 */ 402 public function get_formatted_grade_for_user(stdClass $gradeduser, stdClass $grader): ?stdClass { 403 global $DB; 404 405 if ($grade = $this->get_grade_for_user($gradeduser, $grader)) { 406 $gradeitem = $this->get_grade_item(); 407 if (!$this->is_using_scale()) { 408 $grade->grade = !is_null($grade->grade) ? (float)$grade->grade : null; // Cast non-null values, keeping nulls. 409 $grade->usergrade = grade_format_gradevalue($grade->grade, $gradeitem); 410 $grade->maxgrade = format_float($gradeitem->grademax, $gradeitem->get_decimals()); 411 // If displaying the raw grade, also display the total value. 412 if ($gradeitem->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) { 413 $grade->usergrade .= ' / ' . $grade->maxgrade; 414 } 415 } else { 416 $grade->usergrade = '-'; 417 if ($scale = $DB->get_record('scale', ['id' => $gradeitem->scaleid])) { 418 $options = make_menu_from_list($scale->scale); 419 420 $gradeint = (int) $grade->grade; 421 if (isset($options[$gradeint])) { 422 $grade->usergrade = $options[$gradeint]; 423 } 424 } 425 426 $grade->maxgrade = format_float($gradeitem->grademax, $gradeitem->get_decimals()); 427 } 428 429 return $grade; 430 } 431 432 return null; 433 } 434 435 /** 436 * Get the grade status for the specified user. 437 * If the user has a grade as defined by the implementor return true else return false. 438 * 439 * @param stdClass $gradeduser The user being graded 440 * @return bool The grade status 441 */ 442 abstract public function user_has_grade(stdClass $gradeduser): bool; 443 444 /** 445 * Get grades for all users for the specified gradeitem. 446 * 447 * @return stdClass[] The grades 448 */ 449 abstract public function get_all_grades(): array; 450 451 /** 452 * Get the grade item instance id. 453 * 454 * This is typically the cmid in the case of an activity, and relates to the iteminstance field in the grade_items 455 * table. 456 * 457 * @return int 458 */ 459 abstract public function get_grade_instance_id(): int; 460 461 /** 462 * Get the core grade item from the current component grade item. 463 * This is mainly used to access the max grade for a gradeitem 464 * 465 * @return \grade_item The grade item 466 */ 467 public function get_grade_item(): \grade_item { 468 global $CFG; 469 require_once("{$CFG->libdir}/gradelib.php"); 470 471 [$itemtype, $itemmodule] = \core_component::normalize_component($this->component); 472 $gradeitem = \grade_item::fetch([ 473 'itemtype' => $itemtype, 474 'itemmodule' => $itemmodule, 475 'itemnumber' => $this->itemnumber, 476 'iteminstance' => $this->get_grade_instance_id(), 477 ]); 478 479 return $gradeitem; 480 } 481 482 /** 483 * Create or update the grade. 484 * 485 * @param stdClass $grade 486 * @return bool Success 487 */ 488 abstract protected function store_grade(stdClass $grade): bool; 489 490 /** 491 * Create or update the grade. 492 * 493 * @param stdClass $gradeduser The user being graded 494 * @param stdClass $grader The user who is grading 495 * @param stdClass $formdata The data submitted 496 * @return bool Success 497 */ 498 public function store_grade_from_formdata(stdClass $gradeduser, stdClass $grader, stdClass $formdata): bool { 499 // Require gradelib for grade_floatval. 500 require_once (__DIR__ . '/../../lib/gradelib.php'); 501 $grade = $this->get_grade_for_user($gradeduser, $grader); 502 503 if ($this->is_using_advanced_grading()) { 504 $instanceid = $formdata->instanceid; 505 $gradinginstance = $this->get_advanced_grading_instance($grader, $grade, (int) $instanceid); 506 $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id); 507 508 if ($grade->grade == -1) { 509 // In advanced grading, a value of -1 means no data. 510 return false; 511 } 512 } else { 513 // Handle the case when grade is set to No Grade. 514 if (isset($formdata->grade)) { 515 $grade->grade = grade_floatval(unformat_float($formdata->grade)); 516 } 517 } 518 519 return $this->store_grade($grade); 520 } 521 522 /** 523 * Get the advanced grading instance for the specified grade entry. 524 * 525 * @param stdClass $grader The user who is grading 526 * @param stdClass $grade The row from the grade table. 527 * @param int $instanceid The instanceid of the advanced grading form 528 * @return gradingform_instance 529 */ 530 public function get_advanced_grading_instance(stdClass $grader, stdClass $grade, int $instanceid = null): ?gradingform_instance { 531 $controller = $this->get_advanced_grading_controller($this->itemname); 532 533 if (empty($controller)) { 534 // Advanced grading not enabeld for this item. 535 return null; 536 } 537 538 if (!$controller->is_form_available()) { 539 // The form is not available for this item. 540 return null; 541 } 542 543 // Fetch the instance for the specified graderid/itemid. 544 $gradinginstance = $controller->fetch_instance( 545 (int) $grader->id, 546 (int) $grade->id, 547 $instanceid 548 ); 549 550 // Set the allowed grade range. 551 $gradinginstance->get_controller()->set_grade_range( 552 $this->get_grade_menu(), 553 $this->allow_decimals() 554 ); 555 556 return $gradinginstance; 557 } 558 559 /** 560 * Sends a notification about the item being graded for the student. 561 * 562 * @param stdClass $gradeduser The user being graded 563 * @param stdClass $grader The user who is grading 564 */ 565 public function send_student_notification(stdClass $gradeduser, stdClass $grader): void { 566 $contextname = $this->context->get_context_name(); 567 $eventdata = new \core\message\message(); 568 $eventdata->courseid = $this->context->get_course_context()->instanceid; 569 $eventdata->component = 'moodle'; 570 $eventdata->name = 'gradenotifications'; 571 $eventdata->userfrom = $grader; 572 $eventdata->userto = $gradeduser; 573 $eventdata->subject = get_string('gradenotificationsubject', 'grades'); 574 $eventdata->fullmessage = get_string('gradenotificationmessage', 'grades', $contextname); 575 $eventdata->contexturl = $this->context->get_url(); 576 $eventdata->contexturlname = $contextname; 577 $eventdata->fullmessageformat = FORMAT_HTML; 578 $eventdata->fullmessagehtml = ''; 579 $eventdata->smallmessage = ''; 580 $eventdata->notification = 1; 581 message_send($eventdata); 582 } 583 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body