Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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, $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 133 if (!$is_category_item && ($icon = $this->gtree->get_edit_icon($element, $this->gpr, true))) { 134 $actionsmenu->add($icon); 135 } 136 // MDL-49281 if grade_item already has calculation, it should be editable even if global setting is off. 137 $type = $element['type']; 138 $iscalculated = ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') && $object->is_calculated(); 139 $icon = $this->gtree->get_calculation_icon($element, $this->gpr, true); 140 if ($iscalculated || ($this->show_calculations && $icon)) { 141 $actionsmenu->add($icon); 142 } 143 144 if ($element['type'] == 'item' or ($element['type'] == 'category' and $element['depth'] > 1)) { 145 if ($this->element_deletable($element)) { 146 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'delete', 'eid' => $eid, 'sesskey' => sesskey())); 147 $icon = new action_menu_link_secondary($aurl, new pix_icon('t/delete', get_string('delete')), get_string('delete')); 148 $actionsmenu->add($icon); 149 } 150 151 if ($this->element_duplicatable($element)) { 152 $duplicateparams = array(); 153 $duplicateparams['id'] = $COURSE->id; 154 $duplicateparams['action'] = 'duplicate'; 155 $duplicateparams['eid'] = $eid; 156 $duplicateparams['sesskey'] = sesskey(); 157 $aurl = new moodle_url('index.php', $duplicateparams); 158 $duplicateicon = new pix_icon('t/copy', get_string('duplicate')); 159 $icon = new action_menu_link_secondary($aurl, $duplicateicon, get_string('duplicate')); 160 $actionsmenu->add($icon); 161 } 162 163 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'moveselect', 'eid' => $eid, 'sesskey' => sesskey())); 164 $moveaction .= $OUTPUT->action_icon($aurl, new pix_icon('t/move', get_string('move'))); 165 } 166 167 if ($icon = $this->gtree->get_hiding_icon($element, $this->gpr, true)) { 168 $actionsmenu->add($icon); 169 } 170 171 if ($icon = $this->gtree->get_reset_icon($element, $this->gpr, true)) { 172 $actionsmenu->add($icon); 173 } 174 175 $actions = $OUTPUT->render($actionsmenu); 176 177 $returnrows = array(); 178 $root = false; 179 180 $id = required_param('id', PARAM_INT); 181 182 /// prepare move target if needed 183 $last = ''; 184 185 /// print the list items now 186 if ($this->moving == $eid) { 187 // do not diplay children 188 $cell = new html_table_cell(); 189 $cell->colspan = 12; 190 $cell->attributes['class'] = $element['type'] . ' moving column-name level' . 191 ($level + 1) . ' level' . ($level % 2 ? 'even' : 'odd'); 192 $cell->text = $object->name.' ('.get_string('move').')'; 193 return array(new html_table_row(array($cell))); 194 } 195 196 if ($element['type'] == 'category') { 197 $level++; 198 $this->categories[$object->id] = $object->stripped_name; 199 $category = grade_category::fetch(array('id' => $object->id)); 200 $item = $category->get_grade_item(); 201 202 // Add aggregation coef input if not a course item and if parent category has correct aggregation type 203 $dimmed = ($item->is_hidden()) ? 'dimmed_text' : ''; 204 205 // Before we print the category's row, we must find out how many rows will appear below it (for the filler cell's rowspan) 206 $aggregation_position = grade_get_setting($COURSE->id, 'aggregationposition', $CFG->grade_aggregationposition); 207 $category_total_data = null; // Used if aggregationposition is set to "last", so we can print it last 208 209 $html_children = array(); 210 211 $row_count = 0; 212 213 foreach($element['children'] as $child_el) { 214 $moveto = null; 215 216 if (empty($child_el['object']->itemtype)) { 217 $child_el['object']->itemtype = false; 218 } 219 220 if (($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') && !$totals) { 221 continue; 222 } 223 224 $child_eid = $child_el['eid']; 225 $first = ''; 226 227 if ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') { 228 $first = array('first' => 1); 229 $child_eid = $eid; 230 } 231 232 if ($this->moving && $this->moving != $child_eid) { 233 234 $strmove = get_string('move'); 235 $actions = $moveaction = ''; // no action icons when moving 236 237 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'move', 'eid' => $this->moving, 'moveafter' => $child_eid, 'sesskey' => sesskey())); 238 if ($first) { 239 $aurl->params($first); 240 } 241 242 $cell = new html_table_cell(); 243 $cell->colspan = 12; 244 $cell->attributes['class'] = 'movehere level' . ($level + 1) . ' level' . ($level % 2 ? 'even' : 'odd'); 245 246 $cell->text = html_writer::link($aurl, '', array('title' => get_string('movehere'), 'class' => 'movehere')); 247 248 $moveto = new html_table_row(array($cell)); 249 } 250 251 $newparents = $parents; 252 $newparents[] = $eid; 253 254 $row_count++; 255 $child_row_count = 0; 256 257 // If moving, do not print course and category totals, but still print the moveto target box 258 if ($this->moving && ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category')) { 259 $html_children[] = $moveto; 260 } elseif ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') { 261 // We don't build the item yet because we first need to know the deepest level of categories (for category/name colspans) 262 $category_total_item = $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count); 263 if (!$aggregation_position) { 264 $html_children = array_merge($html_children, $category_total_item); 265 } 266 } else { 267 $html_children = array_merge($html_children, $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count)); 268 if (!empty($moveto)) { 269 $html_children[] = $moveto; 270 } 271 272 if ($this->moving) { 273 $row_count++; 274 } 275 } 276 277 $row_count += $child_row_count; 278 279 // If the child is a category, increment row_count by one more (for the extra coloured row) 280 if ($child_el['type'] == 'category') { 281 $row_count++; 282 } 283 } 284 285 // Print category total at the end if aggregation position is "last" (1) 286 if (!empty($category_total_item) && $aggregation_position) { 287 $html_children = array_merge($html_children, $category_total_item); 288 } 289 290 // Determine if we are at the root 291 if (isset($element['object']->grade_item) && $element['object']->grade_item->is_course_item()) { 292 $root = true; 293 } 294 295 $levelclass = "level$level level" . ($level % 2 ? 'odd' : 'even'); 296 297 $courseclass = ''; 298 if ($level == 1) { 299 $courseclass = 'coursecategory'; 300 } 301 302 $row = new html_table_row(); 303 $row->id = 'grade-item-' . $eid; 304 $row->attributes['class'] = $courseclass . ' category ' . $dimmed; 305 $row->attributes['data-category'] = $eid; 306 $row->attributes['data-itemid'] = $category->get_grade_item()->id; 307 foreach ($rowclasses as $class) { 308 $row->attributes['class'] .= ' ' . $class; 309 } 310 311 $headercell = new html_table_cell(); 312 $headercell->header = true; 313 $headercell->scope = 'row'; 314 $headercell->attributes['title'] = $object->stripped_name; 315 $headercell->attributes['class'] = 'cell column-rowspan rowspan ' . $levelclass; 316 $headercell->rowspan = $row_count + 1; 317 $row->cells[] = $headercell; 318 319 foreach ($this->columns as $column) { 320 if (!($this->moving && $column->hide_when_moving)) { 321 $row->cells[] = $column->get_category_cell($category, $levelclass, [ 322 'id' => $id, 323 'name' => $object->name, 324 'level' => $level, 325 'actions' => $actions, 326 'moveaction' => $moveaction, 327 'eid' => $eid, 328 ]); 329 } 330 } 331 332 $returnrows[] = $row; 333 334 $returnrows = array_merge($returnrows, $html_children); 335 336 // Print a coloured row to show the end of the category across the table 337 $endcell = new html_table_cell(); 338 $endcell->colspan = (19 - $level); 339 $endcell->attributes['class'] = 'emptyrow colspan ' . $levelclass; 340 341 $returnrows[] = new html_table_row(array($endcell)); 342 343 } else { // Dealing with a grade item 344 345 $item = grade_item::fetch(array('id' => $object->id)); 346 $element['type'] = 'item'; 347 $element['object'] = $item; 348 349 $categoryitemclass = ''; 350 if ($item->itemtype == 'category') { 351 $categoryitemclass = 'categoryitem'; 352 } 353 if ($item->itemtype == 'course') { 354 $categoryitemclass = 'courseitem'; 355 } 356 357 $dimmed = ($item->is_hidden()) ? "dimmed_text" : ""; 358 $gradeitemrow = new html_table_row(); 359 $gradeitemrow->id = 'grade-item-' . $eid; 360 $gradeitemrow->attributes['class'] = $categoryitemclass . ' item ' . $dimmed; 361 $gradeitemrow->attributes['data-itemid'] = $object->id; 362 foreach ($rowclasses as $class) { 363 $gradeitemrow->attributes['class'] .= ' ' . $class; 364 } 365 366 foreach ($this->columns as $column) { 367 if (!($this->moving && $column->hide_when_moving)) { 368 $gradeitemrow->cells[] = $column->get_item_cell($item, array('id' => $id, 'name' => $object->name, 369 'level' => $level, 'actions' => $actions, 'element' => $element, 'eid' => $eid, 370 'moveaction' => $moveaction, 'itemtype' => $object->itemtype)); 371 } 372 } 373 374 $returnrows[] = $gradeitemrow; 375 } 376 377 return $returnrows; 378 379 } 380 381 /** 382 * Given a grade_item object, returns a labelled input if an aggregation coefficient (weight or extra credit) applies to it. 383 * @param grade_item $item 384 * @return string HTML 385 */ 386 static function get_weight_input($item) { 387 global $OUTPUT; 388 389 if (!is_object($item) || get_class($item) !== 'grade_item') { 390 throw new Exception('grade_edit_tree::get_weight_input($item) was given a variable that is not of the required type (grade_item object)'); 391 return false; 392 } 393 394 if ($item->is_course_item()) { 395 return ''; 396 } 397 398 $parent_category = $item->get_parent_category(); 399 $parent_category->apply_forced_settings(); 400 $aggcoef = $item->get_coefstring(); 401 402 $itemname = $item->itemname; 403 if ($item->is_category_item()) { 404 // Remember, the parent category of a category item is the category itself. 405 $itemname = $parent_category->get_name(); 406 } 407 $str = ''; 408 409 if ($aggcoef == 'aggregationcoefweight' || $aggcoef == 'aggregationcoef' || $aggcoef == 'aggregationcoefextraweight') { 410 411 return $OUTPUT->render_from_template('core_grades/weight_field', [ 412 'id' => $item->id, 413 'itemname' => $itemname, 414 'value' => self::format_number($item->aggregationcoef) 415 ]); 416 417 } else if ($aggcoef == 'aggregationcoefextraweightsum') { 418 419 $tpldata = [ 420 'id' => $item->id, 421 'itemname' => $itemname, 422 'value' => self::format_number($item->aggregationcoef2 * 100.0), 423 'checked' => $item->weightoverride, 424 'disabled' => !$item->weightoverride 425 ]; 426 $str .= $OUTPUT->render_from_template('core_grades/weight_override_field', $tpldata); 427 428 } 429 430 return $str; 431 } 432 433 // Trims trailing zeros. 434 // Used on the 'Gradebook setup' page for grade items settings like aggregation co-efficient. 435 // Grader report has its own decimal place settings so they are handled elsewhere. 436 static function format_number($number) { 437 $formatted = rtrim(format_float($number, 4),'0'); 438 if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point 439 $formatted .= '0'; 440 } 441 return $formatted; 442 } 443 444 /** 445 * Given an element of the grade tree, returns whether it is deletable or not (only manual grade items are deletable) 446 * 447 * @param array $element 448 * @return bool 449 */ 450 function element_deletable($element) { 451 global $COURSE; 452 453 if ($element['type'] != 'item') { 454 return true; 455 } 456 457 $grade_item = $element['object']; 458 459 if ($grade_item->itemtype != 'mod' or $grade_item->is_outcome_item() or $grade_item->gradetype == GRADE_TYPE_NONE) { 460 return true; 461 } 462 463 $modinfo = get_fast_modinfo($COURSE); 464 if (!isset($modinfo->instances[$grade_item->itemmodule][$grade_item->iteminstance])) { 465 // module does not exist 466 return true; 467 } 468 469 return false; 470 } 471 472 /** 473 * Given an element of the grade tree, returns whether it is duplicatable or not (only manual grade items are duplicatable) 474 * 475 * @param array $element 476 * @return bool 477 */ 478 public function element_duplicatable($element) { 479 if ($element['type'] != 'item') { 480 return false; 481 } 482 483 $gradeitem = $element['object']; 484 if ($gradeitem->itemtype != 'mod') { 485 return true; 486 } 487 return false; 488 } 489 490 /** 491 * Given the grade tree and an array of element ids (e.g. c15, i42), and expecting the 'moveafter' URL param, 492 * moves the selected items to the requested location. Then redirects the user to the given $returnurl 493 * 494 * @param object $gtree The grade tree (a recursive representation of the grade categories and grade items) 495 * @param array $eids 496 * @param string $returnurl 497 */ 498 function move_elements($eids, $returnurl) { 499 $moveafter = required_param('moveafter', PARAM_INT); 500 501 if (!is_array($eids)) { 502 $eids = array($eids); 503 } 504 505 if(!$after_el = $this->gtree->locate_element("cg$moveafter")) { 506 print_error('invalidelementid', '', $returnurl); 507 } 508 509 $after = $after_el['object']; 510 $parent = $after; 511 $sortorder = $after->get_sortorder(); 512 513 foreach ($eids as $eid) { 514 if (!$element = $this->gtree->locate_element($eid)) { 515 print_error('invalidelementid', '', $returnurl); 516 } 517 $object = $element['object']; 518 519 $object->set_parent($parent->id); 520 $object->move_after_sortorder($sortorder); 521 $sortorder++; 522 } 523 524 redirect($returnurl, '', 0); 525 } 526 527 /** 528 * Recurses through the entire grade tree to find and return the maximum depth of the tree. 529 * This should be run only once from the root element (course category), and is used for the 530 * indentation of the Name column's cells (colspan) 531 * 532 * @param array $element An array of values representing a grade tree's element (all grade items in this case) 533 * @param int $level The level of the current recursion 534 * @param int $deepest_level A value passed to each subsequent level of recursion and incremented if $level > $deepest_level 535 * @return int Deepest level 536 */ 537 function get_deepest_level($element, $level=0, $deepest_level=1) { 538 $object = $element['object']; 539 540 $level++; 541 $coefstring = $element['object']->get_coefstring(); 542 if ($element['type'] == 'category') { 543 if ($coefstring == 'aggregationcoefweight' || $coefstring == 'aggregationcoefextraweightsum' || 544 $coefstring == 'aggregationcoefextraweight') { 545 $this->uses_weight = true; 546 } 547 548 foreach($element['children'] as $child_el) { 549 if ($level > $deepest_level) { 550 $deepest_level = $level; 551 } 552 $deepest_level = $this->get_deepest_level($child_el, $level, $deepest_level); 553 } 554 555 $category = grade_category::fetch(array('id' => $object->id)); 556 $item = $category->get_grade_item(); 557 if ($item->gradetype == GRADE_TYPE_NONE) { 558 // Add 1 more level for grade category that has no total. 559 $deepest_level++; 560 } 561 } 562 563 return $deepest_level; 564 } 565 566 /** 567 * Updates the provided gradecategory item with the provided data. 568 * 569 * @param grade_category $gradecategory The category to update. 570 * @param stdClass $data the data to update the category with. 571 * @return void 572 */ 573 public static function update_gradecategory(grade_category $gradecategory, stdClass $data) { 574 // If no fullname is entered for a course category, put ? in the DB. 575 if (!isset($data->fullname) || $data->fullname == '') { 576 $data->fullname = '?'; 577 } 578 579 if (!isset($data->aggregateonlygraded)) { 580 $data->aggregateonlygraded = 0; 581 } 582 if (!isset($data->aggregateoutcomes)) { 583 $data->aggregateoutcomes = 0; 584 } 585 grade_category::set_properties($gradecategory, $data); 586 587 // CATEGORY. 588 if (empty($gradecategory->id)) { 589 $gradecategory->insert(); 590 591 } else { 592 $gradecategory->update(); 593 } 594 595 // GRADE ITEM. 596 // Grade item data saved with prefix "grade_item_". 597 $itemdata = new stdClass(); 598 foreach ($data as $k => $v) { 599 if (preg_match('/grade_item_(.*)/', $k, $matches)) { 600 $itemdata->{$matches[1]} = $v; 601 } 602 } 603 604 if (!isset($itemdata->aggregationcoef)) { 605 $itemdata->aggregationcoef = 0; 606 } 607 608 if (!isset($itemdata->gradepass) || $itemdata->gradepass == '') { 609 $itemdata->gradepass = 0; 610 } 611 612 if (!isset($itemdata->grademax) || $itemdata->grademax == '') { 613 $itemdata->grademax = 0; 614 } 615 616 if (!isset($itemdata->grademin) || $itemdata->grademin == '') { 617 $itemdata->grademin = 0; 618 } 619 620 $hidden = empty($itemdata->hidden) ? 0 : $itemdata->hidden; 621 $hiddenuntil = empty($itemdata->hiddenuntil) ? 0 : $itemdata->hiddenuntil; 622 unset($itemdata->hidden); 623 unset($itemdata->hiddenuntil); 624 625 $locked = empty($itemdata->locked) ? 0 : $itemdata->locked; 626 $locktime = empty($itemdata->locktime) ? 0 : $itemdata->locktime; 627 unset($itemdata->locked); 628 unset($itemdata->locktime); 629 630 $convert = array('grademax', 'grademin', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef', 'aggregationcoef2'); 631 foreach ($convert as $param) { 632 if (property_exists($itemdata, $param)) { 633 $itemdata->$param = unformat_float($itemdata->$param); 634 } 635 } 636 if (isset($itemdata->aggregationcoef2)) { 637 $itemdata->aggregationcoef2 = $itemdata->aggregationcoef2 / 100.0; 638 } 639 640 // When creating a new category, a number of grade item fields are filled out automatically, and are required. 641 // If the user leaves these fields empty during creation of a category, we let the default values take effect. 642 // Otherwise, we let the user-entered grade item values take effect. 643 $gradeitem = $gradecategory->load_grade_item(); 644 $gradeitemcopy = fullclone($gradeitem); 645 grade_item::set_properties($gradeitem, $itemdata); 646 647 if (empty($gradeitem->id)) { 648 $gradeitem->id = $gradeitemcopy->id; 649 } 650 if (empty($gradeitem->grademax) && $gradeitem->grademax != '0') { 651 $gradeitem->grademax = $gradeitemcopy->grademax; 652 } 653 if (empty($gradeitem->grademin) && $gradeitem->grademin != '0') { 654 $gradeitem->grademin = $gradeitemcopy->grademin; 655 } 656 if (empty($gradeitem->gradepass) && $gradeitem->gradepass != '0') { 657 $gradeitem->gradepass = $gradeitemcopy->gradepass; 658 } 659 if (empty($gradeitem->aggregationcoef) && $gradeitem->aggregationcoef != '0') { 660 $gradeitem->aggregationcoef = $gradeitemcopy->aggregationcoef; 661 } 662 663 // Handle null decimals value - must be done before update! 664 if (!property_exists($itemdata, 'decimals') or $itemdata->decimals < 0) { 665 $gradeitem->decimals = null; 666 } 667 668 // Change weightoverride flag. Check if the value is set, because it is not when the checkbox is not ticked. 669 $itemdata->weightoverride = isset($itemdata->weightoverride) ? $itemdata->weightoverride : 0; 670 if ($gradeitem->weightoverride != $itemdata->weightoverride && $gradecategory->aggregation == GRADE_AGGREGATE_SUM) { 671 // If we are using natural weight and the weight has been un-overriden, force parent category to recalculate weights. 672 $gradecategory->force_regrading(); 673 } 674 $gradeitem->weightoverride = $itemdata->weightoverride; 675 676 $gradeitem->outcomeid = null; 677 678 // This means we want to rescale overridden grades as well. 679 if (!empty($data->grade_item_rescalegrades) && $data->grade_item_rescalegrades == 'yes') { 680 $gradeitem->markasoverriddenwhengraded = false; 681 $gradeitem->rescale_grades_keep_percentage($gradeitemcopy->grademin, $gradeitemcopy->grademax, 682 $gradeitem->grademin, $gradeitem->grademax, 'gradebook'); 683 } 684 685 // Only update the category's 'hidden' status if it has changed. Leaving a category as 'unhidden' (checkbox left 686 // unmarked) and submitting the form without this conditional check will result in displaying any grade items that 687 // are in the category, including those that were previously 'hidden'. 688 if (($gradecategory->get_hidden() != $hiddenuntil) || ($gradecategory->get_hidden() != $hidden)) { 689 if ($hiddenuntil) { 690 $gradecategory->set_hidden($hiddenuntil, true); 691 } else { 692 $gradecategory->set_hidden($hidden, true); 693 } 694 } 695 696 $gradeitem->set_locktime($locktime); // Locktime first - it might be removed when unlocking. 697 $gradeitem->set_locked($locked, false, true); 698 699 $gradeitem->update(); // We don't need to insert it, it's already created when the category is created. 700 701 // Set parent if needed. 702 if (isset($data->parentcategory)) { 703 $gradecategory->set_parent($data->parentcategory, 'gradebook'); 704 } 705 } 706 } 707 708 /** 709 * Class grade_edit_tree_column 710 * 711 * @package core_grades 712 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 713 */ 714 abstract class grade_edit_tree_column { 715 public $forced; 716 public $hidden; 717 public $forced_hidden; 718 public $advanced_hidden; 719 public $hide_when_moving = true; 720 /** 721 * html_table_cell object used as a template for header cells in all categories. 722 * It must be cloned before being used. 723 * @var html_table_cell $headercell 724 */ 725 public $headercell; 726 /** 727 * html_table_cell object used as a template for category cells in all categories. 728 * It must be cloned before being used. 729 * @var html_table_cell $categorycell 730 */ 731 public $categorycell; 732 /** 733 * html_table_cell object used as a template for item cells in all categories. 734 * It must be cloned before being used. 735 * @var html_table_cell $itemcell 736 */ 737 public $itemcell; 738 739 public static function factory($name, $params=array()) { 740 $class_name = "grade_edit_tree_column_$name"; 741 if (class_exists($class_name)) { 742 return new $class_name($params); 743 } 744 } 745 746 public abstract function get_header_cell(); 747 748 public function get_category_cell($category, $levelclass, $params) { 749 $cell = clone($this->categorycell); 750 $cell->attributes['class'] .= ' ' . $levelclass; 751 $cell->attributes['text'] = ''; 752 return $cell; 753 } 754 755 public function get_item_cell($item, $params) { 756 $cell = clone($this->itemcell); 757 $cell->attributes['text'] = ''; 758 if (isset($params['level'])) { 759 $level = $params['level'] + (($item->itemtype == 'category' || $item->itemtype == 'course') ? 0 : 1); 760 $cell->attributes['class'] .= ' level' . $level; 761 $cell->attributes['class'] .= ' level' . ($level % 2 ? 'odd' : 'even'); 762 } 763 return $cell; 764 } 765 766 public function __construct() { 767 $this->headercell = new html_table_cell(); 768 $this->headercell->header = true; 769 $this->headercell->attributes['class'] = 'header'; 770 771 $this->categorycell = new html_table_cell(); 772 $this->categorycell->attributes['class'] = 'cell'; 773 774 $this->itemcell = new html_table_cell(); 775 $this->itemcell->attributes['class'] = 'cell'; 776 777 if (preg_match('/^grade_edit_tree_column_(\w*)$/', get_class($this), $matches)) { 778 $this->headercell->attributes['class'] .= ' column-' . $matches[1]; 779 $this->categorycell->attributes['class'] .= ' column-' . $matches[1]; 780 $this->itemcell->attributes['class'] .= ' column-' . $matches[1]; 781 } 782 } 783 } 784 785 /** 786 * Class grade_edit_tree_column_name 787 * 788 * @package core_grades 789 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 790 */ 791 class grade_edit_tree_column_name extends grade_edit_tree_column { 792 public $forced = false; 793 public $hidden = false; 794 public $forced_hidden = false; 795 public $advanced_hidden = false; 796 public $deepest_level = 1; 797 public $hide_when_moving = false; 798 799 public function __construct($params) { 800 if (empty($params['deepest_level'])) { 801 throw new Exception('Tried to instantiate a grade_edit_tree_column_name object without the "deepest_level" param!'); 802 } 803 804 $this->deepest_level = $params['deepest_level']; 805 parent::__construct(); 806 } 807 808 public function get_header_cell() { 809 $headercell = clone($this->headercell); 810 $headercell->colspan = $this->deepest_level + 1; 811 $headercell->text = get_string('name'); 812 return $headercell; 813 } 814 815 public function get_category_cell($category, $levelclass, $params) { 816 global $OUTPUT; 817 if (empty($params['name']) || empty($params['level'])) { 818 throw new Exception('Array key (name or level) missing from 3rd param of grade_edit_tree_column_name::get_category_cell($category, $levelclass, $params)'); 819 } 820 $moveaction = isset($params['moveaction']) ? $params['moveaction'] : ''; 821 $categorycell = parent::get_category_cell($category, $levelclass, $params); 822 $categorycell->colspan = ($this->deepest_level +1) - $params['level']; 823 $categorycell->text = $OUTPUT->heading($moveaction . $params['name'], 4); 824 return $categorycell; 825 } 826 827 public function get_item_cell($item, $params) { 828 global $CFG; 829 830 if (empty($params['element']) || empty($params['name']) || empty($params['level'])) { 831 throw new Exception('Array key (name, level or element) missing from 2nd param of grade_edit_tree_column_name::get_item_cell($item, $params)'); 832 } 833 834 $name = $params['name']; 835 $moveaction = isset($params['moveaction']) ? $params['moveaction'] : ''; 836 837 $itemcell = parent::get_item_cell($item, $params); 838 $itemcell->colspan = ($this->deepest_level + 1) - $params['level']; 839 $itemcell->text = $moveaction . $name; 840 return $itemcell; 841 } 842 } 843 844 /** 845 * Class grade_edit_tree_column_weight 846 * 847 * @package core_grades 848 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 849 */ 850 class grade_edit_tree_column_weight extends grade_edit_tree_column { 851 852 public function get_header_cell() { 853 global $OUTPUT; 854 $headercell = clone($this->headercell); 855 $headercell->text = get_string('weights', 'grades').$OUTPUT->help_icon('aggregationcoefweight', 'grades'); 856 return $headercell; 857 } 858 859 public function get_category_cell($category, $levelclass, $params) { 860 861 $item = $category->get_grade_item(); 862 $categorycell = parent::get_category_cell($category, $levelclass, $params); 863 $categorycell->text = grade_edit_tree::get_weight_input($item); 864 return $categorycell; 865 } 866 867 public function get_item_cell($item, $params) { 868 global $CFG; 869 if (empty($params['element'])) { 870 throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)'); 871 } 872 $itemcell = parent::get_item_cell($item, $params); 873 $itemcell->text = ' '; 874 $object = $params['element']['object']; 875 876 if (!in_array($object->itemtype, array('courseitem', 'categoryitem', 'category')) 877 && !in_array($object->gradetype, array(GRADE_TYPE_NONE, GRADE_TYPE_TEXT)) 878 && (!$object->is_outcome_item() || $object->load_parent_category()->aggregateoutcomes) 879 && ($object->gradetype != GRADE_TYPE_SCALE || !empty($CFG->grade_includescalesinaggregation))) { 880 $itemcell->text = grade_edit_tree::get_weight_input($item); 881 } 882 883 return $itemcell; 884 } 885 } 886 887 /** 888 * Class grade_edit_tree_column_range 889 * 890 * @package core_grades 891 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 892 */ 893 class grade_edit_tree_column_range extends grade_edit_tree_column { 894 895 public function get_header_cell() { 896 $headercell = clone($this->headercell); 897 $headercell->text = get_string('maxgrade', 'grades'); 898 return $headercell; 899 } 900 901 public function get_category_cell($category, $levelclass, $params) { 902 $categorycell = parent::get_category_cell($category, $levelclass, $params); 903 $categorycell->text = ' - '; 904 return $categorycell; 905 } 906 907 public function get_item_cell($item, $params) { 908 global $DB, $OUTPUT; 909 910 // If the parent aggregation is Natural, we should show the number, even for scales, as that value is used... 911 // ...in the computation. For text grades, the grademax is not used, so we can still show the no value string. 912 $parentcat = $item->get_parent_category(); 913 if ($item->gradetype == GRADE_TYPE_TEXT) { 914 $grademax = ' - '; 915 } else if ($item->gradetype == GRADE_TYPE_SCALE) { 916 $scale = $DB->get_record('scale', array('id' => $item->scaleid)); 917 $scale_items = null; 918 if (empty($scale)) { //if the item is using a scale that's been removed 919 $scale_items = array(); 920 } else { 921 $scale_items = explode(',', $scale->scale); 922 } 923 if ($parentcat->aggregation == GRADE_AGGREGATE_SUM) { 924 $grademax = end($scale_items) . ' (' . 925 format_float($item->grademax, $item->get_decimals()) . ')'; 926 } else { 927 $grademax = end($scale_items) . ' (' . count($scale_items) . ')'; 928 } 929 } else { 930 $grademax = format_float($item->grademax, $item->get_decimals()); 931 } 932 933 $isextracredit = false; 934 if ($item->aggregationcoef > 0) { 935 // For category grade items, we need the grandparent category. 936 // The parent is just category the grade item represents. 937 if ($item->is_category_item()) { 938 $grandparentcat = $parentcat->get_parent_category(); 939 if ($grandparentcat->is_extracredit_used()) { 940 $isextracredit = true; 941 } 942 } else if ($parentcat->is_extracredit_used()) { 943 $isextracredit = true; 944 } 945 } 946 if ($isextracredit) { 947 $grademax .= ' ' . html_writer::tag('abbr', get_string('aggregationcoefextrasumabbr', 'grades'), 948 array('title' => get_string('aggregationcoefextrasum', 'grades'))); 949 } 950 951 $itemcell = parent::get_item_cell($item, $params); 952 $itemcell->text = $grademax; 953 return $itemcell; 954 } 955 } 956 957 /** 958 * Class grade_edit_tree_column_actions 959 * 960 * @package core_grades 961 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 962 */ 963 class grade_edit_tree_column_actions extends grade_edit_tree_column { 964 965 public function __construct($params) { 966 parent::__construct(); 967 } 968 969 public function get_header_cell() { 970 $headercell = clone($this->headercell); 971 $headercell->text = get_string('actions'); 972 return $headercell; 973 } 974 975 public function get_category_cell($category, $levelclass, $params) { 976 977 if (empty($params['actions'])) { 978 throw new Exception('Array key (actions) missing from 3rd param of grade_edit_tree_column_actions::get_category_actions($category, $levelclass, $params)'); 979 } 980 981 $categorycell = parent::get_category_cell($category, $levelclass, $params); 982 $categorycell->text = $params['actions']; 983 return $categorycell; 984 } 985 986 public function get_item_cell($item, $params) { 987 if (empty($params['actions'])) { 988 throw new Exception('Array key (actions) missing from 2nd param of grade_edit_tree_column_actions::get_item_cell($item, $params)'); 989 } 990 $itemcell = parent::get_item_cell($item, $params); 991 $itemcell->text = $params['actions']; 992 return $itemcell; 993 } 994 } 995 996 /** 997 * Class grade_edit_tree_column_select 998 * 999 * @package core_grades 1000 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1001 */ 1002 class grade_edit_tree_column_select extends grade_edit_tree_column { 1003 1004 public function get_header_cell() { 1005 $headercell = clone($this->headercell); 1006 $headercell->text = get_string('select'); 1007 return $headercell; 1008 } 1009 1010 public function get_category_cell($category, $levelclass, $params) { 1011 global $OUTPUT; 1012 1013 if (empty($params['eid'])) { 1014 throw new Exception('Array key (eid) missing from 3rd param of grade_edit_tree_column_select::get_category_cell($category, $levelclass, $params)'); 1015 } 1016 1017 // Get toggle group for this master checkbox. 1018 $togglegroup = $this->get_checkbox_togglegroup($category); 1019 // Set label for this master checkbox. 1020 $masterlabel = get_string('all'); 1021 // Use category name if available. 1022 if ($category->fullname !== '?') { 1023 $masterlabel = format_string($category->fullname, true, ['escape' => false]); 1024 // Limit the displayed category name to prevent the Select column from getting too wide. 1025 if (core_text::strlen($masterlabel) > 20) { 1026 $masterlabel = get_string('textellipsis', 'core', core_text::substr($masterlabel, 0, 12)); 1027 } 1028 } 1029 // Build the master checkbox. 1030 $mastercheckbox = new \core\output\checkbox_toggleall($togglegroup, true, [ 1031 'id' => $togglegroup, 1032 'name' => $togglegroup, 1033 'value' => 1, 1034 'classes' => 'itemselect ignoredirty', 1035 'label' => $masterlabel, 1036 // Consistent label to prevent the select column from resizing. 1037 'selectall' => $masterlabel, 1038 'deselectall' => $masterlabel, 1039 'labelclasses' => 'm-0', 1040 ]); 1041 1042 $categorycell = parent::get_category_cell($category, $levelclass, $params); 1043 $categorycell->text = $OUTPUT->render($mastercheckbox); 1044 return $categorycell; 1045 } 1046 1047 public function get_item_cell($item, $params) { 1048 if (empty($params['itemtype']) || empty($params['eid'])) { 1049 print_error('missingitemtypeoreid', 'core_grades'); 1050 } 1051 $itemcell = parent::get_item_cell($item, $params); 1052 1053 if ($params['itemtype'] != 'course' && $params['itemtype'] != 'category') { 1054 global $OUTPUT; 1055 1056 // Fetch the grade item's category. 1057 $category = grade_category::fetch(['id' => $item->categoryid]); 1058 $togglegroup = $this->get_checkbox_togglegroup($category); 1059 1060 $checkboxid = 'select_' . $params['eid']; 1061 $checkbox = new \core\output\checkbox_toggleall($togglegroup, false, [ 1062 'id' => $checkboxid, 1063 'name' => $checkboxid, 1064 'label' => get_string('select', 'grades', $item->itemname), 1065 'labelclasses' => 'accesshide', 1066 'classes' => 'itemselect ignoredirty', 1067 ]); 1068 $itemcell->text = $OUTPUT->render($checkbox); 1069 } 1070 return $itemcell; 1071 } 1072 1073 /** 1074 * Generates a toggle group name for a bulk-action checkbox based on the given grade category. 1075 * 1076 * @param grade_category $category The grade category. 1077 * @return string 1078 */ 1079 protected function get_checkbox_togglegroup(grade_category $category): string { 1080 $levels = []; 1081 $categories = explode('/', $category->path); 1082 foreach ($categories as $categoryid) { 1083 $level = 'category' . $categoryid; 1084 if (!in_array($level, $levels)) { 1085 $levels[] = 'category' . $categoryid; 1086 } 1087 } 1088 $togglegroup = implode(' ', $levels); 1089 1090 return $togglegroup; 1091 } 1092 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body