93bd3a312f41b9b6eaa73a21cc84ece334685afc
[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 grade_tree $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();
53     /**
54      * Show calculator icons next to manual grade items
55      * @var bool $show_calculations
56      */
57     private $show_calculations;
59     /**
60      * Constructor
61      */
62     public function __construct($gtree, $moving=false, $gpr) {
63         global $USER, $OUTPUT, $COURSE;
65         $systemdefault = get_config('moodle', 'grade_report_showcalculations');
66         $this->show_calculations = get_user_preferences('grade_report_showcalculations', $systemdefault);
68         $this->gtree = $gtree;
69         $this->moving = $moving;
70         $this->gpr = $gpr;
71         $this->deepest_level = $this->get_deepest_level($this->gtree->top_element);
73         $this->columns = array(grade_edit_tree_column::factory('name', array('deepest_level' => $this->deepest_level)),
74                                grade_edit_tree_column::factory('aggregation', array('flag' => true)));
76         if ($this->uses_weight) {
77             $this->columns[] = grade_edit_tree_column::factory('weight', array('adv' => 'weight'));
78         }
79         if ($this->uses_extra_credit) {
80             $this->columns[] = grade_edit_tree_column::factory('extracredit', array('adv' => 'aggregationcoef'));
81         }
83         $this->columns[] = grade_edit_tree_column::factory('range'); // This is not a setting... How do we deal with it?
84         $this->columns[] = grade_edit_tree_column::factory('aggregateonlygraded', array('flag' => true));
85         $this->columns[] = grade_edit_tree_column::factory('aggregatesubcats', array('flag' => true));
86         $this->columns[] = grade_edit_tree_column::factory('aggregateoutcomes', array('flag' => true));
87         $this->columns[] = grade_edit_tree_column::factory('droplow', array('flag' => true));
88         $this->columns[] = grade_edit_tree_column::factory('keephigh', array('flag' => true));
89         $this->columns[] = grade_edit_tree_column::factory('multfactor', array('adv' => true));
90         $this->columns[] = grade_edit_tree_column::factory('plusfactor', array('adv' => true));
91         $this->columns[] = grade_edit_tree_column::factory('actions');
92         $this->columns[] = grade_edit_tree_column::factory('select');
94         $this->table = new html_table();
95         $this->table->id = "grade_edit_tree_table";
96         $this->table->attributes['class'] = 'generaltable simple';
98         foreach ($this->columns as $column) {
99             if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden()) {
100                 $this->table->head[] = $column->get_header_cell();
101             }
102         }
104         $rowcount = 0;
105         $this->table->data = $this->build_html_tree($this->gtree->top_element, true, array(), 0, $rowcount);
106     }
108     /**
109      * Recursive function for building the table holding the grade categories and items,
110      * with CSS indentation and styles.
111      *
112      * @param array   $element The current tree element being rendered
113      * @param boolean $totals Whether or not to print category grade items (category totals)
114      * @param array   $parents An array of parent categories for the current element (used for indentation and row classes)
115      *
116      * @return string HTML
117      */
118     public function build_html_tree($element, $totals, $parents, $level, &$row_count) {
119         global $CFG, $COURSE, $USER, $OUTPUT;
121         $object = $element['object'];
122         $eid    = $element['eid'];
123         $object->name = $this->gtree->get_element_header($element, true, true, false);
124         $object->stripped_name = $this->gtree->get_element_header($element, false, false, false);
126         $is_category_item = false;
127         if ($element['type'] == 'categoryitem' || $element['type'] == 'courseitem') {
128             $is_category_item = true;
129         }
131         $rowclasses = array();
132         foreach ($parents as $parent_eid) {
133             $rowclasses[] = $parent_eid;
134         }
136         $actions = '';
138         if (!$is_category_item) {
139             $actions .= $this->gtree->get_edit_icon($element, $this->gpr);
140         }
142         if ($this->show_calculations) {
143             $actions .= $this->gtree->get_calculation_icon($element, $this->gpr);
144         }
146         if ($element['type'] == 'item' or ($element['type'] == 'category' and $element['depth'] > 1)) {
147             if ($this->element_deletable($element)) {
148                 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'delete', 'eid' => $eid, 'sesskey' => sesskey()));
149                 $actions .= $OUTPUT->action_icon($aurl, new pix_icon('t/delete', get_string('delete')));
150             }
152             $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'moveselect', 'eid' => $eid, 'sesskey' => sesskey()));
153             $actions .= $OUTPUT->action_icon($aurl, new pix_icon('t/move', get_string('move')));
154         }
156         $actions .= $this->gtree->get_hiding_icon($element, $this->gpr);
157         $actions .= $this->gtree->get_locking_icon($element, $this->gpr);
158         $actions .= $this->gtree->get_reset_icon($element, $this->gpr);
160         $returnrows = array();
161         $root = false;
163         $id = required_param('id', PARAM_INT);
165         /// prepare move target if needed
166         $last = '';
168         /// print the list items now
169         if ($this->moving == $eid) {
170             // do not diplay children
171             $cell = new html_table_cell();
172             $cell->colspan = 12;
173             $cell->attributes['class'] = $element['type'] . ' moving';
174             $cell->text = $object->name.' ('.get_string('move').')';
175             return array(new html_table_row(array($cell)));
176         }
178         if ($element['type'] == 'category') {
179             $level++;
180             $this->categories[$object->id] = $object->stripped_name;
181             $category = grade_category::fetch(array('id' => $object->id));
182             $item = $category->get_grade_item();
184             // Add aggregation coef input if not a course item and if parent category has correct aggregation type
185             $dimmed = ($item->is_hidden()) ? 'dimmed' : '';
187             // Before we print the category's row, we must find out how many rows will appear below it (for the filler cell's rowspan)
188             $aggregation_position = grade_get_setting($COURSE->id, 'aggregationposition', $CFG->grade_aggregationposition);
189             $category_total_data = null; // Used if aggregationposition is set to "last", so we can print it last
191             $html_children = array();
193             $row_count = 0;
195             foreach($element['children'] as $child_el) {
196                 $moveto = null;
198                 if (empty($child_el['object']->itemtype)) {
199                     $child_el['object']->itemtype = false;
200                 }
202                 if (($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') && !$totals) {
203                     continue;
204                 }
206                 $child_eid = $child_el['eid'];
207                 $first = '';
209                 if ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') {
210                     $first = array('first' => 1);
211                     $child_eid = $eid;
212                 }
214                 if ($this->moving && $this->moving != $child_eid) {
216                     $strmove     = get_string('move');
217                     $strmovehere = get_string('movehere');
218                     $actions = ''; // no action icons when moving
220                     $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'move', 'eid' => $this->moving, 'moveafter' => $child_eid, 'sesskey' => sesskey()));
221                     if ($first) {
222                         $aurl->params($first);
223                     }
225                     $cell = new html_table_cell();
226                     $cell->colspan = 12;
228                     $icon = new pix_icon('movehere', $strmovehere, null, array('class'=>'movetarget'));
229                     $cell->text = $OUTPUT->action_icon($aurl, $icon);
231                     $moveto = new html_table_row(array($cell));
232                 }
234                 $newparents = $parents;
235                 $newparents[] = $eid;
237                 $row_count++;
238                 $child_row_count = 0;
240                 // If moving, do not print course and category totals, but still print the moveto target box
241                 if ($this->moving && ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category')) {
242                     $html_children[] = $moveto;
243                 } elseif ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') {
244                     // We don't build the item yet because we first need to know the deepest level of categories (for category/name colspans)
245                     $category_total_item = $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count);
246                     if (!$aggregation_position) {
247                         $html_children = array_merge($html_children, $category_total_item);
248                     }
249                 } else {
250                     $html_children = array_merge($html_children, $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count));
251                     if (!empty($moveto)) {
252                         $html_children[] = $moveto;
253                     }
255                     if ($this->moving) {
256                         $row_count++;
257                     }
258                 }
260                 $row_count += $child_row_count;
262                 // If the child is a category, increment row_count by one more (for the extra coloured row)
263                 if ($child_el['type'] == 'category') {
264                     $row_count++;
265                 }
266             }
268             // Print category total at the end if aggregation position is "last" (1)
269             if (!empty($category_total_item) && $aggregation_position) {
270                 $html_children = array_merge($html_children, $category_total_item);
271             }
273             // Determine if we are at the root
274             if (isset($element['object']->grade_item) && $element['object']->grade_item->is_course_item()) {
275                 $root = true;
276             }
278             $levelclass = "level$level";
280             $courseclass = '';
281             if ($level == 1) {
282                 $courseclass = 'coursecategory';
283             }
285             $row = new html_table_row();
286             $row->attributes['class'] = $courseclass . ' category ' . $dimmed;
287             foreach ($rowclasses as $class) {
288                 $row->attributes['class'] .= ' ' . $class;
289             }
291             $headercell = new html_table_cell();
292             $headercell->header = true;
293             $headercell->scope = 'row';
294             $headercell->attributes['title'] = $object->stripped_name;
295             $headercell->attributes['class'] = 'cell rowspan ' . $levelclass;
296             $headercell->rowspan = $row_count + 1;
297             $row->cells[] = $headercell;
299             foreach ($this->columns as $column) {
300                 if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden()) {
301                     $row->cells[] = $column->get_category_cell($category, $levelclass, array('id' => $id, 'name' => $object->name, 'level' => $level, 'actions' => $actions, 'eid' => $eid));
302                 }
303             }
305             $returnrows[] = $row;
307             $returnrows = array_merge($returnrows, $html_children);
309             // Print a coloured row to show the end of the category across the table
310             $endcell = new html_table_cell();
311             $endcell->colspan = (19 - $level);
312             $endcell->attributes['class'] = 'colspan ' . $levelclass;
314             $returnrows[] = new html_table_row(array($endcell));
316         } else { // Dealing with a grade item
318             $item = grade_item::fetch(array('id' => $object->id));
319             $element['type'] = 'item';
320             $element['object'] = $item;
322             $categoryitemclass = '';
323             if ($item->itemtype == 'category') {
324                 $categoryitemclass = 'categoryitem';
325             }
327             $dimmed = ($item->is_hidden()) ? "dimmed_text" : "";
328             $gradeitemrow = new html_table_row();
329             $gradeitemrow->attributes['class'] = $categoryitemclass . ' item ' . $dimmed;
330             foreach ($rowclasses as $class) {
331                 $gradeitemrow->attributes['class'] .= ' ' . $class;
332             }
334             foreach ($this->columns as $column) {
335                 if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden()) {
336                     $gradeitemrow->cells[] = $column->get_item_cell($item, array('id' => $id, 'name' => $object->name, 'level' => $level, 'actions' => $actions,
337                                                                  'element' => $element, 'eid' => $eid, 'itemtype' => $object->itemtype));
338                 }
339             }
341             $returnrows[] = $gradeitemrow;
342         }
344         return $returnrows;
346     }
348     /**
349      * Given a grade_item object, returns a labelled input if an aggregation coefficient (weight or extra credit) applies to it.
350      * @param grade_item $item
351      * @param string type "extra" or "weight": the type of the column hosting the weight input
352      * @return string HTML
353      */
354     static function get_weight_input($item, $type) {
355         global $OUTPUT;
357         if (!is_object($item) || get_class($item) !== 'grade_item') {
358             throw new Exception('grade_edit_tree::get_weight_input($item) was given a variable that is not of the required type (grade_item object)');
359             return false;
360         }
362         if ($item->is_course_item()) {
363             return '';
364         }
366         $parent_category = $item->get_parent_category();
367         $parent_category->apply_forced_settings();
368         $aggcoef = $item->get_coefstring();
370         if ((($aggcoef == 'aggregationcoefweight' || $aggcoef == 'aggregationcoef') && $type == 'weight') ||
371             ($aggcoef == 'aggregationcoefextraweight' && $type == 'extra')) {
372             return '<label class="accesshide" for="weight_'.$item->id.'">'.
373                 get_string('extracreditvalue', 'grades', $item->itemname).'</label>'.
374                 '<input type="text" size="6" id="weight_'.$item->id.'" name="weight_'.$item->id.'"
375                 value="'.grade_edit_tree::format_number($item->aggregationcoef).'" />';
376         } elseif (($aggcoef == 'aggregationcoefextrasum' || $aggcoef == 'aggregationcoefextraweightsum') && $type == 'extra') {
377             $checked = ($item->aggregationcoef > 0) ? 'checked="checked"' : '';
378             return '<input type="hidden" name="extracredit_'.$item->id.'" value="0" />
379                 <label class="accesshide" for="extracredit_'.$item->id.'">'.
380                 get_string('extracreditvalue', 'grades', $item->itemname).'</label>
381                 <input type="checkbox" id="extracredit_'.$item->id.'" name="extracredit_'.$item->id.'" value="1" '."$checked />\n";
382         } else if ($aggcoef == 'aggregationcoefextraweightsum' && $type == 'weight') {
383             $label = '';
384             if ($item->weightoverride && $parent_category->aggregation == GRADE_AGGREGATE_SUM) {
385                 $label = get_string('adjusted', 'grades');
386             }
388             $name = 'weight_' . $item->id;
389             $hiddenlabel = html_writer::tag(
390                 'label',
391                 get_string('weight', 'grades', $item->itemname),
392                 array(
393                     'class' => 'accesshide',
394                     'for' => $name
395                 )
396             );
398             $input = html_writer::empty_tag(
399                 'input',
400                 array(
401                     'type' =>   'text',
402                     'size' =>   6,
403                     'id' =>     $name,
404                     'name' =>   $name,
405                     'value' =>  grade_edit_tree::format_number($item->aggregationcoef2 * 100.0)
406                 )
407             );
409             return $hiddenlabel . $input . $label;
410         } else {
411             return '';
412         }
413     }
415     //Trims trailing zeros
416     //Used on the 'categories and items' page for grade items settings like aggregation co-efficient
417     //Grader report has its own decimal place settings so they are handled elsewhere
418     static function format_number($number) {
419         $formatted = rtrim(format_float($number, 4),'0');
420         if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point
421             $formatted .= '0';
422         }
423         return $formatted;
424     }
426     /**
427      * Given an element of the grade tree, returns whether it is deletable or not (only manual grade items are deletable)
428      *
429      * @param array $element
430      * @return bool
431      */
432     function element_deletable($element) {
433         global $COURSE;
435         if ($element['type'] != 'item') {
436             return true;
437         }
439         $grade_item = $element['object'];
441         if ($grade_item->itemtype != 'mod' or $grade_item->is_outcome_item() or $grade_item->gradetype == GRADE_TYPE_NONE) {
442             return true;
443         }
445         $modinfo = get_fast_modinfo($COURSE);
446         if (!isset($modinfo->instances[$grade_item->itemmodule][$grade_item->iteminstance])) {
447             // module does not exist
448             return true;
449         }
451         return false;
452     }
454     /**
455      * Given the grade tree and an array of element ids (e.g. c15, i42), and expecting the 'moveafter' URL param,
456      * moves the selected items to the requested location. Then redirects the user to the given $returnurl
457      *
458      * @param object $gtree The grade tree (a recursive representation of the grade categories and grade items)
459      * @param array $eids
460      * @param string $returnurl
461      */
462     function move_elements($eids, $returnurl) {
463         $moveafter = required_param('moveafter', PARAM_INT);
465         if (!is_array($eids)) {
466             $eids = array($eids);
467         }
469         if(!$after_el = $this->gtree->locate_element("cg$moveafter")) {
470             print_error('invalidelementid', '', $returnurl);
471         }
473         $after = $after_el['object'];
474         $parent = $after;
475         $sortorder = $after->get_sortorder();
477         foreach ($eids as $eid) {
478             if (!$element = $this->gtree->locate_element($eid)) {
479                 print_error('invalidelementid', '', $returnurl);
480             }
481             $object = $element['object'];
483             $object->set_parent($parent->id);
484             $object->move_after_sortorder($sortorder);
485             $sortorder++;
486         }
488         redirect($returnurl, '', 0);
489     }
491     /**
492      * Recurses through the entire grade tree to find and return the maximum depth of the tree.
493      * This should be run only once from the root element (course category), and is used for the
494      * indentation of the Name column's cells (colspan)
495      *
496      * @param array $element An array of values representing a grade tree's element (all grade items in this case)
497      * @param int $level The level of the current recursion
498      * @param int $deepest_level A value passed to each subsequent level of recursion and incremented if $level > $deepest_level
499      * @return int Deepest level
500      */
501     function get_deepest_level($element, $level=0, $deepest_level=1) {
502         $object = $element['object'];
504         $level++;
505         $coefstring = $element['object']->get_coefstring();
506         if ($element['type'] == 'category') {
507             if ($coefstring == 'aggregationcoefweight' || $coefstring == 'aggregationcoefextraweightsum') {
508                 $this->uses_weight = true;
509             }
510             if ($coefstring ==  'aggregationcoefextraweight' || $coefstring == 'aggregationcoefextraweightsum' || $coefstring == 'aggregationcoefextrasum') {
511                 $this->uses_extra_credit = true;
512             }
514             foreach($element['children'] as $child_el) {
515                 if ($level > $deepest_level) {
516                     $deepest_level = $level;
517                 }
518                 $deepest_level = $this->get_deepest_level($child_el, $level, $deepest_level);
519             }
520         }
522         return $deepest_level;
523     }
526 abstract class grade_edit_tree_column {
527     public $forced;
528     public $hidden;
529     public $forced_hidden;
530     public $advanced_hidden;
531     public $hide_when_moving = true;
532     /**
533      * html_table_cell object used as a template for header cells in all categories.
534      * It must be cloned before being used.
535      * @var html_table_cell $headercell
536      */
537     public $headercell;
538     /**
539      * html_table_cell object used as a template for category cells in all categories.
540      * It must be cloned before being used.
541      * @var html_table_cell $categorycell
542      */
543     public $categorycell;
544     /**
545      * html_table_cell object used as a template for item cells in all categories.
546      * It must be cloned before being used.
547      * @var html_table_cell $itemcell
548      */
549     public $itemcell;
551     public static function factory($name, $params=array()) {
552         $class_name = "grade_edit_tree_column_$name";
553         if (class_exists($class_name)) {
554             return new $class_name($params);
555         }
556     }
558     public abstract function get_header_cell();
560     public abstract function get_category_cell($category, $levelclass, $params);
562     public abstract function get_item_cell($item, $params);
564     public abstract function is_hidden($mode='simple');
566     public function __construct() {
567         $this->headercell = new html_table_cell();
568         $this->headercell->header = true;
569         $this->headercell->style = 'whitespace: normal;';
570         $this->headercell->attributes['class'] = 'header';
572         $this->categorycell = new html_table_cell();
573         $this->categorycell->attributes['class']  = 'cell';
575         $this->itemcell = new html_table_cell();
576         $this->itemcell->attributes['class'] = 'cell';
577     }
580 abstract class grade_edit_tree_column_category extends grade_edit_tree_column {
582     public $forced;
583     public $advanced;
585     public function __construct($name) {
586         global $CFG;
587         $this->forced = (int)$CFG->{"grade_$name"."_flag"} & 1;
588         $this->advanced = (int)$CFG->{"grade_$name"."_flag"} & 2;
589         parent::__construct();
590     }
592     public function is_hidden($mode='simple') {
593         global $CFG;
594         if ($mode == 'simple') {
595             return $this->advanced;
596         } elseif ($mode == 'advanced') {
597             if ($this->forced && $CFG->grade_hideforcedsettings) {
598                 return true;
599             } else {
600                 return false;
601             }
602         }
603     }
606 class grade_edit_tree_column_name extends grade_edit_tree_column {
607     public $forced = false;
608     public $hidden = false;
609     public $forced_hidden = false;
610     public $advanced_hidden = false;
611     public $deepest_level = 1;
612     public $hide_when_moving = false;
614     public function __construct($params) {
615         if (empty($params['deepest_level'])) {
616             throw new Exception('Tried to instantiate a grade_edit_tree_column_name object without the "deepest_level" param!');
617         }
619         $this->deepest_level = $params['deepest_level'];
620         parent::__construct();
621     }
623     public function get_header_cell() {
624         $headercell = clone($this->headercell);
625         $headercell->attributes['class'] .= ' name';
626         $headercell->colspan = $this->deepest_level + 1;
627         $headercell->text = get_string('name');
628         return $headercell;
629     }
631     public function get_category_cell($category, $levelclass, $params) {
632         global $OUTPUT;
633         if (empty($params['name']) || empty($params['level'])) {
634             throw new Exception('Array key (name or level) missing from 3rd param of grade_edit_tree_column_name::get_category_cell($category, $levelclass, $params)');
635         }
636         $categorycell = clone($this->categorycell);
637         $categorycell->attributes['class'] .= ' name ' . $levelclass;
638         $categorycell->colspan = ($this->deepest_level +1) - $params['level'];
639         $categorycell->text = $OUTPUT->heading($params['name'], 4);
640         return $categorycell;
641     }
643     public function get_item_cell($item, $params) {
644         global $CFG;
646         if (empty($params['element']) || empty($params['name']) || empty($params['level'])) {
647             throw new Exception('Array key (name, level or element) missing from 2nd param of grade_edit_tree_column_name::get_item_cell($item, $params)');
648         }
650         $name = $params['name'];
652         $itemcell = clone($this->itemcell);
653         $itemcell->attributes['class'] .= ' name';
654         $itemcell->colspan = ($this->deepest_level + 1) - $params['level'];
655         $itemcell->text = $name;
656         return $itemcell;
657     }
659     public function is_hidden($mode='simple') {
660         return false;
661     }
664 class grade_edit_tree_column_aggregation extends grade_edit_tree_column_category {
666     public function __construct($params) {
667         parent::__construct('aggregation');
668     }
670     public function get_header_cell() {
671         global $OUTPUT;
672         $headercell = clone($this->headercell);
673         $headercell->text = get_string('aggregation', 'grades').$OUTPUT->help_icon('aggregation', 'grades');
674         return $headercell;
675     }
677     public function get_category_cell($category, $levelclass, $params) {
678         global $CFG, $OUTPUT;
679         if (empty($params['id'])) {
680             throw new Exception('Array key (id) missing from 3rd param of grade_edit_tree_column_aggregation::get_category_cell($category, $levelclass, $params)');
681         }
683         $options = grade_helper::get_aggregation_strings();
685         $visible = explode(',', $CFG->grade_aggregations_visible);
686         foreach ($options as $constant => $string) {
687             if (!in_array($constant, $visible) && $constant != $category->aggregation) {
688                 unset($options[$constant]);
689             }
690         }
692         if ($this->forced) {
693             $aggregation = $options[$category->aggregation];
694         } else {
695             $attributes = array();
696             $attributes['id'] = 'aggregation_'.$category->id;
697             $attributes['class'] = 'ignoredirty';
698             $aggregation = html_writer::label(get_string('aggregation', 'grades'), 'aggregation_'.$category->id, false, array('class' => 'accesshide'));
699             $aggregation .= html_writer::select($options, 'aggregation_'.$category->id, $category->aggregation, null, $attributes);
700             $action = new component_action('change', 'update_category_aggregation', array('courseid' => $params['id'], 'category' => $category->id, 'sesskey' => sesskey()));
701             $OUTPUT->add_action_handler($action, 'aggregation_'.$category->id);
702         }
704         $categorycell = clone($this->categorycell);
705         $categorycell->attributes['class'] .= ' ' . $levelclass;
706         $categorycell->text = $aggregation;
707         return $categorycell;
709     }
711     public function get_item_cell($item, $params) {
712         $itemcell = clone($this->itemcell);
713         $itemcell->text = ' - ';
714         return $itemcell;
715     }
718 class grade_edit_tree_column_extracredit extends grade_edit_tree_column {
720     public function get_header_cell() {
721         global $OUTPUT;
722         $headercell = clone($this->headercell);
723         $headercell->text = get_string('aggregationcoefextra', 'grades').$OUTPUT->help_icon('aggregationcoefextra', 'grades');
724         return $headercell;
725     }
727     public function get_category_cell($category, $levelclass, $params) {
728         $item = $category->get_grade_item();
729         $categorycell = clone($this->categorycell);
730         $categorycell->attributes['class'] .= ' ' . $levelclass;
731         $categorycell->text = grade_edit_tree::get_weight_input($item, 'extra');
732         return $categorycell;
733     }
735     public function get_item_cell($item, $params) {
736         if (empty($params['element'])) {
737             throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)');
738         }
740         $itemcell = clone($this->itemcell);
741         $itemcell->text = '&nbsp;';
743         if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))) {
744             $itemcell->text = grade_edit_tree::get_weight_input($item, 'extra');
745         }
747         return $itemcell;
748     }
750     public function is_hidden($mode='simple') {
751         global $CFG;
752         if ($mode == 'simple') {
753             return strstr($CFG->grade_item_advanced, 'aggregationcoef');
754         } elseif ($mode == 'advanced') {
755             return false;
756         }
757     }
760 class grade_edit_tree_column_weight extends grade_edit_tree_column {
762     public function get_header_cell() {
763         global $OUTPUT;
764         $headercell = clone($this->headercell);
765         $headercell->text = get_string('weights', 'grades').$OUTPUT->help_icon('aggregationcoefweight', 'grades');
766         return $headercell;
767     }
769     public function get_category_cell($category, $levelclass, $params) {
771         $item = $category->get_grade_item();
772         $categorycell = clone($this->categorycell);
773         $categorycell->attributes['class']  .= ' ' . $levelclass;
774         $categorycell->text = grade_edit_tree::get_weight_input($item, 'weight');
775         return $categorycell;
776     }
778     public function get_item_cell($item, $params) {
779         if (empty($params['element'])) {
780             throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)');
781         }
782         $itemcell = clone($this->itemcell);
783         $itemcell->text = '&nbsp;';
785         if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))) {
786             $itemcell->text = grade_edit_tree::get_weight_input($item, 'weight');
787         }
789         return $itemcell;
790     }
792     public function is_hidden($mode='simple') {
793         global $CFG;
794         if ($mode == 'simple') {
795             return strstr($CFG->grade_item_advanced, 'aggregationcoef');
796         } elseif ($mode == 'advanced') {
797             return false;
798         }
799     }
802 class grade_edit_tree_column_range extends grade_edit_tree_column {
804     public function get_header_cell() {
805         $headercell = clone($this->headercell);
806         $headercell->text = get_string('maxgrade', 'grades');
807         return $headercell;
808     }
810     public function get_category_cell($category, $levelclass, $params) {
811         $categorycell = clone($this->categorycell);
812         $categorycell->attributes['class'] .= ' range ' . $levelclass;
813         $categorycell->text = ' - ';
814         return $categorycell;
815     }
817     public function get_item_cell($item, $params) {
818         global $DB, $OUTPUT;
820         // If the parent aggregation is Natural, we should show the number, even for scales, as that value is used...
821         // ...in the computation. For text grades, the grademax is not used, so we can still show the no value string.
822         $parent_cat = $item->get_parent_category();
823         if ($item->gradetype == GRADE_TYPE_TEXT) {
824             $grademax = ' - ';
825         } else if ($parent_cat->aggregation == GRADE_AGGREGATE_SUM) {
826             $grademax = format_float($item->grademax, $item->get_decimals());
827         } elseif ($item->gradetype == GRADE_TYPE_SCALE) {
828             $scale = $DB->get_record('scale', array('id' => $item->scaleid));
829             $scale_items = null;
830             if (empty($scale)) { //if the item is using a scale that's been removed
831                 $scale_items = array();
832             } else {
833                 $scale_items = explode(',', $scale->scale);
834             }
835             $grademax = end($scale_items) . ' (' . count($scale_items) . ')';
836         } elseif ($item->is_external_item()) {
837             $grademax = format_float($item->grademax, $item->get_decimals());
838         } else {
839             $grademax = '<label class="accesshide" for="grademax'.$item->id.'">'.get_string('grademax', 'grades').'</label>
840                 <input type="text" size="6" id="grademax'.$item->id.'" name="grademax_'.$item->id.'" value="'.
841                 format_float($item->grademax, $item->get_decimals()).'" />';
842         }
844         $itemcell = clone($this->itemcell);
845         $itemcell->text = $grademax;
846         return $itemcell;
847     }
849     public function is_hidden($mode='simple') {
850         global $CFG;
851         if ($mode == 'simple') {
852             return strstr($CFG->grade_item_advanced, 'grademax');
853         } elseif ($mode == 'advanced') {
854             return false;
855         }
856     }
859 class grade_edit_tree_column_aggregateonlygraded extends grade_edit_tree_column_category {
861     public function __construct($params) {
862         parent::__construct('aggregateonlygraded');
863     }
865     public function get_header_cell() {
866         global $OUTPUT;
867         $headercell = clone($this->headercell);
868         $headercell->style .= 'width: 40px;';
869         $headercell->text = get_string('aggregateonlygraded', 'grades')
870                 . $OUTPUT->help_icon('aggregateonlygraded', 'grades');
871         return $headercell;
872     }
874     public function get_category_cell($category, $levelclass, $params) {
875         $onlygradedcheck = ($category->aggregateonlygraded == 1) ? 'checked="checked"' : '';
876         $hidden = '<input type="hidden" name="aggregateonlygraded_'.$category->id.'" value="0" />';
877         $aggregateonlygraded = '<label class="accesshide" for="aggregateonlygraded_'.$category->id.'">'.
878                 get_string('aggregateonlygraded', 'grades').'</label>
879                 <input type="checkbox" id="aggregateonlygraded_'.$category->id.'" name="aggregateonlygraded_'.
880                 $category->id.'" value="1" '.$onlygradedcheck . ' />';
882         if ($this->forced) {
883             $aggregateonlygraded = ($category->aggregateonlygraded) ? get_string('yes') : get_string('no');
884         }
886         $categorycell = clone($this->categorycell);
887         $categorycell->attributes['class'] .= ' ' . $levelclass;
888         $categorycell->text = $hidden.$aggregateonlygraded;
889         return $categorycell;
890     }
892     public function get_item_cell($item, $params) {
893         $itemcell = clone($this->itemcell);
894         $itemcell->text = ' - ';
895         return $itemcell;
896     }
899 class grade_edit_tree_column_aggregatesubcats extends grade_edit_tree_column_category {
901     public function __construct($params) {
902         parent::__construct('aggregatesubcats');
903     }
905     public function get_header_cell() {
906         global $OUTPUT;
907         $headercell = clone($this->headercell);
908         $headercell->style .= 'width: 40px;';
909         $headercell->text = get_string('aggregatesubcats', 'grades')
910               .$OUTPUT->help_icon('aggregatesubcats', 'grades');
911         return $headercell;
912     }
914     public function get_category_cell($category, $levelclass, $params) {
915         $subcatscheck = ($category->aggregatesubcats == 1) ? 'checked="checked"' : '';
916         $hidden = '<input type="hidden" name="aggregatesubcats_'.$category->id.'" value="0" />';
917         $aggregatesubcats = '<label class="accesshide" for="aggregatesubcats_'.$category->id.'">'.
918                 get_string('aggregatesubcats', 'grades').'</label>
919                 <input type="checkbox" id="aggregatesubcats_'.$category->id.'" name="aggregatesubcats_'.$category->id.
920                 '" value="1" ' . $subcatscheck.' />';
922         if ($this->forced) {
923             $aggregatesubcats = ($category->aggregatesubcats) ? get_string('yes') : get_string('no');
924         }
926         $categorycell = clone($this->categorycell);
927         $categorycell->attributes['class'] .= ' ' . $levelclass;
928         $categorycell->text = $hidden.$aggregatesubcats;
929         return $categorycell;
931     }
933     public function get_item_cell($item, $params) {
934         $itemcell = clone($this->itemcell);
935         $itemcell->text = ' - ';
936         return $itemcell;
937     }
940 class grade_edit_tree_column_aggregateoutcomes extends grade_edit_tree_column_category {
942     public function __construct($params) {
943         parent::__construct('aggregateoutcomes');
944     }
946     public function get_header_cell() {
947         global $OUTPUT;
948         $headercell = clone($this->headercell);
949         $headercell->style .= 'width: 40px;';
950         $headercell->text = get_string('aggregateoutcomes', 'grades')
951               .$OUTPUT->help_icon('aggregateoutcomes', 'grades');
952         return $headercell;
953     }
955     public function get_category_cell($category, $levelclass, $params) {
956         $outcomescheck = ($category->aggregateoutcomes == 1) ? 'checked="checked"' : '';
957         $hidden = '<input type="hidden" name="aggregateoutcomes_'.$category->id.'" value="0" />';
958         $aggregateoutcomes = '<label class="accesshide" for="aggregateoutcomes_'.$category->id.'">'.
959                 get_string('aggregateoutcomes', 'grades').'</label>
960                 <input type="checkbox" id="aggregateoutcomes_'.$category->id.'" name="aggregateoutcomes_'.$category->id.
961                 '" value="1" ' . $outcomescheck.' />';
963         if ($this->forced) {
964             $aggregateoutcomes = ($category->aggregateoutcomes) ? get_string('yes') : get_string('no');
965         }
967         $categorycell = clone($this->categorycell);
968         $categorycell->attributes['class'] .= ' ' . $levelclass;
969         $categorycell->text = $hidden.$aggregateoutcomes;
970         return $categorycell;
971     }
973     public function get_item_cell($item, $params) {
974         $itemcell = clone($this->itemcell);
975         $itemcell->text = ' - ';
976         return $itemcell;
977     }
979     public function is_hidden($mode='simple') {
980         global $CFG;
981         if ($CFG->enableoutcomes) {
982             return parent::is_hidden($mode);
983         } else {
984             return true;
985         }
986     }
989 class grade_edit_tree_column_droplow extends grade_edit_tree_column_category {
991     public function __construct($params) {
992         parent::__construct('droplow');
993     }
995     public function get_header_cell() {
996         global $OUTPUT;
997         $headercell = clone($this->headercell);
998         $headercell->text = get_string('droplow', 'grades').$OUTPUT->help_icon('droplow', 'grades');
999         return $headercell;
1000     }
1002     public function get_category_cell($category, $levelclass, $params) {
1003         $droplow = '<label class="accesshide" for="droplow_' . $category->id.'">' . get_string('droplowestvalue', 'grades') . '</label>';
1004         $droplow .= '<input type="text" size="3" id="droplow_' . $category->id . '" name="droplow_' . $category->id . '" value="'
1005                 . $category->droplow.'" />';
1007         if ($this->forced) {
1008             $droplow = $category->droplow;
1009         }
1011         $categorycell = clone($this->categorycell);
1012         $categorycell->attributes['class']  .= ' ' . $levelclass;
1013         $categorycell->text = $droplow;
1014         return $categorycell;
1015     }
1017     public function get_item_cell($item, $params) {
1018         $itemcell = clone($this->itemcell);
1019         $itemcell->text = ' - ';
1020         return $itemcell;
1021     }
1024 class grade_edit_tree_column_keephigh extends grade_edit_tree_column_category {
1026     public function __construct($params) {
1027         parent::__construct('keephigh');
1028     }
1030     public function get_header_cell() {
1031         global $OUTPUT;
1032         $headercell = clone($this->headercell);
1033         $headercell->text = get_string('keephigh', 'grades').$OUTPUT->help_icon('keephigh', 'grades');
1034         return $headercell;
1035     }
1037     public function get_category_cell($category, $levelclass, $params) {
1038         $keephigh = '<label class="accesshide" for="keephigh_'.$category->id.'">'.get_string('keephigh', 'grades').'</label>';
1039         $keephigh .= '<input type="text" size="3" id="keephigh_'.$category->id.'" name="keephigh_'.$category->id.'" value="'.
1040                 $category->keephigh.'" />';
1042         if ($this->forced) {
1043             $keephigh = $category->keephigh;
1044         }
1046         $categorycell = clone($this->categorycell);
1047         $categorycell->attributes['class'] .= ' ' . $levelclass;
1048         $categorycell->text = $keephigh;
1049         return $categorycell;
1050     }
1052     public function get_item_cell($item, $params) {
1053         $itemcell = clone($this->itemcell);
1054         $itemcell->text = ' - ';
1055         return $itemcell;
1056     }
1059 class grade_edit_tree_column_multfactor extends grade_edit_tree_column {
1061     public function __construct($params) {
1062         parent::__construct();
1063     }
1065     public function get_header_cell() {
1066         global $OUTPUT;
1067         $headercell = clone($this->headercell);
1068         $headercell->text = get_string('multfactor', 'grades').$OUTPUT->help_icon('multfactor', 'grades');
1069         return $headercell;
1070     }
1072     public function get_category_cell($category, $levelclass, $params) {
1073         $categorycell = clone($this->categorycell);
1074         $categorycell->attributes['class'] .= ' ' . $levelclass;
1075         $categorycell->text = ' - ';
1076         return $categorycell;
1077     }
1079     public function get_item_cell($item, $params) {
1080         global $OUTPUT;
1082         $itemcell = clone($this->itemcell);
1083         if (!$item->is_raw_used()) {
1084             $itemcell->text = '&nbsp;';
1085             return $itemcell;
1086         }
1087         $multfactor = '<label class="accesshide" for="multfactor'.$item->id.'">'.
1088                 get_string('multfactorvalue', 'grades', $item->itemname).'</label>
1089                 <input type="text" size="4" id="multfactor'.$item->id.'" name="multfactor_'.$item->id.'" value="'.
1090                 grade_edit_tree::format_number($item->multfactor).'" />';
1092         $itemcell->text = $multfactor;
1093         return $itemcell;
1094     }
1096     public function is_hidden($mode='simple') {
1097         global $CFG;
1098         if ($mode == 'simple') {
1099             return strstr($CFG->grade_item_advanced, 'multfactor');
1100         } elseif ($mode == 'advanced') {
1101             return false;
1102         }
1103     }
1106 class grade_edit_tree_column_plusfactor extends grade_edit_tree_column {
1108     public function get_header_cell() {
1109         global $OUTPUT;
1110         $headercell = clone($this->headercell);
1111         $headercell->text = get_string('plusfactor', 'grades').$OUTPUT->help_icon('plusfactor', 'grades');
1112         return $headercell;
1113     }
1115     public function get_category_cell($category, $levelclass, $params) {
1116         $categorycell = clone($this->categorycell);
1117         $categorycell->attributes['class'] .= ' ' . $levelclass;
1118         $categorycell->text = ' - ';
1119         return $categorycell;
1121     }
1123     public function get_item_cell($item, $params) {
1124         global $OUTPUT;
1126         $itemcell = clone($this->itemcell);
1127         if (!$item->is_raw_used()) {
1128             $itemcell->text = '&nbsp;';
1129             return $itemcell;
1130         }
1132         $plusfactor = '<label class="accesshide" for="plusfactor_'. $item->id . '">'.
1133                 get_string('plusfactorvalue', 'grades', $item->itemname).'</label>
1134                 <input type="text" size="4" id="plusfactor_'.$item->id.'" name="plusfactor_'.$item->id.'" value="'.
1135                 grade_edit_tree::format_number($item->plusfactor).'" />';
1137         $itemcell->text = $plusfactor;
1138         return $itemcell;
1140     }
1142     public function is_hidden($mode='simple') {
1143         global $CFG;
1144         if ($mode == 'simple') {
1145             return strstr($CFG->grade_item_advanced, 'plusfactor');
1146         } elseif ($mode == 'advanced') {
1147             return false;
1148         }
1149     }
1152 class grade_edit_tree_column_actions extends grade_edit_tree_column {
1154     public function __construct($params) {
1155         parent::__construct();
1156     }
1158     public function get_header_cell() {
1159         $headercell = clone($this->headercell);
1160         $headercell->attributes['class'] .= ' actions';
1161         $headercell->text = get_string('actions');
1162         return $headercell;
1163     }
1165     public function get_category_cell($category, $levelclass, $params) {
1167         if (empty($params['actions'])) {
1168             throw new Exception('Array key (actions) missing from 3rd param of grade_edit_tree_column_actions::get_category_actions($category, $levelclass, $params)');
1169         }
1171         $categorycell = clone($this->categorycell);
1172         $categorycell->attributes['class'] .= ' ' . $levelclass;
1173         $categorycell->text = $params['actions'];
1174         return $categorycell;
1175     }
1177     public function get_item_cell($item, $params) {
1178         if (empty($params['actions'])) {
1179             throw new Exception('Array key (actions) missing from 2nd param of grade_edit_tree_column_actions::get_item_cell($item, $params)');
1180         }
1181         $itemcell = clone($this->itemcell);
1182         $itemcell->attributes['class'] .= ' actions';
1183         $itemcell->text = $params['actions'];
1184         return $itemcell;
1185     }
1187     public function is_hidden($mode='simple') {
1188         return false;
1189     }
1192 class grade_edit_tree_column_select extends grade_edit_tree_column {
1194     public function get_header_cell() {
1195         $headercell = clone($this->headercell);
1196         $headercell->attributes['class'] .= ' selection';
1197         $headercell->text = get_string('select');
1198         return $headercell;
1199     }
1201     public function get_category_cell($category, $levelclass, $params) {
1202         global $OUTPUT;
1203         if (empty($params['eid'])) {
1204             throw new Exception('Array key (eid) missing from 3rd param of grade_edit_tree_column_select::get_category_cell($category, $levelclass, $params)');
1205         }
1206         $selectall  = new action_link(new moodle_url('#'), get_string('all'), new component_action('click', 'togglecheckboxes', array('eid' => $params['eid'], 'check' => true)));
1207         $selectnone = new action_link(new moodle_url('#'), get_string('none'), new component_action('click', 'togglecheckboxes', array('eid' => $params['eid'], 'check' => false)));
1209         $categorycell = clone($this->categorycell);
1210         $categorycell->attributes['class'] .= ' last ' . $levelclass;
1211         $categorycell->style .= 'text-align: center;';
1212         $categorycell->text = $OUTPUT->render($selectall) . '<br />' . $OUTPUT->render($selectnone);
1213         return $categorycell;
1214     }
1216     public function get_item_cell($item, $params) {
1217         if (empty($params['itemtype']) || empty($params['eid'])) {
1218             error('Array key (itemtype or eid) missing from 2nd param of grade_edit_tree_column_select::get_item_cell($item, $params)');
1219         }
1220         $itemselect = '';
1222         if ($params['itemtype'] != 'course' && $params['itemtype'] != 'category') {
1223             $itemselect = '<label class="accesshide" for="select_'.$params['eid'].'">'.
1224                 get_string('select', 'grades', $item->itemname).'</label>
1225                 <input class="itemselect ignoredirty" type="checkbox" name="select_'.$params['eid'].'" id="select_'.$params['eid'].
1226                 '" onchange="toggleCategorySelector();"/>'; // TODO: convert to YUI handler
1227         }
1228         //html_writer::table() will wrap the item cell contents in a <TD> so don't do it here
1229         return $itemselect;
1230     }
1232     public function is_hidden($mode='simple') {
1233         return false;
1234     }