1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Contains renderer used for displaying rubric
19 *
20 * @package    gradingform_rubric
21 * @copyright  2011 Marina Glancy
22 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27/**
28 * Grading method plugin renderer
29 *
30 * @package    gradingform_rubric
31 * @copyright  2011 Marina Glancy
32 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33 */
34class gradingform_rubric_renderer extends plugin_renderer_base {
35
36    /**
37     * This function returns html code for displaying criterion. Depending on $mode it may be the
38     * code to edit rubric, to preview the rubric, to evaluate somebody or to review the evaluation.
39     *
40     * This function may be called from display_rubric() to display the whole rubric, or it can be
41     * called by itself to return a template used by JavaScript to add new empty criteria to the
42     * rubric being designed.
43     * In this case it will use macros like {NAME}, {LEVELS}, {CRITERION-id}, etc.
44     *
45     * When overriding this function it is very important to remember that all elements of html
46     * form (in edit or evaluate mode) must have the name $elementname.
47     *
48     * Also JavaScript relies on the class names of elements and when developer changes them
49     * script might stop working.
50     *
51     * @param int $mode rubric display mode, see {@link gradingform_rubric_controller}
52     * @param array $options display options for this rubric, defaults are: {@link gradingform_rubric_controller::get_default_options()}
53     * @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
54     * @param array|null $criterion criterion data
55     * @param string $levelsstr evaluated templates for this criterion levels
56     * @param array|null $value (only in view mode) teacher's feedback on this criterion
57     * @return string
58     */
59    public function criterion_template($mode, $options, $elementname = '{NAME}', $criterion = null, $levelsstr = '{LEVELS}', $value = null) {
60        // TODO MDL-31235 description format, remark format
61        if ($criterion === null || !is_array($criterion) || !array_key_exists('id', $criterion)) {
62            $criterion = array('id' => '{CRITERION-id}', 'description' => '{CRITERION-description}', 'sortorder' => '{CRITERION-sortorder}', 'class' => '{CRITERION-class}');
63        } else {
64            foreach (array('sortorder', 'description', 'class') as $key) {
65                // set missing array elements to empty strings to avoid warnings
66                if (!array_key_exists($key, $criterion)) {
67                    $criterion[$key] = '';
68                }
69            }
70        }
71        $criteriontemplate = html_writer::start_tag('tr', array('class' => 'criterion'. $criterion['class'], 'id' => '{NAME}-criteria-{CRITERION-id}'));
72        if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
73            $criteriontemplate .= html_writer::start_tag('td', array('class' => 'controls'));
74            foreach (array('moveup', 'delete', 'movedown', 'duplicate') as $key) {
75                $value = get_string('criterion'.$key, 'gradingform_rubric');
76                $button = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[criteria][{CRITERION-id}]['.$key.']',
77                    'id' => '{NAME}-criteria-{CRITERION-id}-'.$key, 'value' => $value));
78                $criteriontemplate .= html_writer::tag('div', $button, array('class' => $key));
79            }
80            $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
81                                                                        'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]',
82                                                                        'value' => $criterion['sortorder']));
83            $criteriontemplate .= html_writer::end_tag('td'); // .controls
84
85            // Criterion description text area.
86            $descriptiontextareaparams = array(
87                'name' => '{NAME}[criteria][{CRITERION-id}][description]',
88                'id' => '{NAME}-criteria-{CRITERION-id}-description',
89                'aria-label' => get_string('criterion', 'gradingform_rubric', ''),
90                'cols' => '10', 'rows' => '5'
91            );
92            $description = html_writer::tag('textarea', s($criterion['description']), $descriptiontextareaparams);
93        } else {
94            if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
95                $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
96                $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][description]', 'value' => $criterion['description']));
97            }
98            $description = s($criterion['description']);
99        }
100        $descriptionclass = 'description';
101        if (isset($criterion['error_description'])) {
102            $descriptionclass .= ' error';
103        }
104
105        // Description cell params.
106        $descriptiontdparams = array(
107            'class' => $descriptionclass,
108            'id' => '{NAME}-criteria-{CRITERION-id}-description-cell'
109        );
110        if ($mode != gradingform_rubric_controller::DISPLAY_EDIT_FULL &&
111            $mode != gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
112            // Set description's cell as tab-focusable.
113            $descriptiontdparams['tabindex'] = '0';
114            // Set label for the criterion cell.
115            $descriptiontdparams['aria-label'] = get_string('criterion', 'gradingform_rubric', s($criterion['description']));
116        }
117
118        // Description cell.
119        $criteriontemplate .= html_writer::tag('td', $description, $descriptiontdparams);
120
121        // Levels table.
122        $levelsrowparams = array('id' => '{NAME}-criteria-{CRITERION-id}-levels');
123        if ($mode != gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
124            $levelsrowparams['role'] = 'radiogroup';
125        }
126        $levelsrow = html_writer::tag('tr', $levelsstr, $levelsrowparams);
127
128        $levelstableparams = array(
129            'id' => '{NAME}-criteria-{CRITERION-id}-levels-table',
130            'aria-label' => get_string('levelsgroup', 'gradingform_rubric')
131        );
132        $levelsstrtable = html_writer::tag('table', $levelsrow, $levelstableparams);
133        $levelsclass = 'levels';
134        if (isset($criterion['error_levels'])) {
135            $levelsclass .= ' error';
136        }
137        $criteriontemplate .= html_writer::tag('td', $levelsstrtable, array('class' => $levelsclass));
138        if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
139            $value = get_string('criterionaddlevel', 'gradingform_rubric');
140            $button = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][addlevel]',
141                'id' => '{NAME}-criteria-{CRITERION-id}-levels-addlevel', 'value' => $value, 'class' => 'btn btn-secondary'));
142            $criteriontemplate .= html_writer::tag('td', $button, array('class' => 'addlevel'));
143        }
144        $displayremark = ($options['enableremarks'] && ($mode != gradingform_rubric_controller::DISPLAY_VIEW || $options['showremarksstudent']));
145        if ($displayremark) {
146            $currentremark = '';
147            if (isset($value['remark'])) {
148                $currentremark = $value['remark'];
149            }
150
151            // Label for criterion remark.
152            $remarkinfo = new stdClass();
153            $remarkinfo->description = s($criterion['description']);
154            $remarkinfo->remark = $currentremark;
155            $remarklabeltext = get_string('criterionremark', 'gradingform_rubric', $remarkinfo);
156
157            if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
158                // HTML parameters for remarks text area.
159                $remarkparams = array(
160                    'name' => '{NAME}[criteria][{CRITERION-id}][remark]',
161                    'id' => '{NAME}-criteria-{CRITERION-id}-remark',
162                    'cols' => '10', 'rows' => '5',
163                    'aria-label' => $remarklabeltext
164                );
165                $input = html_writer::tag('textarea', s($currentremark), $remarkparams);
166                $criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
167            } else if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN) {
168                $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
169            }else if ($mode == gradingform_rubric_controller::DISPLAY_REVIEW || $mode == gradingform_rubric_controller::DISPLAY_VIEW) {
170                // HTML parameters for remarks cell.
171                $remarkparams = array(
172                    'class' => 'remark',
173                    'tabindex' => '0',
174                    'id' => '{NAME}-criteria-{CRITERION-id}-remark',
175                    'aria-label' => $remarklabeltext
176                );
177                $criteriontemplate .= html_writer::tag('td', s($currentremark), $remarkparams);
178            }
179        }
180        $criteriontemplate .= html_writer::end_tag('tr'); // .criterion
181
182        $criteriontemplate = str_replace('{NAME}', $elementname, $criteriontemplate);
183        $criteriontemplate = str_replace('{CRITERION-id}', $criterion['id'], $criteriontemplate);
184        return $criteriontemplate;
185    }
186
187    /**
188     * This function returns html code for displaying one level of one criterion. Depending on $mode
189     * it may be the code to edit rubric, to preview the rubric, to evaluate somebody or to review the evaluation.
190     *
191     * This function may be called from display_rubric() to display the whole rubric, or it can be
192     * called by itself to return a template used by JavaScript to add new empty level to the
193     * criterion during the design of rubric.
194     * In this case it will use macros like {NAME}, {CRITERION-id}, {LEVEL-id}, etc.
195     *
196     * When overriding this function it is very important to remember that all elements of html
197     * form (in edit or evaluate mode) must have the name $elementname.
198     *
199     * Also JavaScript relies on the class names of elements and when developer changes them
200     * script might stop working.
201     *
202     * @param int $mode rubric display mode see {@link gradingform_rubric_controller}
203     * @param array $options display options for this rubric, defaults are: {@link gradingform_rubric_controller::get_default_options()}
204     * @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
205     * @param string|int $criterionid either id of the nesting criterion or a macro for template
206     * @param array|null $level level data, also in view mode it might also have property $level['checked'] whether this level is checked
207     * @return string
208     */
209    public function level_template($mode, $options, $elementname = '{NAME}', $criterionid = '{CRITERION-id}', $level = null) {
210        // TODO MDL-31235 definition format
211        if (!isset($level['id'])) {
212            $level = array('id' => '{LEVEL-id}', 'definition' => '{LEVEL-definition}', 'score' => '{LEVEL-score}', 'class' => '{LEVEL-class}', 'checked' => false);
213        } else {
214            foreach (array('score', 'definition', 'class', 'checked', 'index') as $key) {
215                // set missing array elements to empty strings to avoid warnings
216                if (!array_key_exists($key, $level)) {
217                    $level[$key] = '';
218                }
219            }
220        }
221
222        // Get level index.
223        $levelindex = isset($level['index']) ? $level['index'] : '{LEVEL-index}';
224
225        // Template for one level within one criterion
226        $tdattributes = array(
227            'id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}',
228            'class' => 'level' . $level['class']
229        );
230        if (isset($level['tdwidth'])) {
231            $tdattributes['width'] = round($level['tdwidth']).'%';
232        }
233
234        $leveltemplate = html_writer::start_tag('div', array('class' => 'level-wrapper'));
235        if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
236            $definitionparams = array(
237                'id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-definition',
238                'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]',
239                'aria-label' => get_string('leveldefinition', 'gradingform_rubric', $levelindex),
240                'cols' => '10', 'rows' => '4'
241            );
242            $definition = html_writer::tag('textarea', s($level['definition']), $definitionparams);
243
244            $scoreparams = array(
245                'type' => 'text',
246                'id' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]',
247                'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]',
248                'aria-label' => get_string('scoreinputforlevel', 'gradingform_rubric', $levelindex),
249                'size' => '3',
250                'value' => $level['score']
251            );
252            $score = html_writer::empty_tag('input', $scoreparams);
253        } else {
254            if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
255                $leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => $level['definition']));
256                $leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'value' => $level['score']));
257            }
258            $definition = s($level['definition']);
259            $score = $level['score'];
260        }
261        if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
262            $levelradioparams = array(
263                'type' => 'radio',
264                'id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-definition',
265                'name' => '{NAME}[criteria][{CRITERION-id}][levelid]',
266                'value' => $level['id']
267            );
268            if ($level['checked']) {
269                $levelradioparams['checked'] = 'checked';
270            }
271            $input = html_writer::empty_tag('input', $levelradioparams);
272            $leveltemplate .= html_writer::div($input, 'radio');
273        }
274        if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN && $level['checked']) {
275            $leveltemplate .= html_writer::empty_tag('input',
276                array(
277                    'type' => 'hidden',
278                    'name' => '{NAME}[criteria][{CRITERION-id}][levelid]',
279                    'value' => $level['id']
280                )
281            );
282        }
283        $score = html_writer::tag('span', $score, array('id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-score', 'class' => 'scorevalue'));
284        $definitionclass = 'definition';
285        if (isset($level['error_definition'])) {
286            $definitionclass .= ' error';
287        }
288
289        if ($mode != gradingform_rubric_controller::DISPLAY_EDIT_FULL &&
290            $mode != gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
291
292            $tdattributes['tabindex'] = '0';
293            $levelinfo = new stdClass();
294            $levelinfo->definition = s($level['definition']);
295            $levelinfo->score = $level['score'];
296            $tdattributes['aria-label'] = get_string('level', 'gradingform_rubric', $levelinfo);
297
298            if ($mode != gradingform_rubric_controller::DISPLAY_PREVIEW &&
299                $mode != gradingform_rubric_controller::DISPLAY_PREVIEW_GRADED) {
300                // Add role of radio button to level cell if not in edit and preview mode.
301                $tdattributes['role'] = 'radio';
302                if ($level['checked']) {
303                    $tdattributes['aria-checked'] = 'true';
304                } else {
305                    $tdattributes['aria-checked'] = 'false';
306                }
307            }
308        }
309
310        $leveltemplateparams = array(
311            'id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-definition-container'
312        );
313        $leveltemplate .= html_writer::div($definition, $definitionclass, $leveltemplateparams);
314        $displayscore = true;
315        if (!$options['showscoreteacher'] && in_array($mode, array(gradingform_rubric_controller::DISPLAY_EVAL, gradingform_rubric_controller::DISPLAY_EVAL_FROZEN, gradingform_rubric_controller::DISPLAY_REVIEW))) {
316            $displayscore = false;
317        }
318        if (!$options['showscorestudent'] && in_array($mode, array(gradingform_rubric_controller::DISPLAY_VIEW, gradingform_rubric_controller::DISPLAY_PREVIEW_GRADED))) {
319            $displayscore = false;
320        }
321        if ($displayscore) {
322            $scoreclass = 'score';
323            if (isset($level['error_score'])) {
324                $scoreclass .= ' error';
325            }
326            $leveltemplate .= html_writer::tag('div', get_string('scorepostfix', 'gradingform_rubric', $score), array('class' => $scoreclass));
327        }
328        if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
329            $value = get_string('leveldelete', 'gradingform_rubric', $levelindex);
330            $buttonparams = array(
331                'type' => 'submit',
332                'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][delete]',
333                'id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-delete',
334                'value' => $value
335            );
336            $button = html_writer::empty_tag('input', $buttonparams);
337            $leveltemplate .= html_writer::tag('div', $button, array('class' => 'delete'));
338        }
339        $leveltemplate .= html_writer::end_tag('div'); // .level-wrapper
340
341        $leveltemplate = html_writer::tag('td', $leveltemplate, $tdattributes); // The .level cell.
342
343        $leveltemplate = str_replace('{NAME}', $elementname, $leveltemplate);
344        $leveltemplate = str_replace('{CRITERION-id}', $criterionid, $leveltemplate);
345        $leveltemplate = str_replace('{LEVEL-id}', $level['id'], $leveltemplate);
346        return $leveltemplate;
347    }
348
349    /**
350     * This function returns html code for displaying rubric template (content before and after
351     * criteria list). Depending on $mode it may be the code to edit rubric, to preview the rubric,
352     * to evaluate somebody or to review the evaluation.
353     *
354     * This function is called from display_rubric() to display the whole rubric.
355     *
356     * When overriding this function it is very important to remember that all elements of html
357     * form (in edit or evaluate mode) must have the name $elementname.
358     *
359     * Also JavaScript relies on the class names of elements and when developer changes them
360     * script might stop working.
361     *
362     * @param int $mode rubric display mode see {@link gradingform_rubric_controller}
363     * @param array $options display options for this rubric, defaults are: {@link gradingform_rubric_controller::get_default_options()}
364     * @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
365     * @param string $criteriastr evaluated templates for this rubric's criteria
366     * @return string
367     */
368    protected function rubric_template($mode, $options, $elementname, $criteriastr) {
369        $classsuffix = ''; // CSS suffix for class of the main div. Depends on the mode
370        switch ($mode) {
371            case gradingform_rubric_controller::DISPLAY_EDIT_FULL:
372                $classsuffix = ' editor editable'; break;
373            case gradingform_rubric_controller::DISPLAY_EDIT_FROZEN:
374                $classsuffix = ' editor frozen';  break;
375            case gradingform_rubric_controller::DISPLAY_PREVIEW:
376            case gradingform_rubric_controller::DISPLAY_PREVIEW_GRADED:
377                $classsuffix = ' editor preview';  break;
378            case gradingform_rubric_controller::DISPLAY_EVAL:
379                $classsuffix = ' evaluate editable'; break;
380            case gradingform_rubric_controller::DISPLAY_EVAL_FROZEN:
381                $classsuffix = ' evaluate frozen';  break;
382            case gradingform_rubric_controller::DISPLAY_REVIEW:
383                $classsuffix = ' review';  break;
384            case gradingform_rubric_controller::DISPLAY_VIEW:
385                $classsuffix = ' view';  break;
386        }
387
388        $rubrictemplate = html_writer::start_tag('div', array('id' => 'rubric-{NAME}', 'class' => 'clearfix gradingform_rubric'.$classsuffix));
389
390        // Rubric table.
391        $rubrictableparams = array(
392            'class' => 'criteria',
393            'id' => '{NAME}-criteria',
394            'aria-label' => get_string('rubric', 'gradingform_rubric'));
395        $rubrictable = html_writer::tag('table', $criteriastr, $rubrictableparams);
396        $rubrictemplate .= $rubrictable;
397        if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
398            $value = get_string('addcriterion', 'gradingform_rubric');
399            $criteriainputparams = array(
400                'type' => 'submit',
401                'name' => '{NAME}[criteria][addcriterion]',
402                'id' => '{NAME}-criteria-addcriterion',
403                'value' => $value
404            );
405            $input = html_writer::empty_tag('input', $criteriainputparams);
406            $rubrictemplate .= html_writer::tag('div', $input, array('class' => 'addcriterion btn btn-secondary'));
407        }
408        $rubrictemplate .= $this->rubric_edit_options($mode, $options);
409        $rubrictemplate .= html_writer::end_tag('div');
410
411        return str_replace('{NAME}', $elementname, $rubrictemplate);
412    }
413
414    /**
415     * Generates html template to view/edit the rubric options. Expression {NAME} is used in
416     * template for the form element name
417     *
418     * @param int $mode rubric display mode see {@link gradingform_rubric_controller}
419     * @param array $options display options for this rubric, defaults are: {@link gradingform_rubric_controller::get_default_options()}
420     * @return string
421     */
422    protected function rubric_edit_options($mode, $options) {
423        if ($mode != gradingform_rubric_controller::DISPLAY_EDIT_FULL
424                && $mode != gradingform_rubric_controller::DISPLAY_EDIT_FROZEN
425                && $mode != gradingform_rubric_controller::DISPLAY_PREVIEW) {
426            // Options are displayed only for people who can manage
427            return;
428        }
429        $html = html_writer::start_tag('div', array('class' => 'options'));
430        $html .= html_writer::tag('div', get_string('rubricoptions', 'gradingform_rubric'), array('class' => 'optionsheading'));
431        $attrs = array('type' => 'hidden', 'name' => '{NAME}[options][optionsset]', 'value' => 1);
432        foreach ($options as $option => $value) {
433            $html .= html_writer::start_tag('div', array('class' => 'option '.$option));
434            $attrs = array('name' => '{NAME}[options]['.$option.']', 'id' => '{NAME}-options-'.$option);
435            switch ($option) {
436                case 'sortlevelsasc':
437                    // Display option as dropdown
438                    $html .= html_writer::label(get_string($option, 'gradingform_rubric'), $attrs['id'], false);
439                    $value = (int)(!!$value); // make sure $value is either 0 or 1
440                    if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
441                        $selectoptions = array(0 => get_string($option.'0', 'gradingform_rubric'), 1 => get_string($option.'1', 'gradingform_rubric'));
442                        $valuestr = html_writer::select($selectoptions, $attrs['name'], $value, false, array('id' => $attrs['id']));
443                        $html .= html_writer::tag('span', $valuestr, array('class' => 'value'));
444                    } else {
445                        $html .= html_writer::tag('span', get_string($option.$value, 'gradingform_rubric'), array('class' => 'value'));
446                        if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
447                            $html .= html_writer::empty_tag('input', $attrs + array('type' => 'hidden', 'value' => $value));
448                        }
449                    }
450                    break;
451                default:
452                    if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN && $value) {
453                        // Id should be different then the actual input added later.
454                        $attrs['id'] .= '_hidden';
455                        $html .= html_writer::empty_tag('input', $attrs + array('type' => 'hidden', 'value' => $value));
456                    }
457                    // Display option as checkbox
458                    $attrs['type'] = 'checkbox';
459                    $attrs['value'] = 1;
460                    if ($value) {
461                        $attrs['checked'] = 'checked';
462                    }
463                    if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN || $mode == gradingform_rubric_controller::DISPLAY_PREVIEW) {
464                        $attrs['disabled'] = 'disabled';
465                        unset($attrs['name']);
466                        // Id should be different then the actual input added later.
467                        $attrs['id'] .= '_disabled';
468                    }
469                    $html .= html_writer::empty_tag('input', $attrs);
470                    $html .= html_writer::tag('label', get_string($option, 'gradingform_rubric'), array('for' => $attrs['id']));
471                    break;
472            }
473            if (get_string_manager()->string_exists($option.'_help', 'gradingform_rubric')) {
474                $html .= $this->help_icon($option, 'gradingform_rubric');
475            }
476            $html .= html_writer::end_tag('div'); // .option
477        }
478        $html .= html_writer::end_tag('div'); // .options
479        return $html;
480    }
481
482    /**
483     * This function returns html code for displaying rubric. Depending on $mode it may be the code
484     * to edit rubric, to preview the rubric, to evaluate somebody or to review the evaluation.
485     *
486     * It is very unlikely that this function needs to be overriden by theme. It does not produce
487     * any html code, it just prepares data about rubric design and evaluation, adds the CSS
488     * class to elements and calls the functions level_template, criterion_template and
489     * rubric_template
490     *
491     * @param array $criteria data about the rubric design
492     * @param array $options display options for this rubric, defaults are: {@link gradingform_rubric_controller::get_default_options()}
493     * @param int $mode rubric display mode, see {@link gradingform_rubric_controller}
494     * @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
495     * @param array $values evaluation result
496     * @return string
497     */
498    public function display_rubric($criteria, $options, $mode, $elementname = null, $values = null) {
499        $criteriastr = '';
500        $cnt = 0;
501        foreach ($criteria as $id => $criterion) {
502            $criterion['class'] = $this->get_css_class_suffix($cnt++, sizeof($criteria) -1);
503            $criterion['id'] = $id;
504            $levelsstr = '';
505            $levelcnt = 0;
506            if (isset($values['criteria'][$id])) {
507                $criterionvalue = $values['criteria'][$id];
508            } else {
509                $criterionvalue = null;
510            }
511            $index = 1;
512            foreach ($criterion['levels'] as $levelid => $level) {
513                $level['id'] = $levelid;
514                $level['class'] = $this->get_css_class_suffix($levelcnt++, sizeof($criterion['levels']) -1);
515                $level['checked'] = (isset($criterionvalue['levelid']) && ((int)$criterionvalue['levelid'] === $levelid));
516                if ($level['checked'] && ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN || $mode == gradingform_rubric_controller::DISPLAY_REVIEW || $mode == gradingform_rubric_controller::DISPLAY_VIEW)) {
517                    $level['class'] .= ' checked';
518                    //in mode DISPLAY_EVAL the class 'checked' will be added by JS if it is enabled. If JS is not enabled, the 'checked' class will only confuse
519                }
520                if (isset($criterionvalue['savedlevelid']) && ((int)$criterionvalue['savedlevelid'] === $levelid)) {
521                    $level['class'] .= ' currentchecked';
522                }
523                $level['tdwidth'] = 100/count($criterion['levels']);
524                $level['index'] = $index;
525                $levelsstr .= $this->level_template($mode, $options, $elementname, $id, $level);
526                $index++;
527            }
528            $criteriastr .= $this->criterion_template($mode, $options, $elementname, $criterion, $levelsstr, $criterionvalue);
529        }
530        return $this->rubric_template($mode, $options, $elementname, $criteriastr);
531    }
532
533    /**
534     * Help function to return CSS class names for element (first/last/even/odd) with leading space
535     *
536     * @param int $idx index of this element in the row/column
537     * @param int $maxidx maximum index of the element in the row/column
538     * @return string
539     */
540    protected function get_css_class_suffix($idx, $maxidx) {
541        $class = '';
542        if ($idx == 0) {
543            $class .= ' first';
544        }
545        if ($idx == $maxidx) {
546            $class .= ' last';
547        }
548        if ($idx%2) {
549            $class .= ' odd';
550        } else {
551            $class .= ' even';
552        }
553        return $class;
554    }
555
556    /**
557     * Displays for the student the list of instances or default content if no instances found
558     *
559     * @param array $instances array of objects of type gradingform_rubric_instance
560     * @param string $defaultcontent default string that would be displayed without advanced grading
561     * @param boolean $cangrade whether current user has capability to grade in this context
562     * @return string
563     */
564    public function display_instances($instances, $defaultcontent, $cangrade) {
565        $return = '';
566        if (sizeof($instances)) {
567            $return .= html_writer::start_tag('div', array('class' => 'advancedgrade'));
568            $idx = 0;
569            foreach ($instances as $instance) {
570                $return .= $this->display_instance($instance, $idx++, $cangrade);
571            }
572            $return .= html_writer::end_tag('div');
573        }
574        return $return. $defaultcontent;
575    }
576
577    /**
578     * Displays one grading instance
579     *
580     * @param gradingform_rubric_instance $instance
581     * @param int $idx unique number of instance on page
582     * @param bool $cangrade whether current user has capability to grade in this context
583     */
584    public function display_instance(gradingform_rubric_instance $instance, $idx, $cangrade) {
585        $criteria = $instance->get_controller()->get_definition()->rubric_criteria;
586        $options = $instance->get_controller()->get_options();
587        $values = $instance->get_rubric_filling();
588        if ($cangrade) {
589            $mode = gradingform_rubric_controller::DISPLAY_REVIEW;
590            $showdescription = $options['showdescriptionteacher'];
591        } else {
592            $mode = gradingform_rubric_controller::DISPLAY_VIEW;
593            $showdescription = $options['showdescriptionstudent'];
594        }
595        $output = '';
596        if ($showdescription) {
597            $output .= $this->box($instance->get_controller()->get_formatted_description(), 'gradingform_rubric-description');
598        }
599        $output .= $this->display_rubric($criteria, $options, $mode, 'rubric'.$idx, $values);
600        return $output;
601    }
602
603    /**
604     * Displays confirmation that students require re-grading
605     *
606     * @param string $elementname
607     * @param int $changelevel
608     * @param string $value
609     * @return string
610     */
611    public function display_regrade_confirmation($elementname, $changelevel, $value) {
612        $html = html_writer::start_tag('div', array('class' => 'gradingform_rubric-regrade', 'role' => 'alert'));
613        if ($changelevel<=2) {
614            $html .= html_writer::label(get_string('regrademessage1', 'gradingform_rubric'), 'menu' . $elementname . 'regrade');
615            $selectoptions = array(
616                0 => get_string('regradeoption0', 'gradingform_rubric'),
617                1 => get_string('regradeoption1', 'gradingform_rubric')
618            );
619            $html .= html_writer::select($selectoptions, $elementname.'[regrade]', $value, false);
620        } else {
621            $html .= get_string('regrademessage5', 'gradingform_rubric');
622            $html .= html_writer::empty_tag('input', array('name' => $elementname.'[regrade]', 'value' => 1, 'type' => 'hidden'));
623        }
624        $html .= html_writer::end_tag('div');
625        return $html;
626    }
627
628    /**
629     * Generates and returns HTML code to display information box about how rubric score is converted to the grade
630     *
631     * @param array $scores
632     * @return string
633     */
634    public function display_rubric_mapping_explained($scores) {
635        $html = '';
636        if (!$scores) {
637            return $html;
638        }
639        if ($scores['minscore'] <> 0) {
640            $html .= $this->output->notification(get_string('zerolevelsabsent', 'gradingform_rubric'), 'error');
641        }
642        $html .= $this->output->notification(get_string('rubricmappingexplained', 'gradingform_rubric', (object)$scores), 'info');
643        return $html;
644    }
645}
646