MDL-47146 core_grades: change action buttons on setup screen
[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 = '';
137         $moveaction = '';
139         if (!$is_category_item) {
140             $actions .= $this->gtree->get_edit_icon($element, $this->gpr);
141         }
143         if ($this->show_calculations) {
144             $actions .= $this->gtree->get_calculation_icon($element, $this->gpr);
145         }
147         if ($element['type'] == 'item' or ($element['type'] == 'category' and $element['depth'] > 1)) {
148             if ($this->element_deletable($element)) {
149                 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'delete', 'eid' => $eid, 'sesskey' => sesskey()));
150                 $actions .= $OUTPUT->action_icon($aurl, new pix_icon('t/delete', get_string('delete')));
151             }
153             $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'moveselect', 'eid' => $eid, 'sesskey' => sesskey()));
154             $moveaction .= $OUTPUT->action_icon($aurl, new pix_icon('t/move', get_string('move')));
155         }
157         $actions .= $this->gtree->get_hiding_icon($element, $this->gpr);
159         $actions .= $this->gtree->get_reset_icon($element, $this->gpr);
161         $returnrows = array();
162         $root = false;
164         $id = required_param('id', PARAM_INT);
166         /// prepare move target if needed
167         $last = '';
169         /// print the list items now
170         if ($this->moving == $eid) {
171             // do not diplay children
172             $cell = new html_table_cell();
173             $cell->colspan = 12;
174             $cell->attributes['class'] = $element['type'] . ' moving';
175             $cell->text = $object->name.' ('.get_string('move').')';
176             return array(new html_table_row(array($cell)));
177         }
179         if ($element['type'] == 'category') {
180             $level++;
181             $this->categories[$object->id] = $object->stripped_name;
182             $category = grade_category::fetch(array('id' => $object->id));
183             $item = $category->get_grade_item();
185             // Add aggregation coef input if not a course item and if parent category has correct aggregation type
186             $dimmed = ($item->is_hidden()) ? 'dimmed' : '';
188             // Before we print the category's row, we must find out how many rows will appear below it (for the filler cell's rowspan)
189             $aggregation_position = grade_get_setting($COURSE->id, 'aggregationposition', $CFG->grade_aggregationposition);
190             $category_total_data = null; // Used if aggregationposition is set to "last", so we can print it last
192             $html_children = array();
194             $row_count = 0;
196             foreach($element['children'] as $child_el) {
197                 $moveto = null;
199                 if (empty($child_el['object']->itemtype)) {
200                     $child_el['object']->itemtype = false;
201                 }
203                 if (($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') && !$totals) {
204                     continue;
205                 }
207                 $child_eid = $child_el['eid'];
208                 $first = '';
210                 if ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') {
211                     $first = array('first' => 1);
212                     $child_eid = $eid;
213                 }
215                 if ($this->moving && $this->moving != $child_eid) {
217                     $strmove     = get_string('move');
218                     $strmovehere = get_string('movehere');
219                     $actions = $moveaction = ''; // no action icons when moving
221                     $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'move', 'eid' => $this->moving, 'moveafter' => $child_eid, 'sesskey' => sesskey()));
222                     if ($first) {
223                         $aurl->params($first);
224                     }
226                     $cell = new html_table_cell();
227                     $cell->colspan = 12;
229                     $icon = new pix_icon('movehere', $strmovehere, null, array('class'=>'movetarget'));
230                     $cell->text = $OUTPUT->action_icon($aurl, $icon);
232                     $moveto = new html_table_row(array($cell));
233                 }
235                 $newparents = $parents;
236                 $newparents[] = $eid;
238                 $row_count++;
239                 $child_row_count = 0;
241                 // If moving, do not print course and category totals, but still print the moveto target box
242                 if ($this->moving && ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category')) {
243                     $html_children[] = $moveto;
244                 } elseif ($child_el['object']->itemtype == 'course' || $child_el['object']->itemtype == 'category') {
245                     // We don't build the item yet because we first need to know the deepest level of categories (for category/name colspans)
246                     $category_total_item = $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count);
247                     if (!$aggregation_position) {
248                         $html_children = array_merge($html_children, $category_total_item);
249                     }
250                 } else {
251                     $html_children = array_merge($html_children, $this->build_html_tree($child_el, $totals, $newparents, $level, $child_row_count));
252                     if (!empty($moveto)) {
253                         $html_children[] = $moveto;
254                     }
256                     if ($this->moving) {
257                         $row_count++;
258                     }
259                 }
261                 $row_count += $child_row_count;
263                 // If the child is a category, increment row_count by one more (for the extra coloured row)
264                 if ($child_el['type'] == 'category') {
265                     $row_count++;
266                 }
267             }
269             // Print category total at the end if aggregation position is "last" (1)
270             if (!empty($category_total_item) && $aggregation_position) {
271                 $html_children = array_merge($html_children, $category_total_item);
272             }
274             // Determine if we are at the root
275             if (isset($element['object']->grade_item) && $element['object']->grade_item->is_course_item()) {
276                 $root = true;
277             }
279             $levelclass = "level$level";
281             $courseclass = '';
282             if ($level == 1) {
283                 $courseclass = 'coursecategory';
284             }
286             $row = new html_table_row();
287             $row->attributes['class'] = $courseclass . ' category ' . $dimmed;
288             foreach ($rowclasses as $class) {
289                 $row->attributes['class'] .= ' ' . $class;
290             }
292             $headercell = new html_table_cell();
293             $headercell->header = true;
294             $headercell->scope = 'row';
295             $headercell->attributes['title'] = $object->stripped_name;
296             $headercell->attributes['class'] = 'cell rowspan ' . $levelclass;
297             $headercell->rowspan = $row_count + 1;
298             $row->cells[] = $headercell;
300             foreach ($this->columns as $column) {
301                 if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden()) {
302                     $row->cells[] = $column->get_category_cell($category, $levelclass, array('id' => $id,
303                         'name' => $object->name, 'level' => $level, 'actions' => $actions,
304                         'moveaction' => $moveaction, 'eid' => $eid));
305                 }
306             }
308             $returnrows[] = $row;
310             $returnrows = array_merge($returnrows, $html_children);
312             // Print a coloured row to show the end of the category across the table
313             $endcell = new html_table_cell();
314             $endcell->colspan = (19 - $level);
315             $endcell->attributes['class'] = 'colspan ' . $levelclass;
317             $returnrows[] = new html_table_row(array($endcell));
319         } else { // Dealing with a grade item
321             $item = grade_item::fetch(array('id' => $object->id));
322             $element['type'] = 'item';
323             $element['object'] = $item;
325             $categoryitemclass = '';
326             if ($item->itemtype == 'category') {
327                 $categoryitemclass = 'categoryitem';
328             }
330             $dimmed = ($item->is_hidden()) ? "dimmed_text" : "";
331             $gradeitemrow = new html_table_row();
332             $gradeitemrow->attributes['class'] = $categoryitemclass . ' item ' . $dimmed;
333             foreach ($rowclasses as $class) {
334                 $gradeitemrow->attributes['class'] .= ' ' . $class;
335             }
337             foreach ($this->columns as $column) {
338                 if (!($this->moving && $column->hide_when_moving) && !$column->is_hidden()) {
339                     $gradeitemrow->cells[] = $column->get_item_cell($item, array('id' => $id, 'name' => $object->name,
340                         'level' => $level, 'actions' => $actions, 'element' => $element, 'eid' => $eid,
341                         'moveaction' => $moveaction, 'itemtype' => $object->itemtype));
342                 }
343             }
345             $returnrows[] = $gradeitemrow;
346         }
348         return $returnrows;
350     }
352     /**
353      * Given a grade_item object, returns a labelled input if an aggregation coefficient (weight or extra credit) applies to it.
354      * @param grade_item $item
355      * @param string type "extra" or "weight": the type of the column hosting the weight input
356      * @return string HTML
357      */
358     static function get_weight_input($item, $type) {
359         global $OUTPUT;
361         if (!is_object($item) || get_class($item) !== 'grade_item') {
362             throw new Exception('grade_edit_tree::get_weight_input($item) was given a variable that is not of the required type (grade_item object)');
363             return false;
364         }
366         if ($item->is_course_item()) {
367             return '';
368         }
370         $parent_category = $item->get_parent_category();
371         $parent_category->apply_forced_settings();
372         $aggcoef = $item->get_coefstring();
374         if ((($aggcoef == 'aggregationcoefweight' || $aggcoef == 'aggregationcoef') && $type == 'weight') ||
375             ($aggcoef == 'aggregationcoefextraweight' && $type == 'extra')) {
376             return '<label class="accesshide" for="weight_'.$item->id.'">'.
377                 get_string('extracreditvalue', 'grades', $item->itemname).'</label>'.
378                 '<input type="text" size="6" id="weight_'.$item->id.'" name="weight_'.$item->id.'"
379                 value="'.grade_edit_tree::format_number($item->aggregationcoef).'" />';
380         } elseif (($aggcoef == 'aggregationcoefextrasum' || $aggcoef == 'aggregationcoefextraweightsum') && $type == 'extra') {
381             $checked = ($item->aggregationcoef > 0) ? 'checked="checked"' : '';
382             return '<input type="hidden" name="extracredit_'.$item->id.'" value="0" />
383                 <label class="accesshide" for="extracredit_'.$item->id.'">'.
384                 get_string('extracreditvalue', 'grades', $item->itemname).'</label>
385                 <input type="checkbox" id="extracredit_'.$item->id.'" name="extracredit_'.$item->id.'" value="1" '."$checked />\n";
386         } else if ($aggcoef == 'aggregationcoefextraweightsum' && $type == 'weight') {
387             $label = '';
388             if ($item->weightoverride && $parent_category->aggregation == GRADE_AGGREGATE_SUM) {
389                 $label = get_string('adjusted', 'grades');
390             }
392             $name = 'weight_' . $item->id;
393             $hiddenlabel = html_writer::tag(
394                 'label',
395                 get_string('weight', 'grades', $item->itemname),
396                 array(
397                     'class' => 'accesshide',
398                     'for' => $name
399                 )
400             );
402             $input = html_writer::empty_tag(
403                 'input',
404                 array(
405                     'type' =>   'text',
406                     'size' =>   6,
407                     'id' =>     $name,
408                     'name' =>   $name,
409                     'value' =>  grade_edit_tree::format_number($item->aggregationcoef2 * 100.0)
410                 )
411             );
413             return $hiddenlabel . $input . $label;
414         } else {
415             return '';
416         }
417     }
419     //Trims trailing zeros
420     //Used on the 'categories and items' page for grade items settings like aggregation co-efficient
421     //Grader report has its own decimal place settings so they are handled elsewhere
422     static function format_number($number) {
423         $formatted = rtrim(format_float($number, 4),'0');
424         if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point
425             $formatted .= '0';
426         }
427         return $formatted;
428     }
430     /**
431      * Given an element of the grade tree, returns whether it is deletable or not (only manual grade items are deletable)
432      *
433      * @param array $element
434      * @return bool
435      */
436     function element_deletable($element) {
437         global $COURSE;
439         if ($element['type'] != 'item') {
440             return true;
441         }
443         $grade_item = $element['object'];
445         if ($grade_item->itemtype != 'mod' or $grade_item->is_outcome_item() or $grade_item->gradetype == GRADE_TYPE_NONE) {
446             return true;
447         }
449         $modinfo = get_fast_modinfo($COURSE);
450         if (!isset($modinfo->instances[$grade_item->itemmodule][$grade_item->iteminstance])) {
451             // module does not exist
452             return true;
453         }
455         return false;
456     }
458     /**
459      * Given the grade tree and an array of element ids (e.g. c15, i42), and expecting the 'moveafter' URL param,
460      * moves the selected items to the requested location. Then redirects the user to the given $returnurl
461      *
462      * @param object $gtree The grade tree (a recursive representation of the grade categories and grade items)
463      * @param array $eids
464      * @param string $returnurl
465      */
466     function move_elements($eids, $returnurl) {
467         $moveafter = required_param('moveafter', PARAM_INT);
469         if (!is_array($eids)) {
470             $eids = array($eids);
471         }
473         if(!$after_el = $this->gtree->locate_element("cg$moveafter")) {
474             print_error('invalidelementid', '', $returnurl);
475         }
477         $after = $after_el['object'];
478         $parent = $after;
479         $sortorder = $after->get_sortorder();
481         foreach ($eids as $eid) {
482             if (!$element = $this->gtree->locate_element($eid)) {
483                 print_error('invalidelementid', '', $returnurl);
484             }
485             $object = $element['object'];
487             $object->set_parent($parent->id);
488             $object->move_after_sortorder($sortorder);
489             $sortorder++;
490         }
492         redirect($returnurl, '', 0);
493     }
495     /**
496      * Recurses through the entire grade tree to find and return the maximum depth of the tree.
497      * This should be run only once from the root element (course category), and is used for the
498      * indentation of the Name column's cells (colspan)
499      *
500      * @param array $element An array of values representing a grade tree's element (all grade items in this case)
501      * @param int $level The level of the current recursion
502      * @param int $deepest_level A value passed to each subsequent level of recursion and incremented if $level > $deepest_level
503      * @return int Deepest level
504      */
505     function get_deepest_level($element, $level=0, $deepest_level=1) {
506         $object = $element['object'];
508         $level++;
509         $coefstring = $element['object']->get_coefstring();
510         if ($element['type'] == 'category') {
511             if ($coefstring == 'aggregationcoefweight' || $coefstring == 'aggregationcoefextraweightsum') {
512                 $this->uses_weight = true;
513             }
514             if ($coefstring ==  'aggregationcoefextraweight' || $coefstring == 'aggregationcoefextraweightsum' || $coefstring == 'aggregationcoefextrasum') {
515                 $this->uses_extra_credit = true;
516             }
518             foreach($element['children'] as $child_el) {
519                 if ($level > $deepest_level) {
520                     $deepest_level = $level;
521                 }
522                 $deepest_level = $this->get_deepest_level($child_el, $level, $deepest_level);
523             }
524         }
526         return $deepest_level;
527     }
530 abstract class grade_edit_tree_column {
531     public $forced;
532     public $hidden;
533     public $forced_hidden;
534     public $advanced_hidden;
535     public $hide_when_moving = true;
536     /**
537      * html_table_cell object used as a template for header cells in all categories.
538      * It must be cloned before being used.
539      * @var html_table_cell $headercell
540      */
541     public $headercell;
542     /**
543      * html_table_cell object used as a template for category cells in all categories.
544      * It must be cloned before being used.
545      * @var html_table_cell $categorycell
546      */
547     public $categorycell;
548     /**
549      * html_table_cell object used as a template for item cells in all categories.
550      * It must be cloned before being used.
551      * @var html_table_cell $itemcell
552      */
553     public $itemcell;
555     public static function factory($name, $params=array()) {
556         $class_name = "grade_edit_tree_column_$name";
557         if (class_exists($class_name)) {
558             return new $class_name($params);
559         }
560     }
562     public abstract function get_header_cell();
564     public abstract function get_category_cell($category, $levelclass, $params);
566     public abstract function get_item_cell($item, $params);
568     public abstract function is_hidden($mode='simple');
570     public function __construct() {
571         $this->headercell = new html_table_cell();
572         $this->headercell->header = true;
573         $this->headercell->style = 'whitespace: normal;';
574         $this->headercell->attributes['class'] = 'header';
576         $this->categorycell = new html_table_cell();
577         $this->categorycell->attributes['class']  = 'cell';
579         $this->itemcell = new html_table_cell();
580         $this->itemcell->attributes['class'] = 'cell';
581     }
584 abstract class grade_edit_tree_column_category extends grade_edit_tree_column {
586     public $forced;
587     public $advanced;
589     public function __construct($name) {
590         global $CFG;
591         $this->forced = (int)$CFG->{"grade_$name"."_flag"} & 1;
592         $this->advanced = (int)$CFG->{"grade_$name"."_flag"} & 2;
593         parent::__construct();
594     }
596     public function is_hidden($mode='simple') {
597         global $CFG;
598         if ($mode == 'simple') {
599             return $this->advanced;
600         } elseif ($mode == 'advanced') {
601             if ($this->forced && $CFG->grade_hideforcedsettings) {
602                 return true;
603             } else {
604                 return false;
605             }
606         }
607     }
610 class grade_edit_tree_column_name extends grade_edit_tree_column {
611     public $forced = false;
612     public $hidden = false;
613     public $forced_hidden = false;
614     public $advanced_hidden = false;
615     public $deepest_level = 1;
616     public $hide_when_moving = false;
618     public function __construct($params) {
619         if (empty($params['deepest_level'])) {
620             throw new Exception('Tried to instantiate a grade_edit_tree_column_name object without the "deepest_level" param!');
621         }
623         $this->deepest_level = $params['deepest_level'];
624         parent::__construct();
625     }
627     public function get_header_cell() {
628         $headercell = clone($this->headercell);
629         $headercell->attributes['class'] .= ' name';
630         $headercell->colspan = $this->deepest_level + 1;
631         $headercell->text = get_string('name');
632         return $headercell;
633     }
635     public function get_category_cell($category, $levelclass, $params) {
636         global $OUTPUT;
637         if (empty($params['name']) || empty($params['level'])) {
638             throw new Exception('Array key (name or level) missing from 3rd param of grade_edit_tree_column_name::get_category_cell($category, $levelclass, $params)');
639         }
640         $moveaction = isset($params['moveaction']) ? $params['moveaction'] : '';
641         $categorycell = clone($this->categorycell);
642         $categorycell->attributes['class'] .= ' name ' . $levelclass;
643         $categorycell->colspan = ($this->deepest_level +1) - $params['level'];
644         $categorycell->text = $OUTPUT->heading($moveaction . $params['name'], 4);
645         return $categorycell;
646     }
648     public function get_item_cell($item, $params) {
649         global $CFG;
651         if (empty($params['element']) || empty($params['name']) || empty($params['level'])) {
652             throw new Exception('Array key (name, level or element) missing from 2nd param of grade_edit_tree_column_name::get_item_cell($item, $params)');
653         }
655         $name = $params['name'];
656         $moveaction = isset($params['moveaction']) ? $params['moveaction'] : '';
658         $itemcell = clone($this->itemcell);
659         $itemcell->attributes['class'] .= ' name';
660         $itemcell->colspan = ($this->deepest_level + 1) - $params['level'];
661         $itemcell->text = $moveaction . $name;
662         return $itemcell;
663     }
665     public function is_hidden($mode='simple') {
666         return false;
667     }
670 class grade_edit_tree_column_aggregation extends grade_edit_tree_column_category {
672     public function __construct($params) {
673         parent::__construct('aggregation');
674     }
676     public function get_header_cell() {
677         global $OUTPUT;
678         $headercell = clone($this->headercell);
679         $headercell->text = get_string('aggregation', 'grades').$OUTPUT->help_icon('aggregation', 'grades');
680         return $headercell;
681     }
683     public function get_category_cell($category, $levelclass, $params) {
684         global $CFG, $OUTPUT;
685         if (empty($params['id'])) {
686             throw new Exception('Array key (id) missing from 3rd param of grade_edit_tree_column_aggregation::get_category_cell($category, $levelclass, $params)');
687         }
689         $options = grade_helper::get_aggregation_strings();
691         $visible = explode(',', $CFG->grade_aggregations_visible);
692         foreach ($options as $constant => $string) {
693             if (!in_array($constant, $visible) && $constant != $category->aggregation) {
694                 unset($options[$constant]);
695             }
696         }
698         if ($this->forced) {
699             $aggregation = $options[$category->aggregation];
700         } else {
701             $attributes = array();
702             $attributes['id'] = 'aggregation_'.$category->id;
703             $attributes['class'] = 'ignoredirty';
704             $aggregation = html_writer::label(get_string('aggregation', 'grades'), 'aggregation_'.$category->id, false, array('class' => 'accesshide'));
705             $aggregation .= html_writer::select($options, 'aggregation_'.$category->id, $category->aggregation, null, $attributes);
706             $action = new component_action('change', 'update_category_aggregation', array('courseid' => $params['id'], 'category' => $category->id, 'sesskey' => sesskey()));
707             $OUTPUT->add_action_handler($action, 'aggregation_'.$category->id);
708         }
710         $categorycell = clone($this->categorycell);
711         $categorycell->attributes['class'] .= ' ' . $levelclass;
712         $categorycell->text = $aggregation;
713         return $categorycell;
715     }
717     public function get_item_cell($item, $params) {
718         $itemcell = clone($this->itemcell);
719         $itemcell->text = ' - ';
720         return $itemcell;
721     }
724 class grade_edit_tree_column_extracredit extends grade_edit_tree_column {
726     public function get_header_cell() {
727         global $OUTPUT;
728         $headercell = clone($this->headercell);
729         $headercell->text = get_string('aggregationcoefextra', 'grades').$OUTPUT->help_icon('aggregationcoefextra', 'grades');
730         return $headercell;
731     }
733     public function get_category_cell($category, $levelclass, $params) {
734         $item = $category->get_grade_item();
735         $categorycell = clone($this->categorycell);
736         $categorycell->attributes['class'] .= ' ' . $levelclass;
737         $categorycell->text = grade_edit_tree::get_weight_input($item, 'extra');
738         return $categorycell;
739     }
741     public function get_item_cell($item, $params) {
742         if (empty($params['element'])) {
743             throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)');
744         }
746         $itemcell = clone($this->itemcell);
747         $itemcell->text = '&nbsp;';
749         if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))) {
750             $itemcell->text = grade_edit_tree::get_weight_input($item, 'extra');
751         }
753         return $itemcell;
754     }
756     public function is_hidden($mode='simple') {
757         global $CFG;
758         if ($mode == 'simple') {
759             return strstr($CFG->grade_item_advanced, 'aggregationcoef');
760         } elseif ($mode == 'advanced') {
761             return false;
762         }
763     }
766 class grade_edit_tree_column_weight extends grade_edit_tree_column {
768     public function get_header_cell() {
769         global $OUTPUT;
770         $headercell = clone($this->headercell);
771         $headercell->text = get_string('weights', 'grades').$OUTPUT->help_icon('aggregationcoefweight', 'grades');
772         return $headercell;
773     }
775     public function get_category_cell($category, $levelclass, $params) {
777         $item = $category->get_grade_item();
778         $categorycell = clone($this->categorycell);
779         $categorycell->attributes['class']  .= ' ' . $levelclass;
780         $categorycell->text = grade_edit_tree::get_weight_input($item, 'weight');
781         return $categorycell;
782     }
784     public function get_item_cell($item, $params) {
785         if (empty($params['element'])) {
786             throw new Exception('Array key (element) missing from 2nd param of grade_edit_tree_column_weightorextracredit::get_item_cell($item, $params)');
787         }
788         $itemcell = clone($this->itemcell);
789         $itemcell->text = '&nbsp;';
791         if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))) {
792             $itemcell->text = grade_edit_tree::get_weight_input($item, 'weight');
793         }
795         return $itemcell;
796     }
798     public function is_hidden($mode='simple') {
799         global $CFG;
800         if ($mode == 'simple') {
801             return strstr($CFG->grade_item_advanced, 'aggregationcoef');
802         } elseif ($mode == 'advanced') {
803             return false;
804         }
805     }
808 class grade_edit_tree_column_range extends grade_edit_tree_column {
810     public function get_header_cell() {
811         $headercell = clone($this->headercell);
812         $headercell->text = get_string('maxgrade', 'grades');
813         return $headercell;
814     }
816     public function get_category_cell($category, $levelclass, $params) {
817         $categorycell = clone($this->categorycell);
818         $categorycell->attributes['class'] .= ' range ' . $levelclass;
819         $categorycell->text = ' - ';
820         return $categorycell;
821     }
823     public function get_item_cell($item, $params) {
824         global $DB, $OUTPUT;
826         // If the parent aggregation is Natural, we should show the number, even for scales, as that value is used...
827         // ...in the computation. For text grades, the grademax is not used, so we can still show the no value string.
828         $parent_cat = $item->get_parent_category();
829         if ($item->gradetype == GRADE_TYPE_TEXT) {
830             $grademax = ' - ';
831         } else if ($parent_cat->aggregation == GRADE_AGGREGATE_SUM) {
832             $grademax = format_float($item->grademax, $item->get_decimals());
833         } elseif ($item->gradetype == GRADE_TYPE_SCALE) {
834             $scale = $DB->get_record('scale', array('id' => $item->scaleid));
835             $scale_items = null;
836             if (empty($scale)) { //if the item is using a scale that's been removed
837                 $scale_items = array();
838             } else {
839                 $scale_items = explode(',', $scale->scale);
840             }
841             $grademax = end($scale_items) . ' (' . count($scale_items) . ')';
842         } elseif ($item->is_external_item()) {
843             $grademax = format_float($item->grademax, $item->get_decimals());
844         } else {
845             $grademax = '<label class="accesshide" for="grademax'.$item->id.'">'.get_string('grademax', 'grades').'</label>
846                 <input type="text" size="6" id="grademax'.$item->id.'" name="grademax_'.$item->id.'" value="'.
847                 format_float($item->grademax, $item->get_decimals()).'" />';
848         }
850         $itemcell = clone($this->itemcell);
851         $itemcell->text = $grademax;
852         return $itemcell;
853     }
855     public function is_hidden($mode='simple') {
856         global $CFG;
857         if ($mode == 'simple') {
858             return strstr($CFG->grade_item_advanced, 'grademax');
859         } elseif ($mode == 'advanced') {
860             return false;
861         }
862     }
865 class grade_edit_tree_column_aggregateonlygraded extends grade_edit_tree_column_category {
867     public function __construct($params) {
868         parent::__construct('aggregateonlygraded');
869     }
871     public function get_header_cell() {
872         global $OUTPUT;
873         $headercell = clone($this->headercell);
874         $headercell->style .= 'width: 40px;';
875         $headercell->text = get_string('aggregateonlygraded', 'grades')
876                 . $OUTPUT->help_icon('aggregateonlygraded', 'grades');
877         return $headercell;
878     }
880     public function get_category_cell($category, $levelclass, $params) {
881         $onlygradedcheck = ($category->aggregateonlygraded == 1) ? 'checked="checked"' : '';
882         $hidden = '<input type="hidden" name="aggregateonlygraded_'.$category->id.'" value="0" />';
883         $aggregateonlygraded = '<label class="accesshide" for="aggregateonlygraded_'.$category->id.'">'.
884                 get_string('aggregateonlygraded', 'grades').'</label>
885                 <input type="checkbox" id="aggregateonlygraded_'.$category->id.'" name="aggregateonlygraded_'.
886                 $category->id.'" value="1" '.$onlygradedcheck . ' />';
888         if ($this->forced) {
889             $aggregateonlygraded = ($category->aggregateonlygraded) ? get_string('yes') : get_string('no');
890         }
892         $categorycell = clone($this->categorycell);
893         $categorycell->attributes['class'] .= ' ' . $levelclass;
894         $categorycell->text = $hidden.$aggregateonlygraded;
895         return $categorycell;
896     }
898     public function get_item_cell($item, $params) {
899         $itemcell = clone($this->itemcell);
900         $itemcell->text = ' - ';
901         return $itemcell;
902     }
905 class grade_edit_tree_column_aggregatesubcats extends grade_edit_tree_column_category {
907     public function __construct($params) {
908         parent::__construct('aggregatesubcats');
909     }
911     public function get_header_cell() {
912         global $OUTPUT;
913         $headercell = clone($this->headercell);
914         $headercell->style .= 'width: 40px;';
915         $headercell->text = get_string('aggregatesubcats', 'grades')
916               .$OUTPUT->help_icon('aggregatesubcats', 'grades');
917         return $headercell;
918     }
920     public function get_category_cell($category, $levelclass, $params) {
921         $subcatscheck = ($category->aggregatesubcats == 1) ? 'checked="checked"' : '';
922         $hidden = '<input type="hidden" name="aggregatesubcats_'.$category->id.'" value="0" />';
923         $aggregatesubcats = '<label class="accesshide" for="aggregatesubcats_'.$category->id.'">'.
924                 get_string('aggregatesubcats', 'grades').'</label>
925                 <input type="checkbox" id="aggregatesubcats_'.$category->id.'" name="aggregatesubcats_'.$category->id.
926                 '" value="1" ' . $subcatscheck.' />';
928         if ($this->forced) {
929             $aggregatesubcats = ($category->aggregatesubcats) ? get_string('yes') : get_string('no');
930         }
932         $categorycell = clone($this->categorycell);
933         $categorycell->attributes['class'] .= ' ' . $levelclass;
934         $categorycell->text = $hidden.$aggregatesubcats;
935         return $categorycell;
937     }
939     public function get_item_cell($item, $params) {
940         $itemcell = clone($this->itemcell);
941         $itemcell->text = ' - ';
942         return $itemcell;
943     }
946 class grade_edit_tree_column_aggregateoutcomes extends grade_edit_tree_column_category {
948     public function __construct($params) {
949         parent::__construct('aggregateoutcomes');
950     }
952     public function get_header_cell() {
953         global $OUTPUT;
954         $headercell = clone($this->headercell);
955         $headercell->style .= 'width: 40px;';
956         $headercell->text = get_string('aggregateoutcomes', 'grades')
957               .$OUTPUT->help_icon('aggregateoutcomes', 'grades');
958         return $headercell;
959     }
961     public function get_category_cell($category, $levelclass, $params) {
962         $outcomescheck = ($category->aggregateoutcomes == 1) ? 'checked="checked"' : '';
963         $hidden = '<input type="hidden" name="aggregateoutcomes_'.$category->id.'" value="0" />';
964         $aggregateoutcomes = '<label class="accesshide" for="aggregateoutcomes_'.$category->id.'">'.
965                 get_string('aggregateoutcomes', 'grades').'</label>
966                 <input type="checkbox" id="aggregateoutcomes_'.$category->id.'" name="aggregateoutcomes_'.$category->id.
967                 '" value="1" ' . $outcomescheck.' />';
969         if ($this->forced) {
970             $aggregateoutcomes = ($category->aggregateoutcomes) ? get_string('yes') : get_string('no');
971         }
973         $categorycell = clone($this->categorycell);
974         $categorycell->attributes['class'] .= ' ' . $levelclass;
975         $categorycell->text = $hidden.$aggregateoutcomes;
976         return $categorycell;
977     }
979     public function get_item_cell($item, $params) {
980         $itemcell = clone($this->itemcell);
981         $itemcell->text = ' - ';
982         return $itemcell;
983     }
985     public function is_hidden($mode='simple') {
986         global $CFG;
987         if ($CFG->enableoutcomes) {
988             return parent::is_hidden($mode);
989         } else {
990             return true;
991         }
992     }
995 class grade_edit_tree_column_droplow extends grade_edit_tree_column_category {
997     public function __construct($params) {
998         parent::__construct('droplow');
999     }
1001     public function get_header_cell() {
1002         global $OUTPUT;
1003         $headercell = clone($this->headercell);
1004         $headercell->text = get_string('droplow', 'grades').$OUTPUT->help_icon('droplow', 'grades');
1005         return $headercell;
1006     }
1008     public function get_category_cell($category, $levelclass, $params) {
1009         $droplow = '<label class="accesshide" for="droplow_' . $category->id.'">' . get_string('droplowestvalue', 'grades') . '</label>';
1010         $droplow .= '<input type="text" size="3" id="droplow_' . $category->id . '" name="droplow_' . $category->id . '" value="'
1011                 . $category->droplow.'" />';
1013         if ($this->forced) {
1014             $droplow = $category->droplow;
1015         }
1017         $categorycell = clone($this->categorycell);
1018         $categorycell->attributes['class']  .= ' ' . $levelclass;
1019         $categorycell->text = $droplow;
1020         return $categorycell;
1021     }
1023     public function get_item_cell($item, $params) {
1024         $itemcell = clone($this->itemcell);
1025         $itemcell->text = ' - ';
1026         return $itemcell;
1027     }
1030 class grade_edit_tree_column_keephigh extends grade_edit_tree_column_category {
1032     public function __construct($params) {
1033         parent::__construct('keephigh');
1034     }
1036     public function get_header_cell() {
1037         global $OUTPUT;
1038         $headercell = clone($this->headercell);
1039         $headercell->text = get_string('keephigh', 'grades').$OUTPUT->help_icon('keephigh', 'grades');
1040         return $headercell;
1041     }
1043     public function get_category_cell($category, $levelclass, $params) {
1044         $keephigh = '<label class="accesshide" for="keephigh_'.$category->id.'">'.get_string('keephigh', 'grades').'</label>';
1045         $keephigh .= '<input type="text" size="3" id="keephigh_'.$category->id.'" name="keephigh_'.$category->id.'" value="'.
1046                 $category->keephigh.'" />';
1048         if ($this->forced) {
1049             $keephigh = $category->keephigh;
1050         }
1052         $categorycell = clone($this->categorycell);
1053         $categorycell->attributes['class'] .= ' ' . $levelclass;
1054         $categorycell->text = $keephigh;
1055         return $categorycell;
1056     }
1058     public function get_item_cell($item, $params) {
1059         $itemcell = clone($this->itemcell);
1060         $itemcell->text = ' - ';
1061         return $itemcell;
1062     }
1065 class grade_edit_tree_column_multfactor extends grade_edit_tree_column {
1067     public function __construct($params) {
1068         parent::__construct();
1069     }
1071     public function get_header_cell() {
1072         global $OUTPUT;
1073         $headercell = clone($this->headercell);
1074         $headercell->text = get_string('multfactor', 'grades').$OUTPUT->help_icon('multfactor', 'grades');
1075         return $headercell;
1076     }
1078     public function get_category_cell($category, $levelclass, $params) {
1079         $categorycell = clone($this->categorycell);
1080         $categorycell->attributes['class'] .= ' ' . $levelclass;
1081         $categorycell->text = ' - ';
1082         return $categorycell;
1083     }
1085     public function get_item_cell($item, $params) {
1086         global $OUTPUT;
1088         $itemcell = clone($this->itemcell);
1089         if (!$item->is_raw_used()) {
1090             $itemcell->text = '&nbsp;';
1091             return $itemcell;
1092         }
1093         $multfactor = '<label class="accesshide" for="multfactor'.$item->id.'">'.
1094                 get_string('multfactorvalue', 'grades', $item->itemname).'</label>
1095                 <input type="text" size="4" id="multfactor'.$item->id.'" name="multfactor_'.$item->id.'" value="'.
1096                 grade_edit_tree::format_number($item->multfactor).'" />';
1098         $itemcell->text = $multfactor;
1099         return $itemcell;
1100     }
1102     public function is_hidden($mode='simple') {
1103         global $CFG;
1104         if ($mode == 'simple') {
1105             return strstr($CFG->grade_item_advanced, 'multfactor');
1106         } elseif ($mode == 'advanced') {
1107             return false;
1108         }
1109     }
1112 class grade_edit_tree_column_plusfactor extends grade_edit_tree_column {
1114     public function get_header_cell() {
1115         global $OUTPUT;
1116         $headercell = clone($this->headercell);
1117         $headercell->text = get_string('plusfactor', 'grades').$OUTPUT->help_icon('plusfactor', 'grades');
1118         return $headercell;
1119     }
1121     public function get_category_cell($category, $levelclass, $params) {
1122         $categorycell = clone($this->categorycell);
1123         $categorycell->attributes['class'] .= ' ' . $levelclass;
1124         $categorycell->text = ' - ';
1125         return $categorycell;
1127     }
1129     public function get_item_cell($item, $params) {
1130         global $OUTPUT;
1132         $itemcell = clone($this->itemcell);
1133         if (!$item->is_raw_used()) {
1134             $itemcell->text = '&nbsp;';
1135             return $itemcell;
1136         }
1138         $plusfactor = '<label class="accesshide" for="plusfactor_'. $item->id . '">'.
1139                 get_string('plusfactorvalue', 'grades', $item->itemname).'</label>
1140                 <input type="text" size="4" id="plusfactor_'.$item->id.'" name="plusfactor_'.$item->id.'" value="'.
1141                 grade_edit_tree::format_number($item->plusfactor).'" />';
1143         $itemcell->text = $plusfactor;
1144         return $itemcell;
1146     }
1148     public function is_hidden($mode='simple') {
1149         global $CFG;
1150         if ($mode == 'simple') {
1151             return strstr($CFG->grade_item_advanced, 'plusfactor');
1152         } elseif ($mode == 'advanced') {
1153             return false;
1154         }
1155     }
1158 class grade_edit_tree_column_actions extends grade_edit_tree_column {
1160     public function __construct($params) {
1161         parent::__construct();
1162     }
1164     public function get_header_cell() {
1165         $headercell = clone($this->headercell);
1166         $headercell->attributes['class'] .= ' actions';
1167         $headercell->text = get_string('actions');
1168         return $headercell;
1169     }
1171     public function get_category_cell($category, $levelclass, $params) {
1173         if (empty($params['actions'])) {
1174             throw new Exception('Array key (actions) missing from 3rd param of grade_edit_tree_column_actions::get_category_actions($category, $levelclass, $params)');
1175         }
1177         $categorycell = clone($this->categorycell);
1178         $categorycell->attributes['class'] .= ' ' . $levelclass;
1179         $categorycell->text = $params['actions'];
1180         return $categorycell;
1181     }
1183     public function get_item_cell($item, $params) {
1184         if (empty($params['actions'])) {
1185             throw new Exception('Array key (actions) missing from 2nd param of grade_edit_tree_column_actions::get_item_cell($item, $params)');
1186         }
1187         $itemcell = clone($this->itemcell);
1188         $itemcell->attributes['class'] .= ' actions';
1189         $itemcell->text = $params['actions'];
1190         return $itemcell;
1191     }
1193     public function is_hidden($mode='simple') {
1194         return false;
1195     }
1198 class grade_edit_tree_column_select extends grade_edit_tree_column {
1200     public function get_header_cell() {
1201         $headercell = clone($this->headercell);
1202         $headercell->attributes['class'] .= ' selection';
1203         $headercell->text = get_string('select');
1204         return $headercell;
1205     }
1207     public function get_category_cell($category, $levelclass, $params) {
1208         global $OUTPUT;
1209         if (empty($params['eid'])) {
1210             throw new Exception('Array key (eid) missing from 3rd param of grade_edit_tree_column_select::get_category_cell($category, $levelclass, $params)');
1211         }
1212         $selectall  = new action_link(new moodle_url('#'), get_string('all'), new component_action('click', 'togglecheckboxes', array('eid' => $params['eid'], 'check' => true)));
1213         $selectnone = new action_link(new moodle_url('#'), get_string('none'), new component_action('click', 'togglecheckboxes', array('eid' => $params['eid'], 'check' => false)));
1215         $categorycell = clone($this->categorycell);
1216         $categorycell->attributes['class'] .= ' last ' . $levelclass;
1217         $categorycell->style .= 'text-align: center;';
1218         $categorycell->text = $OUTPUT->render($selectall) . '<br />' . $OUTPUT->render($selectnone);
1219         return $categorycell;
1220     }
1222     public function get_item_cell($item, $params) {
1223         if (empty($params['itemtype']) || empty($params['eid'])) {
1224             error('Array key (itemtype or eid) missing from 2nd param of grade_edit_tree_column_select::get_item_cell($item, $params)');
1225         }
1226         $itemselect = '';
1228         if ($params['itemtype'] != 'course' && $params['itemtype'] != 'category') {
1229             $itemselect = '<label class="accesshide" for="select_'.$params['eid'].'">'.
1230                 get_string('select', 'grades', $item->itemname).'</label>
1231                 <input class="itemselect ignoredirty" type="checkbox" name="select_'.$params['eid'].'" id="select_'.$params['eid'].
1232                 '" onchange="toggleCategorySelector();"/>'; // TODO: convert to YUI handler
1233         }
1234         //html_writer::table() will wrap the item cell contents in a <TD> so don't do it here
1235         return $itemselect;
1236     }
1238     public function is_hidden($mode='simple') {
1239         return false;
1240     }