Merge branch 'MDL-36741_m23' of git://github.com/markn86/moodle into MOODLE_23_STABLE
[moodle.git] / grade / edit / tree / lib.php
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/>.
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  */
25 class grade_edit_tree {
26     public $columns = array();
28     /**
29      * @var object $gtree          @see grade/lib.php
30      */
31     public $gtree;
33     /**
34      * @var grade_plugin_return @see grade/lib.php
35      */
36     public $gpr;
38     /**
39      * @var string              $moving The eid of the category or item being moved
40      */
41     public $moving;
43     public $deepest_level;
45     public $uses_extra_credit = false;
47     public $uses_weight = false;
49     public $table;
51     public $categories = array();
52     /**
53      * Constructor
54      */
55     public function __construct($gtree, $moving=false, $gpr) {
56         global $USER, $OUTPUT, $COURSE;
58         $this->gtree = $gtree;
59         $this->moving = $moving;
60         $this->gpr = $gpr;
61         $this->deepest_level = $this->get_deepest_level($this->gtree->top_element);
63         $this->columns = array(grade_edit_tree_column::factory('name', array('deepest_level' => $this->deepest_level)),
64                                grade_edit_tree_column::factory('aggregation', array('flag' => true)));
66         if ($this->uses_weight) {
67             $this->columns[] = grade_edit_tree_column::factory('weight', array('adv' => 'aggregationcoef'));
68         }
69         if ($this->uses_extra_credit) {
70             $this->columns[] = grade_edit_tree_column::factory('extracredit', array('adv' => 'aggregationcoef'));
71         }
73         $this->columns[] = grade_edit_tree_column::factory('range'); // This is not a setting... How do we deal with it?
74         $this->columns[] = grade_edit_tree_column::factory('aggregateonlygraded', array('flag' => true));
75         $this->columns[] = grade_edit_tree_column::factory('aggregatesubcats', array('flag' => true));
76         $this->columns[] = grade_edit_tree_column::factory('aggregateoutcomes', array('flag' => true));
77         $this->columns[] = grade_edit_tree_column::factory('droplow', array('flag' => true));
78         $this->columns[] = grade_edit_tree_column::factory('keephigh', array('flag' => true));
79         $this->columns[] = grade_edit_tree_column::factory('multfactor', array('adv' => true));
80         $this->columns[] = grade_edit_tree_column::factory('plusfactor', array('adv' => true));
81         $this->columns[] = grade_edit_tree_column::factory('actions');
82         $this->columns[] = grade_edit_tree_column::factory('select');
84         $mode = ($USER->gradeediting[$COURSE->id]) ? 'advanced' : 'simple';
86         $widthstyle = '';
87         if ($mode == 'simple') {
88             $widthstyle = ' style="width:auto;" ';
89         }
91         $this->table = new html_table();
92         $this->table->id = "grade_edit_tree_table";
93         $this->table->cellpadding = 5;
94         $this->table->attributes['class'] = 'generaltable ' . $mode;
95         $this->table->style = $widthstyle;
97         foreach ($this->columns as $column) {
98             if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden($mode)) {
99                 $this->table->head[] = $column->get_header_cell();
100             }
101         }
103         $rowcount = 0;
104         $this->table->data = $this->build_html_tree($this->gtree->top_element, true, array(), 0, $rowcount);
105     }
107     /**
108      * Recursive function for building the table holding the grade categories and items,
109      * with CSS indentation and styles.
110      *
111      * @param array   $element The current tree element being rendered
112      * @param boolean $totals Whether or not to print category grade items (category totals)
113      * @param array   $parents An array of parent categories for the current element (used for indentation and row classes)
114      *
115      * @return string HTML
116      */
117     public function build_html_tree($element, $totals, $parents, $level, &$row_count) {
118         global $CFG, $COURSE, $USER, $OUTPUT;
120         $object = $element['object'];
121         $eid    = $element['eid'];
122         $object->name = $this->gtree->get_element_header($element, true, true, false);
123         $object->stripped_name = $this->gtree->get_element_header($element, false, false, false);
125         $is_category_item = false;
126         if ($element['type'] == 'categoryitem' || $element['type'] == 'courseitem') {
127             $is_category_item = true;
128         }
130         $rowclasses = array();
131         foreach ($parents as $parent_eid) {
132             $rowclasses[] = $parent_eid;
133         }
135         $actions = '';
137         if (!$is_category_item) {
138             $actions .= $this->gtree->get_edit_icon($element, $this->gpr);
139         }
141         $actions .= $this->gtree->get_calculation_icon($element, $this->gpr);
143         if ($element['type'] == 'item' or ($element['type'] == 'category' and $element['depth'] > 1)) {
144             if ($this->element_deletable($element)) {
145                 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'delete', 'eid' => $eid, 'sesskey' => sesskey()));
146                 $actions .= $OUTPUT->action_icon($aurl, new pix_icon('t/delete', get_string('delete')));
147             }
149             $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'moveselect', 'eid' => $eid, 'sesskey' => sesskey()));
150             $actions .= $OUTPUT->action_icon($aurl, new pix_icon('t/move', get_string('move')));
151         }
153         $actions .= $this->gtree->get_hiding_icon($element, $this->gpr);
154         $actions .= $this->gtree->get_locking_icon($element, $this->gpr);
156         $mode = ($USER->gradeediting[$COURSE->id]) ? 'advanced' : 'simple';
158         $returnrows = array();
159         $root = false;
161         $id = required_param('id', PARAM_INT);
163         /// prepare move target if needed
164         $last = '';
166         /// print the list items now
167         if ($this->moving == $eid) {
168             // do not diplay children
169             $cell = new html_table_cell();
170             $cell->colspan = 12;
171             $cell->attributes['class'] = $element['type'] . ' moving';
172             $cell->text = $object->name.' ('.get_string('move').')';
173             return array(new html_table_row(array($cell)));
174         }
176         if ($element['type'] == 'category') {
177             $level++;
178             $this->categories[$object->id] = $object->stripped_name;
179             $category = grade_category::fetch(array('id' => $object->id));
180             $item = $category->get_grade_item();
182             // Add aggregation coef input if not a course item and if parent category has correct aggregation type
183             $dimmed = ($item->is_hidden()) ? 'dimmed' : '';
185             // Before we print the category's row, we must find out how many rows will appear below it (for the filler cell's rowspan)
186             $aggregation_position = grade_get_setting($COURSE->id, 'aggregationposition', $CFG->grade_aggregationposition);
187             $category_total_data = null; // Used if aggregationposition is set to "last", so we can print it last
189             $html_children = array();
191             $row_count = 0;
193             foreach($element['children'] as $child_el) {
194                 $moveto = null;
196                 if (empty($child_el['object']->itemtype)) {
197                     $child_el['object']->itemtype = false;
198                 }
200                 if (($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') && !$totals) {
201                     continue;
202                 }
204                 $child_eid = $child_el['eid'];
205                 $first = '';
207                 if ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') {
208                     $first = array('first' => 1);
209                     $child_eid = $eid;
210                 }
212                 if ($this->moving && $this->moving != $child_eid) {
214                     $strmove     = get_string('move');
215                     $strmovehere = get_string('movehere');
216                     $actions = ''; // no action icons when moving
218                     $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'move', 'eid' => $this->moving, 'moveafter' => $child_eid, 'sesskey' => sesskey()));
219                     if ($first) {
220                         $aurl->params($first);
221                     }
223                     $cell = new html_table_cell();
224                     $cell->colspan = 12;
226                     $icon = new pix_icon('movehere', $strmovehere, null, array('class'=>'movetarget'));
227                     $cell->text = $OUTPUT->action_icon($aurl, $icon);
229                     $moveto = new html_table_row(array($cell));
230                 }
232                 $newparents = $parents;
233                 $newparents[] = $eid;
235                 $row_count++;
236                 $child_row_count = 0;
238                 // If moving, do not print course and category totals, but still print the moveto target box
239                 if ($this->moving && ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category')) {
240                     $html_children[] = $moveto;
241                 } elseif ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') {
242                     // We don't build the item yet because we first need to know the deepest level of categories (for category/name colspans)
243                     $category_total_item = $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count);
244                     if (!$aggregation_position) {
245                         $html_children = array_merge($html_children, $category_total_item);
246                     }
247                 } else {
248                     $html_children = array_merge($html_children, $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count));
249                     if (!empty($moveto)) {
250                         $html_children[] = $moveto;
251                     }
253                     if ($this->moving) {
254                         $row_count++;
255                     }
256                 }
258                 $row_count += $child_row_count;
260                 // If the child is a category, increment row_count by one more (for the extra coloured row)
261                 if ($child_el['type'] == 'category') {
262                     $row_count++;
263                 }
264             }
266             // Print category total at the end if aggregation position is "last" (1)
267             if (!empty($category_total_item) && $aggregation_position) {
268                 $html_children = array_merge($html_children, $category_total_item);
269             }
271             // Determine if we are at the root
272             if (isset($element['object']->grade_item) && $element['object']->grade_item->is_course_item()) {
273                 $root = true;
274             }
276             $row_count_offset = 0;
278             if (empty($category_total_item) && !$this->moving) {
279                 $row_count_offset = -1;
280             }
282             $levelclass = "level$level";
284             $courseclass = '';
285             if ($level == 1) {
286                 $courseclass = 'coursecategory';
287             }
289             $row = new html_table_row();
290             $row->attributes['class'] = $courseclass . ' category ' . $dimmed;
291             foreach ($rowclasses as $class) {
292                 $row->attributes['class'] .= ' ' . $class;
293             }
295             $headercell = new html_table_cell();
296             $headercell->header = true;
297             $headercell->scope = 'row';
298             $headercell->attributes['title'] = $object->stripped_name;
299             $headercell->attributes['class'] = 'cell rowspan ' . $levelclass;
300             $headercell->rowspan = $row_count+1+$row_count_offset;
301             $row->cells[] = $headercell;
303             foreach ($this->columns as $column) {
304                 if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden($mode)) {
305                     $row->cells[] = $column->get_category_cell($category, $levelclass, array('id' => $id, 'name' => $object->name, 'level' => $level, 'actions' => $actions, 'eid' => $eid));
306                 }
307             }
309             $returnrows[] = $row;
311             $returnrows = array_merge($returnrows, $html_children);
313             // Print a coloured row to show the end of the category across the table
314             $endcell = new html_table_cell();
315             $endcell->colspan = (19 - $level);
316             $endcell->attributes['class'] = 'colspan ' . $levelclass;
318             $returnrows[] = new html_table_row(array($endcell));;
320         } else { // Dealing with a grade item
322             $item = grade_item::fetch(array('id' => $object->id));
323             $element['type'] = 'item';
324             $element['object'] = $item;
326             $categoryitemclass = '';
327             if ($item->itemtype == 'category') {
328                 $categoryitemclass = 'categoryitem';
329             }
331             $dimmed = ($item->is_hidden()) ? "dimmed_text" : "";
332             $gradeitemrow = new html_table_row();
333             $gradeitemrow->attributes['class'] = $categoryitemclass . ' item ' . $dimmed;
334             foreach ($rowclasses as $class) {
335                 $gradeitemrow->attributes['class'] .= ' ' . $class;
336             }
338             foreach ($this->columns as $column) {
339                 if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden($mode)) {
340                     $gradeitemrow->cells[] = $column->get_item_cell($item, array('id' => $id, 'name' => $object->name, 'level' => $level, 'actions' => $actions,
341                                                                  'element' => $element, 'eid' => $eid, 'itemtype' => $object->itemtype));
342                 }
343             }
345             $returnrows[] = $gradeitemrow;
346         }
348         return $returnrows;
350     }
352     /**
353      * Given a grade_item object, returns a labelled input if an aggregation coefficient (weight or extra credit) applies to it.
354      * @param grade_item $item
355      * @param string type "extra" or "weight": the type of the column hosting the weight input
356      * @return string HTML
357      */
358     static function get_weight_input($item, $type) {
359         global $OUTPUT;
361         if (!is_object($item) || get_class($item) !== 'grade_item') {
362             throw new Exception('grade_edit_tree::get_weight_input($item) was given a variable that is not of the required type (grade_item object)');
363             return false;
364         }
366         if ($item->is_course_item()) {
367             return '';
368         }
370         $parent_category = $item->get_parent_category();
371         $parent_category->apply_forced_settings();
372         $aggcoef = $item->get_coefstring();
374         if ((($aggcoef == 'aggregationcoefweight' || $aggcoef == 'aggregationcoef') && $type == 'weight') ||
375             ($aggcoef == 'aggregationcoefextraweight' && $type == 'extra')) {
376             return '<input type="text" size="6" id="aggregationcoef_'.$item->id.'" name="aggregationcoef_'.$item->id.'"
377                 value="'.grade_edit_tree::format_number($item->aggregationcoef).'" />';
378         } elseif ($aggcoef == 'aggregationcoefextrasum' && $type == 'extra') {
379             $checked = ($item->aggregationcoef > 0) ? 'checked="checked"' : '';
380             return '<input type="hidden" name="extracredit_'.$item->id.'" value="0" />
381                     <input type="checkbox" id="extracredit_'.$item->id.'" name="extracredit_'.$item->id.'" value="1" '."$checked />\n";
382         } else {
383             return '';
384         }
385     }
387     //Trims trailing zeros
388     //Used on the 'categories and items' page for grade items settings like aggregation co-efficient
389     //Grader report has its own decimal place settings so they are handled elsewhere
390     static function format_number($number) {
391         $formatted = rtrim(format_float($number, 4),'0');
392         if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point
393             $formatted .= '0';
394         }
395         return $formatted;
396     }
398     /**
399      * Given an element of the grade tree, returns whether it is deletable or not (only manual grade items are deletable)
400      *
401      * @param array $element
402      * @return bool
403      */
404     function element_deletable($element) {
405         global $COURSE;
407         if ($element['type'] != 'item') {
408             return true;
409         }
411         $grade_item = $element['object'];
413         if ($grade_item->itemtype != 'mod' or $grade_item->is_outcome_item() or $grade_item->gradetype == GRADE_TYPE_NONE) {
414             return true;
415         }
417         $modinfo = get_fast_modinfo($COURSE);
418         if (!isset($modinfo->instances[$grade_item->itemmodule][$grade_item->iteminstance])) {
419             // module does not exist
420             return true;
421         }
423         return false;
424     }
426     /**
427      * Given the grade tree and an array of element ids (e.g. c15, i42), and expecting the 'moveafter' URL param,
428      * moves the selected items to the requested location. Then redirects the user to the given $returnurl
429      *
430      * @param object $gtree The grade tree (a recursive representation of the grade categories and grade items)
431      * @param array $eids
432      * @param string $returnurl
433      */
434     function move_elements($eids, $returnurl) {
435         $moveafter = required_param('moveafter', PARAM_INT);
437         if (!is_array($eids)) {
438             $eids = array($eids);
439         }
441         if(!$after_el = $this->gtree->locate_element("c$moveafter")) {
442             print_error('invalidelementid', '', $returnurl);
443         }
445         $after = $after_el['object'];
446         $parent = $after;
447         $sortorder = $after->get_sortorder();
449         foreach ($eids as $eid) {
450             if (!$element = $this->gtree->locate_element($eid)) {
451                 print_error('invalidelementid', '', $returnurl);
452             }
453             $object = $element['object'];
455             $object->set_parent($parent->id);
456             $object->move_after_sortorder($sortorder);
457             $sortorder++;
458         }
460         redirect($returnurl, '', 0);
461     }
463     /**
464      * Recurses through the entire grade tree to find and return the maximum depth of the tree.
465      * This should be run only once from the root element (course category), and is used for the
466      * indentation of the Name column's cells (colspan)
467      *
468      * @param array $element An array of values representing a grade tree's element (all grade items in this case)
469      * @param int $level The level of the current recursion
470      * @param int $deepest_level A value passed to each subsequent level of recursion and incremented if $level > $deepest_level
471      * @return int Deepest level
472      */
473     function get_deepest_level($element, $level=0, $deepest_level=1) {
474         $object = $element['object'];
476         $level++;
477         $coefstring = $element['object']->get_coefstring();
478         if ($element['type'] == 'category') {
479             if ($coefstring == 'aggregationcoefweight') {
480                 $this->uses_weight = true;
481             } elseif ($coefstring ==  'aggregationcoefextraweight' || $coefstring == 'aggregationcoefextrasum') {
482                 $this->uses_extra_credit = true;
483             }
485             foreach($element['children'] as $child_el) {
486                 if ($level > $deepest_level) {
487                     $deepest_level = $level;
488                 }
489                 $deepest_level = $this->get_deepest_level($child_el, $level, $deepest_level);
490             }
491         }
493         return $deepest_level;
494     }
497 abstract class grade_edit_tree_column {
498     public $forced;
499     public $hidden;
500     public $forced_hidden;
501     public $advanced_hidden;
502     public $hide_when_moving = true;
503     /**
504      * html_table_cell object used as a template for header cells in all categories.
505      * It must be cloned before being used.
506      * @var html_table_cell $headercell
507      */
508     public $headercell;
509     /**
510      * html_table_cell object used as a template for category cells in all categories.
511      * It must be cloned before being used.
512      * @var html_table_cell $categorycell
513      */
514     public $categorycell;
515     /**
516      * html_table_cell object used as a template for item cells in all categories.
517      * It must be cloned before being used.
518      * @var html_table_cell $itemcell
519      */
520     public $itemcell;
522     public static function factory($name, $params=array()) {
523         $class_name = "grade_edit_tree_column_$name";
524         if (class_exists($class_name)) {
525             return new $class_name($params);
526         }
527     }
529     public abstract function get_header_cell();
531     public abstract function get_category_cell($category, $levelclass, $params);
533     public abstract function get_item_cell($item, $params);
535     public abstract function is_hidden($mode='simple');
537     public function __construct() {
538         $this->headercell = new html_table_cell();
539         $this->headercell->header = true;
540         $this->headercell->style = 'whitespace: normal;';
541         $this->headercell->attributes['class'] = 'header';
543         $this->categorycell = new html_table_cell();
544         $this->categorycell->attributes['class']  = 'cell';
546         $this->itemcell = new html_table_cell();
547         $this->itemcell->attributes['class'] = 'cell';
548     }
551 abstract class grade_edit_tree_column_category extends grade_edit_tree_column {
553     public $forced;
554     public $advanced;
556     public function __construct($name) {
557         global $CFG;
558         $this->forced = (int)$CFG->{"grade_$name"."_flag"} & 1;
559         $this->advanced = (int)$CFG->{"grade_$name"."_flag"} & 2;
560         parent::__construct();
561     }
563     public function is_hidden($mode='simple') {
564         global $CFG;
565         if ($mode == 'simple') {
566             return $this->advanced;
567         } elseif ($mode == 'advanced') {
568             if ($this->forced && $CFG->grade_hideforcedsettings) {
569                 return true;
570             } else {
571                 return false;
572             }
573         }
574     }
577 class grade_edit_tree_column_name extends grade_edit_tree_column {
578     public $forced = false;
579     public $hidden = false;
580     public $forced_hidden = false;
581     public $advanced_hidden = false;
582     public $deepest_level = 1;
583     public $hide_when_moving = false;
585     public function __construct($params) {
586         if (empty($params['deepest_level'])) {
587             throw new Exception('Tried to instantiate a grade_edit_tree_column_name object without the "deepest_level" param!');
588         }
590         $this->deepest_level = $params['deepest_level'];
591         parent::__construct();
592     }
594     public function get_header_cell() {
595         $headercell = clone($this->headercell);
596         $headercell->attributes['class'] .= ' name';
597         $headercell->colspan = $this->deepest_level + 1;
598         $headercell->text = get_string('name');
599         return $headercell;
600     }
602     public function get_category_cell($category, $levelclass, $params) {
603         global $OUTPUT;
604         if (empty($params['name']) || empty($params['level'])) {
605             throw new Exception('Array key (name or level) missing from 3rd param of grade_edit_tree_column_name::get_category_cell($category, $levelclass, $params)');
606         }
607         $categorycell = clone($this->categorycell);
608         $categorycell->attributes['class'] .= ' name ' . $levelclass;
609         $categorycell->colspan = ($this->deepest_level +1) - $params['level'];
610         $categorycell->text = $OUTPUT->heading($params['name'], 4);
611         return $categorycell;
612     }
614     public function get_item_cell($item, $params) {
615         global $CFG;
617         if (empty($params['element']) || empty($params['name']) || empty($params['level'])) {
618             throw new Exception('Array key (name, level or element) missing from 2nd param of grade_edit_tree_column_name::get_item_cell($item, $params)');
619         }
621         $name = $params['name'];
623         $itemcell = clone($this->itemcell);
624         $itemcell->attributes['class'] .= ' name';
625         $itemcell->colspan = ($this->deepest_level + 1) - $params['level'];
626         $itemcell->text = $name;
627         return $itemcell;
628     }
630     public function is_hidden($mode='simple') {
631         return false;
632     }
635 class grade_edit_tree_column_aggregation extends grade_edit_tree_column_category {
637     public function __construct($params) {
638         parent::__construct('aggregation');
639     }
641     public function get_header_cell() {
642         global $OUTPUT;
643         $headercell = clone($this->headercell);
644         $headercell->text = get_string('aggregation', 'grades').$OUTPUT->help_icon('aggregation', 'grades');
645         return $headercell;
646     }
648     public function get_category_cell($category, $levelclass, $params) {
649         global $CFG, $OUTPUT;
650         if (empty($params['id'])) {
651             throw new Exception('Array key (id) missing from 3rd param of grade_edit_tree_column_aggregation::get_category_cell($category, $levelclass, $params)');
652         }
654         $options = array(GRADE_AGGREGATE_MEAN             => get_string('aggregatemean', 'grades'),
655                          GRADE_AGGREGATE_WEIGHTED_MEAN    => get_string('aggregateweightedmean', 'grades'),
656                          GRADE_AGGREGATE_WEIGHTED_MEAN2   => get_string('aggregateweightedmean2', 'grades'),
657                          GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'),
658                          GRADE_AGGREGATE_MEDIAN           => get_string('aggregatemedian', 'grades'),
659                          GRADE_AGGREGATE_MIN              => get_string('aggregatemin', 'grades'),
660                          GRADE_AGGREGATE_MAX              => get_string('aggregatemax', 'grades'),
661                          GRADE_AGGREGATE_MODE             => get_string('aggregatemode', 'grades'),
662                          GRADE_AGGREGATE_SUM              => get_string('aggregatesum', 'grades'));
664         $visible = explode(',', $CFG->grade_aggregations_visible);
665         foreach ($options as $constant => $string) {
666             if (!in_array($constant, $visible) && $constant != $category->aggregation) {
667                 unset($options[$constant]);
668             }
669         }
671         if ($this->forced) {
672             $aggregation = $options[$category->aggregation];
673         } else {
674             $attributes = array();
675             $attributes['id'] = 'aggregation_'.$category->id;
676             $aggregation = html_writer::label(get_string('aggregation', 'grades'), 'aggregation_'.$category->id, false, array('class' => 'accesshide'));
677             $aggregation .= html_writer::select($options, 'aggregation_'.$category->id, $category->aggregation, null, $attributes);
678             $action = new component_action('change', 'update_category_aggregation', array('courseid' => $params['id'], 'category' => $category->id, 'sesskey' => sesskey()));
679             $OUTPUT->add_action_handler($action, 'aggregation_'.$category->id);
680         }
682         $categorycell = clone($this->categorycell);
683         $categorycell->attributes['class'] .= ' ' . $levelclass;
684         $categorycell->text = $aggregation;
685         return $categorycell;
687     }
689     public function get_item_cell($item, $params) {
690         $itemcell = clone($this->itemcell);
691         $itemcell->text = ' - ';
692         return $itemcell;
693     }
696 class grade_edit_tree_column_extracredit extends grade_edit_tree_column {
698     public function get_header_cell() {
699         global $OUTPUT;
700         $headercell = clone($this->headercell);
701         $headercell->text = get_string('aggregationcoefextra', 'grades').$OUTPUT->help_icon('aggregationcoefextra', 'grades');
702         return $headercell;
703     }
705     public function get_category_cell($category, $levelclass, $params) {
706         $item = $category->get_grade_item();
707         $categorycell = clone($this->categorycell);
708         $categorycell->attributes['class'] .= ' ' . $levelclass;
709         $categorycell->text = grade_edit_tree::get_weight_input($item, 'extra');
710         return $categorycell;
711     }
713     public function get_item_cell($item, $params) {
714         if (empty($params['element'])) {
715             throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)');
716         }
718         $itemcell = clone($this->itemcell);
719         $itemcell->text = '&nbsp;';
721         if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))) {
722             $itemcell->text = grade_edit_tree::get_weight_input($item, 'extra');
723         }
725         return $itemcell;
726     }
728     public function is_hidden($mode='simple') {
729         global $CFG;
730         if ($mode == 'simple') {
731             return strstr($CFG->grade_item_advanced, 'aggregationcoef');
732         } elseif ($mode == 'advanced') {
733             return false;
734         }
735     }
738 class grade_edit_tree_column_weight extends grade_edit_tree_column {
740     public function get_header_cell() {
741         global $OUTPUT;
742         $headercell = clone($this->headercell);
743         $headercell->text = get_string('weightuc', 'grades').$OUTPUT->help_icon('aggregationcoefweight', 'grades');
744         return $headercell;
745     }
747     public function get_category_cell($category, $levelclass, $params) {
749         $item = $category->get_grade_item();
750         $categorycell = clone($this->categorycell);
751         $categorycell->attributes['class']  .= ' ' . $levelclass;
752         $categorycell->text = grade_edit_tree::get_weight_input($item, 'weight');
753         return $categorycell;
754     }
756     public function get_item_cell($item, $params) {
757         if (empty($params['element'])) {
758             throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)');
759         }
760         $itemcell = clone($this->itemcell);
761         $itemcell->text = '&nbsp;';
763         if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))) {
764             $itemcell->text = grade_edit_tree::get_weight_input($item, 'weight');
765         }
767         return $itemcell;
768     }
770     public function is_hidden($mode='simple') {
771         global $CFG;
772         if ($mode == 'simple') {
773             return strstr($CFG->grade_item_advanced, 'aggregationcoef');
774         } elseif ($mode == 'advanced') {
775             return false;
776         }
777     }
780 class grade_edit_tree_column_range extends grade_edit_tree_column {
782     public function get_header_cell() {
783         $headercell = clone($this->headercell);
784         $headercell->text = get_string('maxgrade', 'grades');
785         return $headercell;
786     }
788     public function get_category_cell($category, $levelclass, $params) {
789         $categorycell = clone($this->categorycell);
790         $categorycell->attributes['class'] .= ' range ' . $levelclass;
791         $categorycell->text = ' - ';
792         return $categorycell;
793     }
795     public function get_item_cell($item, $params) {
796         global $DB, $OUTPUT;
798         // If the parent aggregation is Sum of Grades, this cannot be changed
799         $parent_cat = $item->get_parent_category();
800         if ($parent_cat->aggregation == GRADE_AGGREGATE_SUM) {
801             $grademax = format_float($item->grademax, $item->get_decimals());
802         } elseif ($item->gradetype == GRADE_TYPE_SCALE) {
803             $scale = $DB->get_record('scale', array('id' => $item->scaleid));
804             $scale_items = null;
805             if (empty($scale)) { //if the item is using a scale that's been removed
806                 $scale_items = array();
807             } else {
808                 $scale_items = explode(',', $scale->scale);
809             }
810             $grademax = end($scale_items) . ' (' . count($scale_items) . ')';
811         } elseif ($item->is_external_item()) {
812             $grademax = format_float($item->grademax, $item->get_decimals());
813         } else {
814             $grademax = '<input type="text" size="6" id="grademax'.$item->id.'" name="grademax_'.$item->id.'" value="'.format_float($item->grademax, $item->get_decimals()).'" />';
815         }
817         $itemcell = clone($this->itemcell);
818         $itemcell->text = $grademax;
819         return $itemcell;
820     }
822     public function is_hidden($mode='simple') {
823         global $CFG;
824         if ($mode == 'simple') {
825             return strstr($CFG->grade_item_advanced, 'grademax');
826         } elseif ($mode == 'advanced') {
827             return false;
828         }
829     }
832 class grade_edit_tree_column_aggregateonlygraded extends grade_edit_tree_column_category {
834     public function __construct($params) {
835         parent::__construct('aggregateonlygraded');
836     }
838     public function get_header_cell() {
839         global $OUTPUT;
840         $headercell = clone($this->headercell);
841         $headercell->style .= 'width: 40px;';
842         $headercell->text = get_string('aggregateonlygraded', 'grades')
843                 . $OUTPUT->help_icon('aggregateonlygraded', 'grades');
844         return $headercell;
845     }
847     public function get_category_cell($category, $levelclass, $params) {
848         $onlygradedcheck = ($category->aggregateonlygraded == 1) ? 'checked="checked"' : '';
849         $hidden = '<input type="hidden" name="aggregateonlygraded_'.$category->id.'" value="0" />';
850         $aggregateonlygraded ='<input type="checkbox" id="aggregateonlygraded_'.$category->id.'" name="aggregateonlygraded_'.$category->id.'" value="1" '.$onlygradedcheck . ' />';
852         if ($this->forced) {
853             $aggregateonlygraded = ($category->aggregateonlygraded) ? get_string('yes') : get_string('no');
854         }
856         $categorycell = clone($this->categorycell);
857         $categorycell->attributes['class'] .= ' ' . $levelclass;
858         $categorycell->text = $hidden.$aggregateonlygraded;
859         return $categorycell;
860     }
862     public function get_item_cell($item, $params) {
863         $itemcell = clone($this->itemcell);
864         $itemcell->text = ' - ';
865         return $itemcell;
866     }
869 class grade_edit_tree_column_aggregatesubcats extends grade_edit_tree_column_category {
871     public function __construct($params) {
872         parent::__construct('aggregatesubcats');
873     }
875     public function get_header_cell() {
876         global $OUTPUT;
877         $headercell = clone($this->headercell);
878         $headercell->style .= 'width: 40px;';
879         $headercell->text = get_string('aggregatesubcats', 'grades')
880               .$OUTPUT->help_icon('aggregatesubcats', 'grades');
881         return $headercell;
882     }
884     public function get_category_cell($category, $levelclass, $params) {
885         $subcatscheck = ($category->aggregatesubcats == 1) ? 'checked="checked"' : '';
886         $hidden = '<input type="hidden" name="aggregatesubcats_'.$category->id.'" value="0" />';
887         $aggregatesubcats = '<input type="checkbox" id="aggregatesubcats_'.$category->id.'" name="aggregatesubcats_'.$category->id.'" value="1" ' . $subcatscheck.' />';
889         if ($this->forced) {
890             $aggregatesubcats = ($category->aggregatesubcats) ? get_string('yes') : get_string('no');
891         }
893         $categorycell = clone($this->categorycell);
894         $categorycell->attributes['class'] .= ' ' . $levelclass;
895         $categorycell->text = $hidden.$aggregatesubcats;
896         return $categorycell;
898     }
900     public function get_item_cell($item, $params) {
901         $itemcell = clone($this->itemcell);
902         $itemcell->text = ' - ';
903         return $itemcell;
904     }
907 class grade_edit_tree_column_aggregateoutcomes extends grade_edit_tree_column_category {
909     public function __construct($params) {
910         parent::__construct('aggregateoutcomes');
911     }
913     public function get_header_cell() {
914         global $OUTPUT;
915         $headercell = clone($this->headercell);
916         $headercell->style .= 'width: 40px;';
917         $headercell->text = get_string('aggregateoutcomes', 'grades')
918               .$OUTPUT->help_icon('aggregateoutcomes', 'grades');
919         return $headercell;
920     }
922     public function get_category_cell($category, $levelclass, $params) {
923         $outcomescheck = ($category->aggregateoutcomes == 1) ? 'checked="checked"' : '';
924         $hidden = '<input type="hidden" name="aggregateoutcomes_'.$category->id.'" value="0" />';
925         $aggregateoutcomes = '<input type="checkbox" id="aggregateoutcomes_'.$category->id.'" name="aggregateoutcomes_'.$category->id.'" value="1" ' . $outcomescheck.' />';
927         if ($this->forced) {
928             $aggregateoutcomes = ($category->aggregateoutcomes) ? get_string('yes') : get_string('no');
929         }
931         $categorycell = clone($this->categorycell);
932         $categorycell->attributes['class'] .= ' ' . $levelclass;
933         $categorycell->text = $hidden.$aggregateoutcomes;
934         return $categorycell;
935     }
937     public function get_item_cell($item, $params) {
938         $itemcell = clone($this->itemcell);
939         $itemcell->text = ' - ';
940         return $itemcell;
941     }
943     public function is_hidden($mode='simple') {
944         global $CFG;
945         if ($CFG->enableoutcomes) {
946             return parent::is_hidden($mode);
947         } else {
948             return true;
949         }
950     }
953 class grade_edit_tree_column_droplow extends grade_edit_tree_column_category {
955     public function __construct($params) {
956         parent::__construct('droplow');
957     }
959     public function get_header_cell() {
960         global $OUTPUT;
961         $headercell = clone($this->headercell);
962         $headercell->text = get_string('droplow', 'grades').$OUTPUT->help_icon('droplow', 'grades');
963         return $headercell;
964     }
966     public function get_category_cell($category, $levelclass, $params) {
967         $droplow = '<input type="text" size="3" id="droplow_'.$category->id.'" name="droplow_'.$category->id.'" value="'.$category->droplow.'" />';
969         if ($this->forced) {
970             $droplow = $category->droplow;
971         }
973         $categorycell = clone($this->categorycell);
974         $categorycell->attributes['class']  .= ' ' . $levelclass;
975         $categorycell->text = $droplow;
976         return $categorycell;
977     }
979     public function get_item_cell($item, $params) {
980         $itemcell = clone($this->itemcell);
981         $itemcell->text = ' - ';
982         return $itemcell;
983     }
986 class grade_edit_tree_column_keephigh extends grade_edit_tree_column_category {
988     public function __construct($params) {
989         parent::__construct('keephigh');
990     }
992     public function get_header_cell() {
993         global $OUTPUT;
994         $headercell = clone($this->headercell);
995         $headercell->text = get_string('keephigh', 'grades').$OUTPUT->help_icon('keephigh', 'grades');
996         return $headercell;
997     }
999     public function get_category_cell($category, $levelclass, $params) {
1000         $keephigh = '<input type="text" size="3" id="keephigh_'.$category->id.'" name="keephigh_'.$category->id.'" value="'.$category->keephigh.'" />';
1002         if ($this->forced) {
1003             $keephigh = $category->keephigh;
1004         }
1006         $categorycell = clone($this->categorycell);
1007         $categorycell->attributes['class'] .= ' ' . $levelclass;
1008         $categorycell->text = $keephigh;
1009         return $categorycell;
1010     }
1012     public function get_item_cell($item, $params) {
1013         $itemcell = clone($this->itemcell);
1014         $itemcell->text = ' - ';
1015         return $itemcell;
1016     }
1019 class grade_edit_tree_column_multfactor extends grade_edit_tree_column {
1021     public function __construct($params) {
1022         parent::__construct();
1023     }
1025     public function get_header_cell() {
1026         global $OUTPUT;
1027         $headercell = clone($this->headercell);
1028         $headercell->text = get_string('multfactor', 'grades').$OUTPUT->help_icon('multfactor', 'grades');
1029         return $headercell;
1030     }
1032     public function get_category_cell($category, $levelclass, $params) {
1033         $categorycell = clone($this->categorycell);
1034         $categorycell->attributes['class'] .= ' ' . $levelclass;
1035         $categorycell->text = ' - ';
1036         return $categorycell;
1037     }
1039     public function get_item_cell($item, $params) {
1040         global $OUTPUT;
1042         $itemcell = clone($this->itemcell);
1043         if (!$item->is_raw_used()) {
1044             $itemcell->text = '&nbsp;';
1045             return $itemcell;
1046         }
1048         $multfactor = '<input type="text" size="4" id="multfactor'.$item->id.'" name="multfactor_'.$item->id.'" value="'.grade_edit_tree::format_number($item->multfactor).'" />';
1050         $itemcell->text = $multfactor;
1051         return $itemcell;
1052     }
1054     public function is_hidden($mode='simple') {
1055         global $CFG;
1056         if ($mode == 'simple') {
1057             return strstr($CFG->grade_item_advanced, 'multfactor');
1058         } elseif ($mode == 'advanced') {
1059             return false;
1060         }
1061     }
1064 class grade_edit_tree_column_plusfactor extends grade_edit_tree_column {
1066     public function get_header_cell() {
1067         global $OUTPUT;
1068         $headercell = clone($this->headercell);
1069         $headercell->text = get_string('plusfactor', 'grades').$OUTPUT->help_icon('plusfactor', 'grades');
1070         return $headercell;
1071     }
1073     public function get_category_cell($category, $levelclass, $params) {
1074         $categorycell = clone($this->categorycell);
1075         $categorycell->attributes['class'] .= ' ' . $levelclass;
1076         $categorycell->text = ' - ';
1077         return $categorycell;
1079     }
1081     public function get_item_cell($item, $params) {
1082         global $OUTPUT;
1084         $itemcell = clone($this->itemcell);
1085         if (!$item->is_raw_used()) {
1086             $itemcell->text = '&nbsp;';
1087             return $itemcell;
1088         }
1090         $plusfactor = '<input type="text" size="4" id="plusfactor_'.$item->id.'" name="plusfactor_'.$item->id.'" value="'.grade_edit_tree::format_number($item->plusfactor).'" />';
1092         $itemcell->text = $plusfactor;
1093         return $itemcell;
1095     }
1097     public function is_hidden($mode='simple') {
1098         global $CFG;
1099         if ($mode == 'simple') {
1100             return strstr($CFG->grade_item_advanced, 'plusfactor');
1101         } elseif ($mode == 'advanced') {
1102             return false;
1103         }
1104     }
1107 class grade_edit_tree_column_actions extends grade_edit_tree_column {
1109     public function __construct($params) {
1110         parent::__construct();
1111     }
1113     public function get_header_cell() {
1114         $headercell = clone($this->headercell);
1115         $headercell->attributes['class'] .= ' actions';
1116         $headercell->text = get_string('actions');
1117         return $headercell;
1118     }
1120     public function get_category_cell($category, $levelclass, $params) {
1122         if (empty($params['actions'])) {
1123             throw new Exception('Array key (actions) missing from 3rd param of grade_edit_tree_column_actions::get_category_actions($category, $levelclass, $params)');
1124         }
1126         $categorycell = clone($this->categorycell);
1127         $categorycell->attributes['class'] .= ' ' . $levelclass;
1128         $categorycell->text = $params['actions'];
1129         return $categorycell;
1130     }
1132     public function get_item_cell($item, $params) {
1133         if (empty($params['actions'])) {
1134             throw new Exception('Array key (actions) missing from 2nd param of grade_edit_tree_column_actions::get_item_cell($item, $params)');
1135         }
1136         $itemcell = clone($this->itemcell);
1137         $itemcell->attributes['class'] .= ' actions';
1138         $itemcell->text = $params['actions'];
1139         return $itemcell;
1140     }
1142     public function is_hidden($mode='simple') {
1143         return false;
1144     }
1147 class grade_edit_tree_column_select extends grade_edit_tree_column {
1149     public function get_header_cell() {
1150         $headercell = clone($this->headercell);
1151         $headercell->attributes['class'] .= ' selection';
1152         $headercell->text = get_string('select');
1153         return $headercell;
1154     }
1156     public function get_category_cell($category, $levelclass, $params) {
1157         global $OUTPUT;
1158         if (empty($params['eid'])) {
1159             throw new Exception('Array key (eid) missing from 3rd param of grade_edit_tree_column_select::get_category_cell($category, $levelclass, $params)');
1160         }
1161         $selectall  = new action_link(new moodle_url('#'), get_string('all'), new component_action('click', 'togglecheckboxes', array('eid' => $params['eid'], 'check' => true)));
1162         $selectnone = new action_link(new moodle_url('#'), get_string('none'), new component_action('click', 'togglecheckboxes', array('eid' => $params['eid'], 'check' => false)));
1164         $categorycell = clone($this->categorycell);
1165         $categorycell->attributes['class'] .= ' last ' . $levelclass;
1166         $categorycell->style .= 'text-align: center;';
1167         $categorycell->text = $OUTPUT->render($selectall) . '<br />' . $OUTPUT->render($selectnone);
1168         return $categorycell;
1169     }
1171     public function get_item_cell($item, $params) {
1172         if (empty($params['itemtype']) || empty($params['eid'])) {
1173             error('Array key (itemtype or eid) missing from 2nd param of grade_edit_tree_column_select::get_item_cell($item, $params)');
1174         }
1175         $itemselect = '';
1177         if ($params['itemtype'] != 'course' && $params['itemtype'] != 'category') {
1178             $itemselect = '<input class="itemselect" type="checkbox" name="select_'.$params['eid'].'" onchange="toggleCategorySelector();"/>'; // TODO: convert to YUI handler
1179         }
1180         //html_writer::table() will wrap the item cell contents in a <TD> so don't do it here
1181         return $itemselect;
1182     }
1184     public function is_hidden($mode='simple') {
1185         return false;
1186     }