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 * A library of classes used by the grade edit pages 19 * 20 * @package core_grades 21 * @copyright 2009 Nicolas Connault 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 class grade_edit_tree { 26 public $columns = array(); 27 28 /** 29 * @var grade_tree $gtree @see grade/lib.php 30 */ 31 public $gtree; 32 33 /** 34 * @var grade_plugin_return @see grade/lib.php 35 */ 36 public $gpr; 37 38 /** 39 * @var string $moving The eid of the category or item being moved 40 */ 41 public $moving; 42 43 public $deepest_level; 44 45 public $uses_weight = false; 46 47 public $table; 48 49 public $categories = array(); 50 51 /** 52 * Show calculator icons next to manual grade items 53 * @var bool $show_calculations 54 */ 55 private $show_calculations; 56 57 /** 58 * Constructor 59 */ 60 public function __construct($gtree, $moving=false, $gpr) { 61 global $USER, $OUTPUT, $COURSE; 62 63 $systemdefault = get_config('moodle', 'grade_report_showcalculations'); 64 $this->show_calculations = get_user_preferences('grade_report_showcalculations', $systemdefault); 65 66 $this->gtree = $gtree; 67 $this->moving = $moving; 68 $this->gpr = $gpr; 69 $this->deepest_level = $this->get_deepest_level($this->gtree->top_element); 70 71 $this->columns = array(grade_edit_tree_column::factory('name', array('deepest_level' => $this->deepest_level))); 72 73 if ($this->uses_weight) { 74 $this->columns[] = grade_edit_tree_column::factory('weight', array('adv' => 'weight')); 75 } 76 77 $this->columns[] = grade_edit_tree_column::factory('range'); // This is not a setting... How do we deal with it? 78 $this->columns[] = grade_edit_tree_column::factory('actions'); 79 80 if ($this->deepest_level > 1) { 81 $this->columns[] = grade_edit_tree_column::factory('select'); 82 } 83 84 $this->table = new html_table(); 85 $this->table->id = "grade_edit_tree_table"; 86 $this->table->attributes['class'] = 'generaltable simple setup-grades'; 87 if ($this->moving) { 88 $this->table->attributes['class'] .= ' moving'; 89 } 90 91 foreach ($this->columns as $column) { 92 if (!($this->moving && $column->hide_when_moving)) { 93 $this->table->head[] = $column->get_header_cell(); 94 } 95 } 96 97 $rowcount = 0; 98 $this->table->data = $this->build_html_tree($this->gtree->top_element, true, array(), 0, $rowcount); 99 } 100 101 /** 102 * Recursive function for building the table holding the grade categories and items, 103 * with CSS indentation and styles. 104 * 105 * @param array $element The current tree element being rendered 106 * @param boolean $totals Whether or not to print category grade items (category totals) 107 * @param array $parents An array of parent categories for the current element (used for indentation and row classes) 108 * 109 * @return string HTML 110 */ 111 public function build_html_tree($element, $totals, $parents, $level, &$row_count) { 112 global $CFG, $COURSE, $PAGE, $OUTPUT; 113 114 $object = $element['object']; 115 $eid = $element['eid']; 116 $object->name = $this->gtree->get_element_header($element, true, true, true, true, true); 117 $object->stripped_name = $this->gtree->get_element_header($element, false, false, false); 118 $is_category_item = false; 119 if ($element['type'] == 'categoryitem' || $element['type'] == 'courseitem') { 120 $is_category_item = true; 121 } 122 123 $rowclasses = array(); 124 foreach ($parents as $parent_eid) { 125 $rowclasses[] = $parent_eid; 126 } 127 128 $moveaction = ''; 129 $actionsmenu = new action_menu(); 130 $actionsmenu->set_menu_trigger(get_string('edit')); 131 $actionsmenu->set_owner_selector('grade-item-' . $eid); 132 $actionsmenu->set_alignment(action_menu::TL, action_menu::BL); 133 134 if (!$is_category_item && ($icon = $this->gtree->get_edit_icon($element, $this->gpr, true))) { 135 $actionsmenu->add($icon); 136 } 137 // MDL-49281 if grade_item already has calculation, it should be editable even if global setting is off. 138 $type = $element['type']; 139 $iscalculated = ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') && $object->is_calculated(); 140 $icon = $this->gtree->get_calculation_icon($element, $this->gpr, true); 141 if ($iscalculated || ($this->show_calculations && $icon)) { 142 $actionsmenu->add($icon); 143 } 144 145 if ($element['type'] == 'item' or ($element['type'] == 'category' and $element['depth'] > 1)) { 146 if ($this->element_deletable($element)) { 147 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'delete', 'eid' => $eid, 'sesskey' => sesskey())); 148 $icon = new action_menu_link_secondary($aurl, new pix_icon('t/delete', get_string('delete')), get_string('delete')); 149 $actionsmenu->add($icon); 150 } 151 152 if ($this->element_duplicatable($element)) { 153 $duplicateparams = array(); 154 $duplicateparams['id'] = $COURSE->id; 155 $duplicateparams['action'] = 'duplicate'; 156 $duplicateparams['eid'] = $eid; 157 $duplicateparams['sesskey'] = sesskey(); 158 $aurl = new moodle_url('index.php', $duplicateparams); 159 $duplicateicon = new pix_icon('t/copy', get_string('duplicate')); 160 $icon = new action_menu_link_secondary($aurl, $duplicateicon, get_string('duplicate')); 161 $actionsmenu->add($icon); 162 } 163 164 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'moveselect', 'eid' => $eid, 'sesskey' => sesskey())); 165 $moveaction .= $OUTPUT->action_icon($aurl, new pix_icon('t/move', get_string('move'))); 166 } 167 168 if ($icon = $this->gtree->get_hiding_icon($element, $this->gpr, true)) { 169 $actionsmenu->add($icon); 170 } 171 172 if ($icon = $this->gtree->get_reset_icon($element, $this->gpr, true)) { 173 $actionsmenu->add($icon); 174 } 175 176 $actions = $OUTPUT->render($actionsmenu); 177 178 $returnrows = array(); 179 $root = false; 180 181 $id = required_param('id', PARAM_INT); 182 183 /// prepare move target if needed 184 $last = ''; 185 186 /// print the list items now 187 if ($this->moving == $eid) { 188 // do not diplay children 189 $cell = new html_table_cell(); 190 $cell->colspan = 12; 191 $cell->attributes['class'] = $element['type'] . ' moving column-name level' . 192 ($level + 1) . ' level' . ($level % 2 ? 'even' : 'odd'); 193 $cell->text = $object->name.' ('.get_string('move').')'; 194 return array(new html_table_row(array($cell))); 195 } 196 197 if ($element['type'] == 'category') { 198 $level++; 199 $this->categories[$object->id] = $object->stripped_name; 200 $category = grade_category::fetch(array('id' => $object->id)); 201 $item = $category->get_grade_item(); 202 203 // Add aggregation coef input if not a course item and if parent category has correct aggregation type 204 $dimmed = ($item->is_hidden()) ? 'dimmed_text' : ''; 205 206 // Before we print the category's row, we must find out how many rows will appear below it (for the filler cell's rowspan) 207 $aggregation_position = grade_get_setting($COURSE->id, 'aggregationposition', $CFG->grade_aggregationposition); 208 $category_total_data = null; // Used if aggregationposition is set to "last", so we can print it last 209 210 $html_children = array(); 211 212 $row_count = 0; 213 214 foreach($element['children'] as $child_el) { 215 $moveto = null; 216 217 if (empty($child_el['object']->itemtype)) { 218 $child_el['object']->itemtype = false; 219 } 220 221 if (($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') && !$totals) { 222 continue; 223 } 224 225 $child_eid = $child_el['eid']; 226 $first = ''; 227 228 if ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') { 229 $first = array('first' => 1); 230 $child_eid = $eid; 231 } 232 233 if ($this->moving && $this->moving != $child_eid) { 234 235 $strmove = get_string('move'); 236 $strmovehere = get_string('movehere'); 237 $actions = $moveaction = ''; // no action icons when moving 238 239 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'move', 'eid' => $this->moving, 'moveafter' => $child_eid, 'sesskey' => sesskey())); 240 if ($first) { 241 $aurl->params($first); 242 } 243 244 $cell = new html_table_cell(); 245 $cell->colspan = 12; 246 $cell->attributes['class'] = 'movehere level' . ($level + 1) . ' level' . ($level % 2 ? 'even' : 'odd'); 247 248 $icon = new pix_icon('movehere', $strmovehere, null, array('class'=>'movetarget')); 249 $cell->text = $OUTPUT->action_icon($aurl, $icon); 250 251 $moveto = new html_table_row(array($cell)); 252 } 253 254 $newparents = $parents; 255 $newparents[] = $eid; 256 257 $row_count++; 258 $child_row_count = 0; 259 260 // If moving, do not print course and category totals, but still print the moveto target box 261 if ($this->moving && ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category')) { 262 $html_children[] = $moveto; 263 } elseif ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') { 264 // We don't build the item yet because we first need to know the deepest level of categories (for category/name colspans) 265 $category_total_item = $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count); 266 if (!$aggregation_position) { 267 $html_children = array_merge($html_children, $category_total_item); 268 } 269 } else { 270 $html_children = array_merge($html_children, $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count)); 271 if (!empty($moveto)) { 272 $html_children[] = $moveto; 273 } 274 275 if ($this->moving) { 276 $row_count++; 277 } 278 } 279 280 $row_count += $child_row_count; 281 282 // If the child is a category, increment row_count by one more (for the extra coloured row) 283 if ($child_el['type'] == 'category') { 284 $row_count++; 285 } 286 } 287 288 // Print category total at the end if aggregation position is "last" (1) 289 if (!empty($category_total_item) && $aggregation_position) { 290 $html_children = array_merge($html_children, $category_total_item); 291 } 292 293 // Determine if we are at the root 294 if (isset($element['object']->grade_item) && $element['object']->grade_item->is_course_item()) { 295 $root = true; 296 } 297 298 $levelclass = "level$level level" . ($level % 2 ? 'odd' : 'even'); 299 300 $courseclass = ''; 301 if ($level == 1) { 302 $courseclass = 'coursecategory'; 303 } 304 305 $row = new html_table_row(); 306 $row->id = 'grade-item-' . $eid; 307 $row->attributes['class'] = $courseclass . ' category ' . $dimmed; 308 $row->attributes['data-category'] = $eid; 309 $row->attributes['data-itemid'] = $category->get_grade_item()->id; 310 foreach ($rowclasses as $class) { 311 $row->attributes['class'] .= ' ' . $class; 312 } 313 314 $headercell = new html_table_cell(); 315 $headercell->header = true; 316 $headercell->scope = 'row'; 317 $headercell->attributes['title'] = $object->stripped_name; 318 $headercell->attributes['class'] = 'cell column-rowspan rowspan ' . $levelclass; 319 $headercell->rowspan = $row_count + 1; 320 $row->cells[] = $headercell; 321 322 foreach ($this->columns as $column) { 323 if (!($this->moving && $column->hide_when_moving)) { 324 $row->cells[] = $column->get_category_cell($category, $levelclass, [ 325 'id' => $id, 326 'name' => $object->name, 327 'level' => $level, 328 'actions' => $actions, 329 'moveaction' => $moveaction, 330 'eid' => $eid, 331 ]); 332 } 333 } 334 335 $returnrows[] = $row; 336 337 $returnrows = array_merge($returnrows, $html_children); 338 339 // Print a coloured row to show the end of the category across the table 340 $endcell = new html_table_cell(); 341 $endcell->colspan = (19 - $level); 342 $endcell->attributes['class'] = 'emptyrow colspan ' . $levelclass; 343 344 $returnrows[] = new html_table_row(array($endcell)); 345 346 } else { // Dealing with a grade item 347 348 $item = grade_item::fetch(array('id' => $object->id)); 349 $element['type'] = 'item'; 350 $element['object'] = $item; 351 352 $categoryitemclass = ''; 353 if ($item->itemtype == 'category') { 354 $categoryitemclass = 'categoryitem'; 355 } 356 if ($item->itemtype == 'course') { 357 $categoryitemclass = 'courseitem'; 358 } 359 360 $dimmed = ($item->is_hidden()) ? "dimmed_text" : ""; 361 $gradeitemrow = new html_table_row(); 362 $gradeitemrow->id = 'grade-item-' . $eid; 363 $gradeitemrow->attributes['class'] = $categoryitemclass . ' item ' . $dimmed; 364 $gradeitemrow->attributes['data-itemid'] = $object->id; 365 foreach ($rowclasses as $class) { 366 $gradeitemrow->attributes['class'] .= ' ' . $class; 367 } 368 369 foreach ($this->columns as $column) { 370 if (!($this->moving && $column->hide_when_moving)) { 371 $gradeitemrow->cells[] = $column->get_item_cell($item, array('id' => $id, 'name' => $object->name, 372 'level' => $level, 'actions' => $actions, 'element' => $element, 'eid' => $eid, 373 'moveaction' => $moveaction, 'itemtype' => $object->itemtype)); 374 } 375 } 376 377 $returnrows[] = $gradeitemrow; 378 } 379 380 return $returnrows; 381 382 } 383 384 /** 385 * Given a grade_item object, returns a labelled input if an aggregation coefficient (weight or extra credit) applies to it. 386 * @param grade_item $item 387 * @return string HTML 388 */ 389 static function get_weight_input($item) { 390 global $OUTPUT; 391 392 if (!is_object($item) || get_class($item) !== 'grade_item') { 393 throw new Exception('grade_edit_tree::get_weight_input($item) was given a variable that is not of the required type (grade_item object)'); 394 return false; 395 } 396 397 if ($item->is_course_item()) { 398 return ''; 399 } 400 401 $parent_category = $item->get_parent_category(); 402 $parent_category->apply_forced_settings(); 403 $aggcoef = $item->get_coefstring(); 404 405 $itemname = $item->itemname; 406 if ($item->is_category_item()) { 407 // Remember, the parent category of a category item is the category itself. 408 $itemname = $parent_category->get_name(); 409 } 410 $str = ''; 411 412 if ($aggcoef == 'aggregationcoefweight' || $aggcoef == 'aggregationcoef' || $aggcoef == 'aggregationcoefextraweight') { 413 414 return $OUTPUT->render_from_template('core_grades/weight_field', [ 415 'id' => $item->id, 416 'itemname' => $itemname, 417 'value' => self::format_number($item->aggregationcoef) 418 ]); 419 420 } else if ($aggcoef == 'aggregationcoefextraweightsum') { 421 422 $tpldata = [ 423 'id' => $item->id, 424 'itemname' => $itemname, 425 'value' => self::format_number($item->aggregationcoef2 * 100.0), 426 'checked' => $item->weightoverride, 427 'disabled' => !$item->weightoverride 428 ]; 429 $str .= $OUTPUT->render_from_template('core_grades/weight_override_field', $tpldata); 430 431 } 432 433 return $str; 434 } 435 436 // Trims trailing zeros. 437 // Used on the 'Gradebook setup' page for grade items settings like aggregation co-efficient. 438 // Grader report has its own decimal place settings so they are handled elsewhere. 439 static function format_number($number) { 440 $formatted = rtrim(format_float($number, 4),'0'); 441 if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point 442 $formatted .= '0'; 443 } 444 return $formatted; 445 } 446 447 /** 448 * Given an element of the grade tree, returns whether it is deletable or not (only manual grade items are deletable) 449 * 450 * @param array $element 451 * @return bool 452 */ 453 function element_deletable($element) { 454 global $COURSE; 455 456 if ($element['type'] != 'item') { 457 return true; 458 } 459 460 $grade_item = $element['object']; 461 462 if ($grade_item->itemtype != 'mod' or $grade_item->is_outcome_item() or $grade_item->gradetype == GRADE_TYPE_NONE) { 463 return true; 464 } 465 466 $modinfo = get_fast_modinfo($COURSE); 467 if (!isset($modinfo->instances[$grade_item->itemmodule][$grade_item->iteminstance])) { 468 // module does not exist 469 return true; 470 } 471 472 return false; 473 } 474 475 /** 476 * Given an element of the grade tree, returns whether it is duplicatable or not (only manual grade items are duplicatable) 477 * 478 * @param array $element 479 * @return bool 480 */ 481 public function element_duplicatable($element) { 482 if ($element['type'] != 'item') { 483 return false; 484 } 485 486 $gradeitem = $element['object']; 487 if ($gradeitem->itemtype != 'mod') { 488 return true; 489 } 490 return false; 491 } 492 493 /** 494 * Given the grade tree and an array of element ids (e.g. c15, i42), and expecting the 'moveafter' URL param, 495 * moves the selected items to the requested location. Then redirects the user to the given $returnurl 496 * 497 * @param object $gtree The grade tree (a recursive representation of the grade categories and grade items) 498 * @param array $eids 499 * @param string $returnurl 500 */ 501 function move_elements($eids, $returnurl) { 502 $moveafter = required_param('moveafter', PARAM_INT); 503 504 if (!is_array($eids)) { 505 $eids = array($eids); 506 } 507 508 if(!$after_el = $this->gtree->locate_element("cg$moveafter")) { 509 print_error('invalidelementid', '', $returnurl); 510 } 511 512 $after = $after_el['object']; 513 $parent = $after; 514 $sortorder = $after->get_sortorder(); 515 516 foreach ($eids as $eid) { 517 if (!$element = $this->gtree->locate_element($eid)) { 518 print_error('invalidelementid', '', $returnurl); 519 } 520 $object = $element['object']; 521 522 $object->set_parent($parent->id); 523 $object->move_after_sortorder($sortorder); 524 $sortorder++; 525 } 526 527 redirect($returnurl, '', 0); 528 } 529 530 /** 531 * Recurses through the entire grade tree to find and return the maximum depth of the tree. 532 * This should be run only once from the root element (course category), and is used for the 533 * indentation of the Name column's cells (colspan) 534 * 535 * @param array $element An array of values representing a grade tree's element (all grade items in this case) 536 * @param int $level The level of the current recursion 537 * @param int $deepest_level A value passed to each subsequent level of recursion and incremented if $level > $deepest_level 538 * @return int Deepest level 539 */ 540 function get_deepest_level($element, $level=0, $deepest_level=1) { 541 $object = $element['object']; 542 543 $level++; 544 $coefstring = $element['object']->get_coefstring(); 545 if ($element['type'] == 'category') { 546 if ($coefstring == 'aggregationcoefweight' || $coefstring == 'aggregationcoefextraweightsum' || 547 $coefstring == 'aggregationcoefextraweight') { 548 $this->uses_weight = true; 549 } 550 551 foreach($element['children'] as $child_el) { 552 if ($level > $deepest_level) { 553 $deepest_level = $level; 554 } 555 $deepest_level = $this->get_deepest_level($child_el, $level, $deepest_level); 556 } 557 558 $category = grade_category::fetch(array('id' => $object->id)); 559 $item = $category->get_grade_item(); 560 if ($item->gradetype == GRADE_TYPE_NONE) { 561 // Add 1 more level for grade category that has no total. 562 $deepest_level++; 563 } 564 } 565 566 return $deepest_level; 567 } 568 } 569 570 /** 571 * Class grade_edit_tree_column 572 * 573 * @package core_grades 574 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 575 */ 576 abstract class grade_edit_tree_column { 577 public $forced; 578 public $hidden; 579 public $forced_hidden; 580 public $advanced_hidden; 581 public $hide_when_moving = true; 582 /** 583 * html_table_cell object used as a template for header cells in all categories. 584 * It must be cloned before being used. 585 * @var html_table_cell $headercell 586 */ 587 public $headercell; 588 /** 589 * html_table_cell object used as a template for category cells in all categories. 590 * It must be cloned before being used. 591 * @var html_table_cell $categorycell 592 */ 593 public $categorycell; 594 /** 595 * html_table_cell object used as a template for item cells in all categories. 596 * It must be cloned before being used. 597 * @var html_table_cell $itemcell 598 */ 599 public $itemcell; 600 601 public static function factory($name, $params=array()) { 602 $class_name = "grade_edit_tree_column_$name"; 603 if (class_exists($class_name)) { 604 return new $class_name($params); 605 } 606 } 607 608 public abstract function get_header_cell(); 609 610 public function get_category_cell($category, $levelclass, $params) { 611 $cell = clone($this->categorycell); 612 $cell->attributes['class'] .= ' ' . $levelclass; 613 $cell->attributes['text'] = ''; 614 return $cell; 615 } 616 617 public function get_item_cell($item, $params) { 618 $cell = clone($this->itemcell); 619 $cell->attributes['text'] = ''; 620 if (isset($params['level'])) { 621 $level = $params['level'] + (($item->itemtype == 'category' || $item->itemtype == 'course') ? 0 : 1); 622 $cell->attributes['class'] .= ' level' . $level; 623 $cell->attributes['class'] .= ' level' . ($level % 2 ? 'odd' : 'even'); 624 } 625 return $cell; 626 } 627 628 public function __construct() { 629 $this->headercell = new html_table_cell(); 630 $this->headercell->header = true; 631 $this->headercell->attributes['class'] = 'header'; 632 633 $this->categorycell = new html_table_cell(); 634 $this->categorycell->attributes['class'] = 'cell'; 635 636 $this->itemcell = new html_table_cell(); 637 $this->itemcell->attributes['class'] = 'cell'; 638 639 if (preg_match('/^grade_edit_tree_column_(\w*)$/', get_class($this), $matches)) { 640 $this->headercell->attributes['class'] .= ' column-' . $matches[1]; 641 $this->categorycell->attributes['class'] .= ' column-' . $matches[1]; 642 $this->itemcell->attributes['class'] .= ' column-' . $matches[1]; 643 } 644 } 645 } 646 647 /** 648 * Class grade_edit_tree_column_name 649 * 650 * @package core_grades 651 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 652 */ 653 class grade_edit_tree_column_name extends grade_edit_tree_column { 654 public $forced = false; 655 public $hidden = false; 656 public $forced_hidden = false; 657 public $advanced_hidden = false; 658 public $deepest_level = 1; 659 public $hide_when_moving = false; 660 661 public function __construct($params) { 662 if (empty($params['deepest_level'])) { 663 throw new Exception('Tried to instantiate a grade_edit_tree_column_name object without the "deepest_level" param!'); 664 } 665 666 $this->deepest_level = $params['deepest_level']; 667 parent::__construct(); 668 } 669 670 public function get_header_cell() { 671 $headercell = clone($this->headercell); 672 $headercell->colspan = $this->deepest_level + 1; 673 $headercell->text = get_string('name'); 674 return $headercell; 675 } 676 677 public function get_category_cell($category, $levelclass, $params) { 678 global $OUTPUT; 679 if (empty($params['name']) || empty($params['level'])) { 680 throw new Exception('Array key (name or level) missing from 3rd param of grade_edit_tree_column_name::get_category_cell($category, $levelclass, $params)'); 681 } 682 $moveaction = isset($params['moveaction']) ? $params['moveaction'] : ''; 683 $categorycell = parent::get_category_cell($category, $levelclass, $params); 684 $categorycell->colspan = ($this->deepest_level +1) - $params['level']; 685 $categorycell->text = $OUTPUT->heading($moveaction . $params['name'], 4); 686 return $categorycell; 687 } 688 689 public function get_item_cell($item, $params) { 690 global $CFG; 691 692 if (empty($params['element']) || empty($params['name']) || empty($params['level'])) { 693 throw new Exception('Array key (name, level or element) missing from 2nd param of grade_edit_tree_column_name::get_item_cell($item, $params)'); 694 } 695 696 $name = $params['name']; 697 $moveaction = isset($params['moveaction']) ? $params['moveaction'] : ''; 698 699 $itemcell = parent::get_item_cell($item, $params); 700 $itemcell->colspan = ($this->deepest_level + 1) - $params['level']; 701 $itemcell->text = $moveaction . $name; 702 return $itemcell; 703 } 704 } 705 706 /** 707 * Class grade_edit_tree_column_weight 708 * 709 * @package core_grades 710 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 711 */ 712 class grade_edit_tree_column_weight extends grade_edit_tree_column { 713 714 public function get_header_cell() { 715 global $OUTPUT; 716 $headercell = clone($this->headercell); 717 $headercell->text = get_string('weights', 'grades').$OUTPUT->help_icon('aggregationcoefweight', 'grades'); 718 return $headercell; 719 } 720 721 public function get_category_cell($category, $levelclass, $params) { 722 723 $item = $category->get_grade_item(); 724 $categorycell = parent::get_category_cell($category, $levelclass, $params); 725 $categorycell->text = grade_edit_tree::get_weight_input($item); 726 return $categorycell; 727 } 728 729 public function get_item_cell($item, $params) { 730 global $CFG; 731 if (empty($params['element'])) { 732 throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)'); 733 } 734 $itemcell = parent::get_item_cell($item, $params); 735 $itemcell->text = ' '; 736 $object = $params['element']['object']; 737 738 if (!in_array($object->itemtype, array('courseitem', 'categoryitem', 'category')) 739 && !in_array($object->gradetype, array(GRADE_TYPE_NONE, GRADE_TYPE_TEXT)) 740 && (!$object->is_outcome_item() || $object->load_parent_category()->aggregateoutcomes) 741 && ($object->gradetype != GRADE_TYPE_SCALE || !empty($CFG->grade_includescalesinaggregation))) { 742 $itemcell->text = grade_edit_tree::get_weight_input($item); 743 } 744 745 return $itemcell; 746 } 747 } 748 749 /** 750 * Class grade_edit_tree_column_range 751 * 752 * @package core_grades 753 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 754 */ 755 class grade_edit_tree_column_range extends grade_edit_tree_column { 756 757 public function get_header_cell() { 758 $headercell = clone($this->headercell); 759 $headercell->text = get_string('maxgrade', 'grades'); 760 return $headercell; 761 } 762 763 public function get_category_cell($category, $levelclass, $params) { 764 $categorycell = parent::get_category_cell($category, $levelclass, $params); 765 $categorycell->text = ' - '; 766 return $categorycell; 767 } 768 769 public function get_item_cell($item, $params) { 770 global $DB, $OUTPUT; 771 772 // If the parent aggregation is Natural, we should show the number, even for scales, as that value is used... 773 // ...in the computation. For text grades, the grademax is not used, so we can still show the no value string. 774 $parentcat = $item->get_parent_category(); 775 if ($item->gradetype == GRADE_TYPE_TEXT) { 776 $grademax = ' - '; 777 } else if ($item->gradetype == GRADE_TYPE_SCALE) { 778 $scale = $DB->get_record('scale', array('id' => $item->scaleid)); 779 $scale_items = null; 780 if (empty($scale)) { //if the item is using a scale that's been removed 781 $scale_items = array(); 782 } else { 783 $scale_items = explode(',', $scale->scale); 784 } 785 if ($parentcat->aggregation == GRADE_AGGREGATE_SUM) { 786 $grademax = end($scale_items) . ' (' . 787 format_float($item->grademax, $item->get_decimals()) . ')'; 788 } else { 789 $grademax = end($scale_items) . ' (' . count($scale_items) . ')'; 790 } 791 } else { 792 $grademax = format_float($item->grademax, $item->get_decimals()); 793 } 794 795 $isextracredit = false; 796 if ($item->aggregationcoef > 0) { 797 // For category grade items, we need the grandparent category. 798 // The parent is just category the grade item represents. 799 if ($item->is_category_item()) { 800 $grandparentcat = $parentcat->get_parent_category(); 801 if ($grandparentcat->is_extracredit_used()) { 802 $isextracredit = true; 803 } 804 } else if ($parentcat->is_extracredit_used()) { 805 $isextracredit = true; 806 } 807 } 808 if ($isextracredit) { 809 $grademax .= ' ' . html_writer::tag('abbr', get_string('aggregationcoefextrasumabbr', 'grades'), 810 array('title' => get_string('aggregationcoefextrasum', 'grades'))); 811 } 812 813 $itemcell = parent::get_item_cell($item, $params); 814 $itemcell->text = $grademax; 815 return $itemcell; 816 } 817 } 818 819 /** 820 * Class grade_edit_tree_column_actions 821 * 822 * @package core_grades 823 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 824 */ 825 class grade_edit_tree_column_actions extends grade_edit_tree_column { 826 827 public function __construct($params) { 828 parent::__construct(); 829 } 830 831 public function get_header_cell() { 832 $headercell = clone($this->headercell); 833 $headercell->text = get_string('actions'); 834 return $headercell; 835 } 836 837 public function get_category_cell($category, $levelclass, $params) { 838 839 if (empty($params['actions'])) { 840 throw new Exception('Array key (actions) missing from 3rd param of grade_edit_tree_column_actions::get_category_actions($category, $levelclass, $params)'); 841 } 842 843 $categorycell = parent::get_category_cell($category, $levelclass, $params); 844 $categorycell->text = $params['actions']; 845 return $categorycell; 846 } 847 848 public function get_item_cell($item, $params) { 849 if (empty($params['actions'])) { 850 throw new Exception('Array key (actions) missing from 2nd param of grade_edit_tree_column_actions::get_item_cell($item, $params)'); 851 } 852 $itemcell = parent::get_item_cell($item, $params); 853 $itemcell->text = $params['actions']; 854 return $itemcell; 855 } 856 } 857 858 /** 859 * Class grade_edit_tree_column_select 860 * 861 * @package core_grades 862 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 863 */ 864 class grade_edit_tree_column_select extends grade_edit_tree_column { 865 866 public function get_header_cell() { 867 $headercell = clone($this->headercell); 868 $headercell->text = get_string('select'); 869 return $headercell; 870 } 871 872 public function get_category_cell($category, $levelclass, $params) { 873 global $OUTPUT; 874 875 if (empty($params['eid'])) { 876 throw new Exception('Array key (eid) missing from 3rd param of grade_edit_tree_column_select::get_category_cell($category, $levelclass, $params)'); 877 } 878 879 // Get toggle group for this master checkbox. 880 $togglegroup = $this->get_checkbox_togglegroup($category); 881 // Set label for this master checkbox. 882 $masterlabel = get_string('all'); 883 // Use category name if available. 884 if ($category->fullname !== '?') { 885 $masterlabel = format_string($category->fullname); 886 // Limit the displayed category name to prevent the Select column from getting too wide. 887 if (core_text::strlen($masterlabel) > 20) { 888 $masterlabel = get_string('textellipsis', 'core', core_text::substr($masterlabel, 0, 12)); 889 } 890 } 891 // Build the master checkbox. 892 $mastercheckbox = new \core\output\checkbox_toggleall($togglegroup, true, [ 893 'id' => $togglegroup, 894 'name' => $togglegroup, 895 'value' => 1, 896 'classes' => 'itemselect ignoredirty', 897 'label' => $masterlabel, 898 // Consistent label to prevent the select column from resizing. 899 'selectall' => $masterlabel, 900 'deselectall' => $masterlabel, 901 'labelclasses' => 'm-0', 902 ]); 903 904 $categorycell = parent::get_category_cell($category, $levelclass, $params); 905 $categorycell->text = $OUTPUT->render($mastercheckbox); 906 return $categorycell; 907 } 908 909 public function get_item_cell($item, $params) { 910 if (empty($params['itemtype']) || empty($params['eid'])) { 911 print_error('missingitemtypeoreid', 'core_grades'); 912 } 913 $itemcell = parent::get_item_cell($item, $params); 914 915 if ($params['itemtype'] != 'course' && $params['itemtype'] != 'category') { 916 global $OUTPUT; 917 918 // Fetch the grade item's category. 919 $category = grade_category::fetch(['id' => $item->categoryid]); 920 $togglegroup = $this->get_checkbox_togglegroup($category); 921 922 $checkboxid = 'select_' . $params['eid']; 923 $checkbox = new \core\output\checkbox_toggleall($togglegroup, false, [ 924 'id' => $checkboxid, 925 'name' => $checkboxid, 926 'label' => get_string('select', 'grades', $item->itemname), 927 'labelclasses' => 'accesshide', 928 'classes' => 'itemselect ignoredirty', 929 ]); 930 $itemcell->text = $OUTPUT->render($checkbox); 931 } 932 return $itemcell; 933 } 934 935 /** 936 * Generates a toggle group name for a bulk-action checkbox based on the given grade category. 937 * 938 * @param grade_category $category The grade category. 939 * @return string 940 */ 941 protected function get_checkbox_togglegroup(grade_category $category): string { 942 $levels = []; 943 $categories = explode('/', $category->path); 944 foreach ($categories as $categoryid) { 945 $level = 'category' . $categoryid; 946 if (!in_array($level, $levels)) { 947 $levels[] = 'category' . $categoryid; 948 } 949 } 950 $togglegroup = implode(' ', $levels); 951 952 return $togglegroup; 953 } 954 } 955
title
Description
Body
title
Description
Body
title
Description
Body
title
Body