Merge branch 'MDL-29327_none_23' of git://github.com/andyjdavis/moodle into MOODLE_23...
[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             $levelclass = "level$level";
278             $courseclass = '';
279             if ($level == 1) {
280                 $courseclass = 'coursecategory';
281             }
283             $row = new html_table_row();
284             $row->attributes['class'] = $courseclass . ' category ' . $dimmed;
285             foreach ($rowclasses as $class) {
286                 $row->attributes['class'] .= ' ' . $class;
287             }
289             $headercell = new html_table_cell();
290             $headercell->header = true;
291             $headercell->scope = 'row';
292             $headercell->attributes['title'] = $object->stripped_name;
293             $headercell->attributes['class'] = 'cell rowspan ' . $levelclass;
294             $headercell->rowspan = $row_count + 1;
295             $row->cells[] = $headercell;
297             foreach ($this->columns as $column) {
298                 if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden($mode)) {
299                     $row->cells[] = $column->get_category_cell($category, $levelclass, array('id' => $id, 'name' => $object->name, 'level' => $level, 'actions' => $actions, 'eid' => $eid));
300                 }
301             }
303             $returnrows[] = $row;
305             $returnrows = array_merge($returnrows, $html_children);
307             // Print a coloured row to show the end of the category across the table
308             $endcell = new html_table_cell();
309             $endcell->colspan = (19 - $level);
310             $endcell->attributes['class'] = 'colspan ' . $levelclass;
312             $returnrows[] = new html_table_row(array($endcell));;
314         } else { // Dealing with a grade item
316             $item = grade_item::fetch(array('id' => $object->id));
317             $element['type'] = 'item';
318             $element['object'] = $item;
320             $categoryitemclass = '';
321             if ($item->itemtype == 'category') {
322                 $categoryitemclass = 'categoryitem';
323             }
325             $dimmed = ($item->is_hidden()) ? "dimmed_text" : "";
326             $gradeitemrow = new html_table_row();
327             $gradeitemrow->attributes['class'] = $categoryitemclass . ' item ' . $dimmed;
328             foreach ($rowclasses as $class) {
329                 $gradeitemrow->attributes['class'] .= ' ' . $class;
330             }
332             foreach ($this->columns as $column) {
333                 if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden($mode)) {
334                     $gradeitemrow->cells[] = $column->get_item_cell($item, array('id' => $id, 'name' => $object->name, 'level' => $level, 'actions' => $actions,
335                                                                  'element' => $element, 'eid' => $eid, 'itemtype' => $object->itemtype));
336                 }
337             }
339             $returnrows[] = $gradeitemrow;
340         }
342         return $returnrows;
344     }
346     /**
347      * Given a grade_item object, returns a labelled input if an aggregation coefficient (weight or extra credit) applies to it.
348      * @param grade_item $item
349      * @param string type "extra" or "weight": the type of the column hosting the weight input
350      * @return string HTML
351      */
352     static function get_weight_input($item, $type) {
353         global $OUTPUT;
355         if (!is_object($item) || get_class($item) !== 'grade_item') {
356             throw new Exception('grade_edit_tree::get_weight_input($item) was given a variable that is not of the required type (grade_item object)');
357             return false;
358         }
360         if ($item->is_course_item()) {
361             return '';
362         }
364         $parent_category = $item->get_parent_category();
365         $parent_category->apply_forced_settings();
366         $aggcoef = $item->get_coefstring();
368         if ((($aggcoef == 'aggregationcoefweight' || $aggcoef == 'aggregationcoef') && $type == 'weight') ||
369             ($aggcoef == 'aggregationcoefextraweight' && $type == 'extra')) {
370             return '<input type="text" size="6" id="aggregationcoef_'.$item->id.'" name="aggregationcoef_'.$item->id.'"
371                 value="'.grade_edit_tree::format_number($item->aggregationcoef).'" />';
372         } elseif ($aggcoef == 'aggregationcoefextrasum' && $type == 'extra') {
373             $checked = ($item->aggregationcoef > 0) ? 'checked="checked"' : '';
374             return '<input type="hidden" name="extracredit_'.$item->id.'" value="0" />
375                     <input type="checkbox" id="extracredit_'.$item->id.'" name="extracredit_'.$item->id.'" value="1" '."$checked />\n";
376         } else {
377             return '';
378         }
379     }
381     //Trims trailing zeros
382     //Used on the 'categories and items' page for grade items settings like aggregation co-efficient
383     //Grader report has its own decimal place settings so they are handled elsewhere
384     static function format_number($number) {
385         $formatted = rtrim(format_float($number, 4),'0');
386         if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point
387             $formatted .= '0';
388         }
389         return $formatted;
390     }
392     /**
393      * Given an element of the grade tree, returns whether it is deletable or not (only manual grade items are deletable)
394      *
395      * @param array $element
396      * @return bool
397      */
398     function element_deletable($element) {
399         global $COURSE;
401         if ($element['type'] != 'item') {
402             return true;
403         }
405         $grade_item = $element['object'];
407         if ($grade_item->itemtype != 'mod' or $grade_item->is_outcome_item() or $grade_item->gradetype == GRADE_TYPE_NONE) {
408             return true;
409         }
411         $modinfo = get_fast_modinfo($COURSE);
412         if (!isset($modinfo->instances[$grade_item->itemmodule][$grade_item->iteminstance])) {
413             // module does not exist
414             return true;
415         }
417         return false;
418     }
420     /**
421      * Given the grade tree and an array of element ids (e.g. c15, i42), and expecting the 'moveafter' URL param,
422      * moves the selected items to the requested location. Then redirects the user to the given $returnurl
423      *
424      * @param object $gtree The grade tree (a recursive representation of the grade categories and grade items)
425      * @param array $eids
426      * @param string $returnurl
427      */
428     function move_elements($eids, $returnurl) {
429         $moveafter = required_param('moveafter', PARAM_INT);
431         if (!is_array($eids)) {
432             $eids = array($eids);
433         }
435         if(!$after_el = $this->gtree->locate_element("c$moveafter")) {
436             print_error('invalidelementid', '', $returnurl);
437         }
439         $after = $after_el['object'];
440         $parent = $after;
441         $sortorder = $after->get_sortorder();
443         foreach ($eids as $eid) {
444             if (!$element = $this->gtree->locate_element($eid)) {
445                 print_error('invalidelementid', '', $returnurl);
446             }
447             $object = $element['object'];
449             $object->set_parent($parent->id);
450             $object->move_after_sortorder($sortorder);
451             $sortorder++;
452         }
454         redirect($returnurl, '', 0);
455     }
457     /**
458      * Recurses through the entire grade tree to find and return the maximum depth of the tree.
459      * This should be run only once from the root element (course category), and is used for the
460      * indentation of the Name column's cells (colspan)
461      *
462      * @param array $element An array of values representing a grade tree's element (all grade items in this case)
463      * @param int $level The level of the current recursion
464      * @param int $deepest_level A value passed to each subsequent level of recursion and incremented if $level > $deepest_level
465      * @return int Deepest level
466      */
467     function get_deepest_level($element, $level=0, $deepest_level=1) {
468         $object = $element['object'];
470         $level++;
471         $coefstring = $element['object']->get_coefstring();
472         if ($element['type'] == 'category') {
473             if ($coefstring == 'aggregationcoefweight') {
474                 $this->uses_weight = true;
475             } elseif ($coefstring ==  'aggregationcoefextraweight' || $coefstring == 'aggregationcoefextrasum') {
476                 $this->uses_extra_credit = true;
477             }
479             foreach($element['children'] as $child_el) {
480                 if ($level > $deepest_level) {
481                     $deepest_level = $level;
482                 }
483                 $deepest_level = $this->get_deepest_level($child_el, $level, $deepest_level);
484             }
485         }
487         return $deepest_level;
488     }
491 abstract class grade_edit_tree_column {
492     public $forced;
493     public $hidden;
494     public $forced_hidden;
495     public $advanced_hidden;
496     public $hide_when_moving = true;
497     /**
498      * html_table_cell object used as a template for header cells in all categories.
499      * It must be cloned before being used.
500      * @var html_table_cell $headercell
501      */
502     public $headercell;
503     /**
504      * html_table_cell object used as a template for category cells in all categories.
505      * It must be cloned before being used.
506      * @var html_table_cell $categorycell
507      */
508     public $categorycell;
509     /**
510      * html_table_cell object used as a template for item cells in all categories.
511      * It must be cloned before being used.
512      * @var html_table_cell $itemcell
513      */
514     public $itemcell;
516     public static function factory($name, $params=array()) {
517         $class_name = "grade_edit_tree_column_$name";
518         if (class_exists($class_name)) {
519             return new $class_name($params);
520         }
521     }
523     public abstract function get_header_cell();
525     public abstract function get_category_cell($category, $levelclass, $params);
527     public abstract function get_item_cell($item, $params);
529     public abstract function is_hidden($mode='simple');
531     public function __construct() {
532         $this->headercell = new html_table_cell();
533         $this->headercell->header = true;
534         $this->headercell->style = 'whitespace: normal;';
535         $this->headercell->attributes['class'] = 'header';
537         $this->categorycell = new html_table_cell();
538         $this->categorycell->attributes['class']  = 'cell';
540         $this->itemcell = new html_table_cell();
541         $this->itemcell->attributes['class'] = 'cell';
542     }
545 abstract class grade_edit_tree_column_category extends grade_edit_tree_column {
547     public $forced;
548     public $advanced;
550     public function __construct($name) {
551         global $CFG;
552         $this->forced = (int)$CFG->{"grade_$name"."_flag"} & 1;
553         $this->advanced = (int)$CFG->{"grade_$name"."_flag"} & 2;
554         parent::__construct();
555     }
557     public function is_hidden($mode='simple') {
558         global $CFG;
559         if ($mode == 'simple') {
560             return $this->advanced;
561         } elseif ($mode == 'advanced') {
562             if ($this->forced && $CFG->grade_hideforcedsettings) {
563                 return true;
564             } else {
565                 return false;
566             }
567         }
568     }
571 class grade_edit_tree_column_name extends grade_edit_tree_column {
572     public $forced = false;
573     public $hidden = false;
574     public $forced_hidden = false;
575     public $advanced_hidden = false;
576     public $deepest_level = 1;
577     public $hide_when_moving = false;
579     public function __construct($params) {
580         if (empty($params['deepest_level'])) {
581             throw new Exception('Tried to instantiate a grade_edit_tree_column_name object without the "deepest_level" param!');
582         }
584         $this->deepest_level = $params['deepest_level'];
585         parent::__construct();
586     }
588     public function get_header_cell() {
589         $headercell = clone($this->headercell);
590         $headercell->attributes['class'] .= ' name';
591         $headercell->colspan = $this->deepest_level + 1;
592         $headercell->text = get_string('name');
593         return $headercell;
594     }
596     public function get_category_cell($category, $levelclass, $params) {
597         global $OUTPUT;
598         if (empty($params['name']) || empty($params['level'])) {
599             throw new Exception('Array key (name or level) missing from 3rd param of grade_edit_tree_column_name::get_category_cell($category, $levelclass, $params)');
600         }
601         $categorycell = clone($this->categorycell);
602         $categorycell->attributes['class'] .= ' name ' . $levelclass;
603         $categorycell->colspan = ($this->deepest_level +1) - $params['level'];
604         $categorycell->text = $OUTPUT->heading($params['name'], 4);
605         return $categorycell;
606     }
608     public function get_item_cell($item, $params) {
609         global $CFG;
611         if (empty($params['element']) || empty($params['name']) || empty($params['level'])) {
612             throw new Exception('Array key (name, level or element) missing from 2nd param of grade_edit_tree_column_name::get_item_cell($item, $params)');
613         }
615         $name = $params['name'];
617         $itemcell = clone($this->itemcell);
618         $itemcell->attributes['class'] .= ' name';
619         $itemcell->colspan = ($this->deepest_level + 1) - $params['level'];
620         $itemcell->text = $name;
621         return $itemcell;
622     }
624     public function is_hidden($mode='simple') {
625         return false;
626     }
629 class grade_edit_tree_column_aggregation extends grade_edit_tree_column_category {
631     public function __construct($params) {
632         parent::__construct('aggregation');
633     }
635     public function get_header_cell() {
636         global $OUTPUT;
637         $headercell = clone($this->headercell);
638         $headercell->text = get_string('aggregation', 'grades').$OUTPUT->help_icon('aggregation', 'grades');
639         return $headercell;
640     }
642     public function get_category_cell($category, $levelclass, $params) {
643         global $CFG, $OUTPUT;
644         if (empty($params['id'])) {
645             throw new Exception('Array key (id) missing from 3rd param of grade_edit_tree_column_aggregation::get_category_cell($category, $levelclass, $params)');
646         }
648         $options = array(GRADE_AGGREGATE_MEAN             => get_string('aggregatemean', 'grades'),
649                          GRADE_AGGREGATE_WEIGHTED_MEAN    => get_string('aggregateweightedmean', 'grades'),
650                          GRADE_AGGREGATE_WEIGHTED_MEAN2   => get_string('aggregateweightedmean2', 'grades'),
651                          GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'),
652                          GRADE_AGGREGATE_MEDIAN           => get_string('aggregatemedian', 'grades'),
653                          GRADE_AGGREGATE_MIN              => get_string('aggregatemin', 'grades'),
654                          GRADE_AGGREGATE_MAX              => get_string('aggregatemax', 'grades'),
655                          GRADE_AGGREGATE_MODE             => get_string('aggregatemode', 'grades'),
656                          GRADE_AGGREGATE_SUM              => get_string('aggregatesum', 'grades'));
658         $visible = explode(',', $CFG->grade_aggregations_visible);
659         foreach ($options as $constant => $string) {
660             if (!in_array($constant, $visible) && $constant != $category->aggregation) {
661                 unset($options[$constant]);
662             }
663         }
665         if ($this->forced) {
666             $aggregation = $options[$category->aggregation];
667         } else {
668             $attributes = array();
669             $attributes['id'] = 'aggregation_'.$category->id;
670             $aggregation = html_writer::label(get_string('aggregation', 'grades'), 'aggregation_'.$category->id, false, array('class' => 'accesshide'));
671             $aggregation .= html_writer::select($options, 'aggregation_'.$category->id, $category->aggregation, null, $attributes);
672             $action = new component_action('change', 'update_category_aggregation', array('courseid' => $params['id'], 'category' => $category->id, 'sesskey' => sesskey()));
673             $OUTPUT->add_action_handler($action, 'aggregation_'.$category->id);
674         }
676         $categorycell = clone($this->categorycell);
677         $categorycell->attributes['class'] .= ' ' . $levelclass;
678         $categorycell->text = $aggregation;
679         return $categorycell;
681     }
683     public function get_item_cell($item, $params) {
684         $itemcell = clone($this->itemcell);
685         $itemcell->text = ' - ';
686         return $itemcell;
687     }
690 class grade_edit_tree_column_extracredit extends grade_edit_tree_column {
692     public function get_header_cell() {
693         global $OUTPUT;
694         $headercell = clone($this->headercell);
695         $headercell->text = get_string('aggregationcoefextra', 'grades').$OUTPUT->help_icon('aggregationcoefextra', 'grades');
696         return $headercell;
697     }
699     public function get_category_cell($category, $levelclass, $params) {
700         $item = $category->get_grade_item();
701         $categorycell = clone($this->categorycell);
702         $categorycell->attributes['class'] .= ' ' . $levelclass;
703         $categorycell->text = grade_edit_tree::get_weight_input($item, 'extra');
704         return $categorycell;
705     }
707     public function get_item_cell($item, $params) {
708         if (empty($params['element'])) {
709             throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)');
710         }
712         $itemcell = clone($this->itemcell);
713         $itemcell->text = '&nbsp;';
715         if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))) {
716             $itemcell->text = grade_edit_tree::get_weight_input($item, 'extra');
717         }
719         return $itemcell;
720     }
722     public function is_hidden($mode='simple') {
723         global $CFG;
724         if ($mode == 'simple') {
725             return strstr($CFG->grade_item_advanced, 'aggregationcoef');
726         } elseif ($mode == 'advanced') {
727             return false;
728         }
729     }
732 class grade_edit_tree_column_weight extends grade_edit_tree_column {
734     public function get_header_cell() {
735         global $OUTPUT;
736         $headercell = clone($this->headercell);
737         $headercell->text = get_string('weightuc', 'grades').$OUTPUT->help_icon('aggregationcoefweight', 'grades');
738         return $headercell;
739     }
741     public function get_category_cell($category, $levelclass, $params) {
743         $item = $category->get_grade_item();
744         $categorycell = clone($this->categorycell);
745         $categorycell->attributes['class']  .= ' ' . $levelclass;
746         $categorycell->text = grade_edit_tree::get_weight_input($item, 'weight');
747         return $categorycell;
748     }
750     public function get_item_cell($item, $params) {
751         if (empty($params['element'])) {
752             throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)');
753         }
754         $itemcell = clone($this->itemcell);
755         $itemcell->text = '&nbsp;';
757         if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))) {
758             $itemcell->text = grade_edit_tree::get_weight_input($item, 'weight');
759         }
761         return $itemcell;
762     }
764     public function is_hidden($mode='simple') {
765         global $CFG;
766         if ($mode == 'simple') {
767             return strstr($CFG->grade_item_advanced, 'aggregationcoef');
768         } elseif ($mode == 'advanced') {
769             return false;
770         }
771     }
774 class grade_edit_tree_column_range extends grade_edit_tree_column {
776     public function get_header_cell() {
777         $headercell = clone($this->headercell);
778         $headercell->text = get_string('maxgrade', 'grades');
779         return $headercell;
780     }
782     public function get_category_cell($category, $levelclass, $params) {
783         $categorycell = clone($this->categorycell);
784         $categorycell->attributes['class'] .= ' range ' . $levelclass;
785         $categorycell->text = ' - ';
786         return $categorycell;
787     }
789     public function get_item_cell($item, $params) {
790         global $DB, $OUTPUT;
792         // If the parent aggregation is Sum of Grades, this cannot be changed
793         $parent_cat = $item->get_parent_category();
794         if ($parent_cat->aggregation == GRADE_AGGREGATE_SUM) {
795             $grademax = format_float($item->grademax, $item->get_decimals());
796         } elseif ($item->gradetype == GRADE_TYPE_SCALE) {
797             $scale = $DB->get_record('scale', array('id' => $item->scaleid));
798             $scale_items = null;
799             if (empty($scale)) { //if the item is using a scale that's been removed
800                 $scale_items = array();
801             } else {
802                 $scale_items = explode(',', $scale->scale);
803             }
804             $grademax = end($scale_items) . ' (' . count($scale_items) . ')';
805         } elseif ($item->is_external_item()) {
806             $grademax = format_float($item->grademax, $item->get_decimals());
807         } else {
808             $grademax = '<input type="text" size="6" id="grademax'.$item->id.'" name="grademax_'.$item->id.'" value="'.format_float($item->grademax, $item->get_decimals()).'" />';
809         }
811         $itemcell = clone($this->itemcell);
812         $itemcell->text = $grademax;
813         return $itemcell;
814     }
816     public function is_hidden($mode='simple') {
817         global $CFG;
818         if ($mode == 'simple') {
819             return strstr($CFG->grade_item_advanced, 'grademax');
820         } elseif ($mode == 'advanced') {
821             return false;
822         }
823     }
826 class grade_edit_tree_column_aggregateonlygraded extends grade_edit_tree_column_category {
828     public function __construct($params) {
829         parent::__construct('aggregateonlygraded');
830     }
832     public function get_header_cell() {
833         global $OUTPUT;
834         $headercell = clone($this->headercell);
835         $headercell->style .= 'width: 40px;';
836         $headercell->text = get_string('aggregateonlygraded', 'grades')
837                 . $OUTPUT->help_icon('aggregateonlygraded', 'grades');
838         return $headercell;
839     }
841     public function get_category_cell($category, $levelclass, $params) {
842         $onlygradedcheck = ($category->aggregateonlygraded == 1) ? 'checked="checked"' : '';
843         $hidden = '<input type="hidden" name="aggregateonlygraded_'.$category->id.'" value="0" />';
844         $aggregateonlygraded ='<input type="checkbox" id="aggregateonlygraded_'.$category->id.'" name="aggregateonlygraded_'.$category->id.'" value="1" '.$onlygradedcheck . ' />';
846         if ($this->forced) {
847             $aggregateonlygraded = ($category->aggregateonlygraded) ? get_string('yes') : get_string('no');
848         }
850         $categorycell = clone($this->categorycell);
851         $categorycell->attributes['class'] .= ' ' . $levelclass;
852         $categorycell->text = $hidden.$aggregateonlygraded;
853         return $categorycell;
854     }
856     public function get_item_cell($item, $params) {
857         $itemcell = clone($this->itemcell);
858         $itemcell->text = ' - ';
859         return $itemcell;
860     }
863 class grade_edit_tree_column_aggregatesubcats extends grade_edit_tree_column_category {
865     public function __construct($params) {
866         parent::__construct('aggregatesubcats');
867     }
869     public function get_header_cell() {
870         global $OUTPUT;
871         $headercell = clone($this->headercell);
872         $headercell->style .= 'width: 40px;';
873         $headercell->text = get_string('aggregatesubcats', 'grades')
874               .$OUTPUT->help_icon('aggregatesubcats', 'grades');
875         return $headercell;
876     }
878     public function get_category_cell($category, $levelclass, $params) {
879         $subcatscheck = ($category->aggregatesubcats == 1) ? 'checked="checked"' : '';
880         $hidden = '<input type="hidden" name="aggregatesubcats_'.$category->id.'" value="0" />';
881         $aggregatesubcats = '<input type="checkbox" id="aggregatesubcats_'.$category->id.'" name="aggregatesubcats_'.$category->id.'" value="1" ' . $subcatscheck.' />';
883         if ($this->forced) {
884             $aggregatesubcats = ($category->aggregatesubcats) ? get_string('yes') : get_string('no');
885         }
887         $categorycell = clone($this->categorycell);
888         $categorycell->attributes['class'] .= ' ' . $levelclass;
889         $categorycell->text = $hidden.$aggregatesubcats;
890         return $categorycell;
892     }
894     public function get_item_cell($item, $params) {
895         $itemcell = clone($this->itemcell);
896         $itemcell->text = ' - ';
897         return $itemcell;
898     }
901 class grade_edit_tree_column_aggregateoutcomes extends grade_edit_tree_column_category {
903     public function __construct($params) {
904         parent::__construct('aggregateoutcomes');
905     }
907     public function get_header_cell() {
908         global $OUTPUT;
909         $headercell = clone($this->headercell);
910         $headercell->style .= 'width: 40px;';
911         $headercell->text = get_string('aggregateoutcomes', 'grades')
912               .$OUTPUT->help_icon('aggregateoutcomes', 'grades');
913         return $headercell;
914     }
916     public function get_category_cell($category, $levelclass, $params) {
917         $outcomescheck = ($category->aggregateoutcomes == 1) ? 'checked="checked"' : '';
918         $hidden = '<input type="hidden" name="aggregateoutcomes_'.$category->id.'" value="0" />';
919         $aggregateoutcomes = '<input type="checkbox" id="aggregateoutcomes_'.$category->id.'" name="aggregateoutcomes_'.$category->id.'" value="1" ' . $outcomescheck.' />';
921         if ($this->forced) {
922             $aggregateoutcomes = ($category->aggregateoutcomes) ? get_string('yes') : get_string('no');
923         }
925         $categorycell = clone($this->categorycell);
926         $categorycell->attributes['class'] .= ' ' . $levelclass;
927         $categorycell->text = $hidden.$aggregateoutcomes;
928         return $categorycell;
929     }
931     public function get_item_cell($item, $params) {
932         $itemcell = clone($this->itemcell);
933         $itemcell->text = ' - ';
934         return $itemcell;
935     }
937     public function is_hidden($mode='simple') {
938         global $CFG;
939         if ($CFG->enableoutcomes) {
940             return parent::is_hidden($mode);
941         } else {
942             return true;
943         }
944     }
947 class grade_edit_tree_column_droplow extends grade_edit_tree_column_category {
949     public function __construct($params) {
950         parent::__construct('droplow');
951     }
953     public function get_header_cell() {
954         global $OUTPUT;
955         $headercell = clone($this->headercell);
956         $headercell->text = get_string('droplow', 'grades').$OUTPUT->help_icon('droplow', 'grades');
957         return $headercell;
958     }
960     public function get_category_cell($category, $levelclass, $params) {
961         $droplow = '<input type="text" size="3" id="droplow_'.$category->id.'" name="droplow_'.$category->id.'" value="'.$category->droplow.'" />';
963         if ($this->forced) {
964             $droplow = $category->droplow;
965         }
967         $categorycell = clone($this->categorycell);
968         $categorycell->attributes['class']  .= ' ' . $levelclass;
969         $categorycell->text = $droplow;
970         return $categorycell;
971     }
973     public function get_item_cell($item, $params) {
974         $itemcell = clone($this->itemcell);
975         $itemcell->text = ' - ';
976         return $itemcell;
977     }
980 class grade_edit_tree_column_keephigh extends grade_edit_tree_column_category {
982     public function __construct($params) {
983         parent::__construct('keephigh');
984     }
986     public function get_header_cell() {
987         global $OUTPUT;
988         $headercell = clone($this->headercell);
989         $headercell->text = get_string('keephigh', 'grades').$OUTPUT->help_icon('keephigh', 'grades');
990         return $headercell;
991     }
993     public function get_category_cell($category, $levelclass, $params) {
994         $keephigh = '<input type="text" size="3" id="keephigh_'.$category->id.'" name="keephigh_'.$category->id.'" value="'.$category->keephigh.'" />';
996         if ($this->forced) {
997             $keephigh = $category->keephigh;
998         }
1000         $categorycell = clone($this->categorycell);
1001         $categorycell->attributes['class'] .= ' ' . $levelclass;
1002         $categorycell->text = $keephigh;
1003         return $categorycell;
1004     }
1006     public function get_item_cell($item, $params) {
1007         $itemcell = clone($this->itemcell);
1008         $itemcell->text = ' - ';
1009         return $itemcell;
1010     }
1013 class grade_edit_tree_column_multfactor extends grade_edit_tree_column {
1015     public function __construct($params) {
1016         parent::__construct();
1017     }
1019     public function get_header_cell() {
1020         global $OUTPUT;
1021         $headercell = clone($this->headercell);
1022         $headercell->text = get_string('multfactor', 'grades').$OUTPUT->help_icon('multfactor', 'grades');
1023         return $headercell;
1024     }
1026     public function get_category_cell($category, $levelclass, $params) {
1027         $categorycell = clone($this->categorycell);
1028         $categorycell->attributes['class'] .= ' ' . $levelclass;
1029         $categorycell->text = ' - ';
1030         return $categorycell;
1031     }
1033     public function get_item_cell($item, $params) {
1034         global $OUTPUT;
1036         $itemcell = clone($this->itemcell);
1037         if (!$item->is_raw_used()) {
1038             $itemcell->text = '&nbsp;';
1039             return $itemcell;
1040         }
1042         $multfactor = '<input type="text" size="4" id="multfactor'.$item->id.'" name="multfactor_'.$item->id.'" value="'.grade_edit_tree::format_number($item->multfactor).'" />';
1044         $itemcell->text = $multfactor;
1045         return $itemcell;
1046     }
1048     public function is_hidden($mode='simple') {
1049         global $CFG;
1050         if ($mode == 'simple') {
1051             return strstr($CFG->grade_item_advanced, 'multfactor');
1052         } elseif ($mode == 'advanced') {
1053             return false;
1054         }
1055     }
1058 class grade_edit_tree_column_plusfactor extends grade_edit_tree_column {
1060     public function get_header_cell() {
1061         global $OUTPUT;
1062         $headercell = clone($this->headercell);
1063         $headercell->text = get_string('plusfactor', 'grades').$OUTPUT->help_icon('plusfactor', 'grades');
1064         return $headercell;
1065     }
1067     public function get_category_cell($category, $levelclass, $params) {
1068         $categorycell = clone($this->categorycell);
1069         $categorycell->attributes['class'] .= ' ' . $levelclass;
1070         $categorycell->text = ' - ';
1071         return $categorycell;
1073     }
1075     public function get_item_cell($item, $params) {
1076         global $OUTPUT;
1078         $itemcell = clone($this->itemcell);
1079         if (!$item->is_raw_used()) {
1080             $itemcell->text = '&nbsp;';
1081             return $itemcell;
1082         }
1084         $plusfactor = '<input type="text" size="4" id="plusfactor_'.$item->id.'" name="plusfactor_'.$item->id.'" value="'.grade_edit_tree::format_number($item->plusfactor).'" />';
1086         $itemcell->text = $plusfactor;
1087         return $itemcell;
1089     }
1091     public function is_hidden($mode='simple') {
1092         global $CFG;
1093         if ($mode == 'simple') {
1094             return strstr($CFG->grade_item_advanced, 'plusfactor');
1095         } elseif ($mode == 'advanced') {
1096             return false;
1097         }
1098     }
1101 class grade_edit_tree_column_actions extends grade_edit_tree_column {
1103     public function __construct($params) {
1104         parent::__construct();
1105     }
1107     public function get_header_cell() {
1108         $headercell = clone($this->headercell);
1109         $headercell->attributes['class'] .= ' actions';
1110         $headercell->text = get_string('actions');
1111         return $headercell;
1112     }
1114     public function get_category_cell($category, $levelclass, $params) {
1116         if (empty($params['actions'])) {
1117             throw new Exception('Array key (actions) missing from 3rd param of grade_edit_tree_column_actions::get_category_actions($category, $levelclass, $params)');
1118         }
1120         $categorycell = clone($this->categorycell);
1121         $categorycell->attributes['class'] .= ' ' . $levelclass;
1122         $categorycell->text = $params['actions'];
1123         return $categorycell;
1124     }
1126     public function get_item_cell($item, $params) {
1127         if (empty($params['actions'])) {
1128             throw new Exception('Array key (actions) missing from 2nd param of grade_edit_tree_column_actions::get_item_cell($item, $params)');
1129         }
1130         $itemcell = clone($this->itemcell);
1131         $itemcell->attributes['class'] .= ' actions';
1132         $itemcell->text = $params['actions'];
1133         return $itemcell;
1134     }
1136     public function is_hidden($mode='simple') {
1137         return false;
1138     }
1141 class grade_edit_tree_column_select extends grade_edit_tree_column {
1143     public function get_header_cell() {
1144         $headercell = clone($this->headercell);
1145         $headercell->attributes['class'] .= ' selection';
1146         $headercell->text = get_string('select');
1147         return $headercell;
1148     }
1150     public function get_category_cell($category, $levelclass, $params) {
1151         global $OUTPUT;
1152         if (empty($params['eid'])) {
1153             throw new Exception('Array key (eid) missing from 3rd param of grade_edit_tree_column_select::get_category_cell($category, $levelclass, $params)');
1154         }
1155         $selectall  = new action_link(new moodle_url('#'), get_string('all'), new component_action('click', 'togglecheckboxes', array('eid' => $params['eid'], 'check' => true)));
1156         $selectnone = new action_link(new moodle_url('#'), get_string('none'), new component_action('click', 'togglecheckboxes', array('eid' => $params['eid'], 'check' => false)));
1158         $categorycell = clone($this->categorycell);
1159         $categorycell->attributes['class'] .= ' last ' . $levelclass;
1160         $categorycell->style .= 'text-align: center;';
1161         $categorycell->text = $OUTPUT->render($selectall) . '<br />' . $OUTPUT->render($selectnone);
1162         return $categorycell;
1163     }
1165     public function get_item_cell($item, $params) {
1166         if (empty($params['itemtype']) || empty($params['eid'])) {
1167             error('Array key (itemtype or eid) missing from 2nd param of grade_edit_tree_column_select::get_item_cell($item, $params)');
1168         }
1169         $itemselect = '';
1171         if ($params['itemtype'] != 'course' && $params['itemtype'] != 'category') {
1172             $itemselect = '<input class="itemselect" type="checkbox" name="select_'.$params['eid'].'" onchange="toggleCategorySelector();"/>'; // TODO: convert to YUI handler
1173         }
1174         //html_writer::table() will wrap the item cell contents in a <TD> so don't do it here
1175         return $itemselect;
1176     }
1178     public function is_hidden($mode='simple') {
1179         return false;
1180     }