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