MDL-29794: fixed bug with description cloning
[moodle.git] / grade / grading / form / rubric / renderer.php
CommitLineData
c586d2bf
MG
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * @package gradingform
20 * @subpackage rubric
21 * @copyright 2011 Marina Glancy
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
c586d2bf
MG
25defined('MOODLE_INTERNAL') || die();
26
27/**
28 * Grading method plugin renderer
29 */
ab156741 30class gradingform_rubric_renderer {
c586d2bf 31
ab156741 32 /**
fc5adc3b
MG
33 * This function returns html code for displaying criterion. Depending on $mode it may be the
34 * code to edit rubric, to preview the rubric, to evaluate somebody or to review the evaluation.
35 *
36 * This function may be called from display_rubric() to display the whole rubric, or it can be
37 * called by itself to return a template used by JavaScript to add new empty criteria to the
38 * rubric being designed.
39 * In this case it will use macros like {NAME}, {LEVELS}, {CRITERION-id}, etc.
40 *
41 * When overriding this function it is very important to remember that all elements of html
42 * form (in edit or evaluate mode) must have the name $elementname.
ab156741 43 *
fc5adc3b
MG
44 * Also JavaScript relies on the class names of elements and when developer changes them
45 * script might stop working.
46 *
47 * @param int $mode rubric display mode @see gradingform_rubric_controller
48 * @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
49 * @param array|null $criterion criterion data
50 * @param string $levels_str evaluated templates for this criterion levels
51 * @param array|null $value (only in view mode) teacher's feedback on this criterion
ab156741
MG
52 * @return string
53 */
5060997b
MG
54 public function criterion_template($mode, $elementname = '{NAME}', $criterion = null, $levels_str = '{LEVELS}', $value = null) {
55 // TODO description format, remark format
ab156741
MG
56 if ($criterion === null || !is_array($criterion) || !array_key_exists('id', $criterion)) {
57 $criterion = array('id' => '{CRITERION-id}', 'description' => '{CRITERION-description}', 'sortorder' => '{CRITERION-sortorder}', 'class' => '{CRITERION-class}');
58 } else {
59 foreach (array('sortorder', 'description', 'class') as $key) {
60 // set missing array elements to empty strings to avoid warnings
61 if (!array_key_exists($key, $criterion)) {
62 $criterion[$key] = '';
63 }
64 }
65 }
8df55bbe 66 $criterion_template = html_writer::start_tag('tr', array('class' => 'clearfix criterion'. $criterion['class'], 'id' => '{NAME}-{CRITERION-id}'));
ab156741 67 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
8df55bbe 68 $criterion_template .= html_writer::start_tag('td', array('class' => 'controls'));
ab156741
MG
69 foreach (array('moveup', 'delete', 'movedown') as $key) {
70 $value = get_string('criterion'.$key, 'gradingform_rubric');
71 $button = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[{CRITERION-id}]['.$key.']',
72 'id' => '{NAME}-{CRITERION-id}-'.$key, 'value' => $value, 'title' => $value));
73 $criterion_template .= html_writer::tag('div', $button, array('class' => $key));
74 }
8df55bbe 75 $criterion_template .= html_writer::end_tag('td'); // .controls
ab156741
MG
76 $criterion_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
77 $description = html_writer::tag('textarea', htmlspecialchars($criterion['description']), array('name' => '{NAME}[{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
78 } else {
79 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
80 $criterion_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
81 $criterion_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][description]', 'value' => $criterion['description']));
82 }
83 $description = $criterion['description'];
84 }
8df55bbe
MG
85 $criterion_template .= html_writer::tag('td', $description, array('class' => 'description', 'id' => '{NAME}-{CRITERION-id}-description'));
86 $levels_str_table = html_writer::tag('table', html_writer::tag('tr', $levels_str, array('id' => '{NAME}-{CRITERION-id}-levels')));
87 $criterion_template .= html_writer::tag('td', $levels_str_table, array('class' => 'clearfix levels'));
ab156741
MG
88 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
89 $value = get_string('criterionaddlevel', 'gradingform_rubric');
90 $button = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[{CRITERION-id}][levels][addlevel]',
91 'id' => '{NAME}-{CRITERION-id}-levels-addlevel', 'value' => $value, 'title' => $value)); //TODO '{NAME}-{CRITERION-id}-levels-addlevel
8df55bbe 92 $criterion_template .= html_writer::tag('td', $button, array('class' => 'addlevel'));
ab156741 93 }
5060997b
MG
94 if (isset($value['remark'])) {
95 $currentremark = $value['remark'];
96 } else {
97 $currentremark = '';
98 }
99 if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
100 $input = html_writer::tag('textarea', htmlspecialchars($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
8df55bbe 101 $criterion_template .= html_writer::tag('td', $input, array('class' => 'remark'));
5060997b
MG
102 }
103 if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN) {
104 $criterion_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
105 }
106 if ($mode == gradingform_rubric_controller::DISPLAY_REVIEW) {
8df55bbe 107 $criterion_template .= html_writer::tag('td', $currentremark, array('class' => 'remark')); // TODO maybe some prefix here like 'Teacher remark:'
5060997b 108 }
8df55bbe 109 $criterion_template .= html_writer::end_tag('tr'); // .criterion
ab156741
MG
110
111 $criterion_template = str_replace('{NAME}', $elementname, $criterion_template);
112 $criterion_template = str_replace('{CRITERION-id}', $criterion['id'], $criterion_template);
113 return $criterion_template;
114 }
115
fc5adc3b
MG
116 /**
117 * This function returns html code for displaying one level of one criterion. Depending on $mode
118 * it may be the code to edit rubric, to preview the rubric, to evaluate somebody or to review the evaluation.
119 *
120 * This function may be called from display_rubric() to display the whole rubric, or it can be
121 * called by itself to return a template used by JavaScript to add new empty level to the
122 * criterion during the design of rubric.
123 * In this case it will use macros like {NAME}, {CRITERION-id}, {LEVEL-id}, etc.
124 *
125 * When overriding this function it is very important to remember that all elements of html
126 * form (in edit or evaluate mode) must have the name $elementname.
127 *
128 * Also JavaScript relies on the class names of elements and when developer changes them
129 * script might stop working.
130 *
131 * @param int $mode rubric display mode @see gradingform_rubric_controller
132 * @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
133 * @param string|int $criterionid either id of the nesting criterion or a macro for template
134 * @param array|null $level level data, also in view mode it might also have property $level['checked'] whether this level is checked
135 * @return string
136 */
ab156741
MG
137 public function level_template($mode, $elementname = '{NAME}', $criterionid = '{CRITERION-id}', $level = null) {
138 // TODO definition format
5060997b 139 if (!isset($level['id'])) {
ab156741
MG
140 $level = array('id' => '{LEVEL-id}', 'definition' => '{LEVEL-definition}', 'score' => '{LEVEL-score}', 'class' => '{LEVEL-class}', 'checked' => false);
141 } else {
142 foreach (array('score', 'definition', 'class', 'checked') as $key) {
143 // set missing array elements to empty strings to avoid warnings
144 if (!array_key_exists($key, $level)) {
145 $level[$key] = '';
146 }
147 }
148 }
149
150 // Template for one level within one criterion
8df55bbe 151 $level_template = html_writer::start_tag('td', array('id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}', 'class' => 'clearfix level'. $level['class']));
ab156741
MG
152 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
153 $definition = html_writer::tag('textarea', htmlspecialchars($level['definition']), array('name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
154 $score = html_writer::empty_tag('input', array('type' => 'text', 'name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '4', 'value' => $level['score']));
155 } else {
156 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
157 $level_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => $level['definition']));
158 $level_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][score]', 'value' => $level['score']));
159 }
160 $definition = $level['definition'];
161 $score = $level['score'];
162 }
163 if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
5060997b 164 $input = html_writer::empty_tag('input', array('type' => 'radio', 'name' => '{NAME}[criteria][{CRITERION-id}][levelid]', 'value' => $level['id']) +
ab156741
MG
165 ($level['checked'] ? array('checked' => 'checked') : array()));
166 $level_template .= html_writer::tag('div', $input, array('class' => 'radio'));
167 }
168 if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN && $level['checked']) {
5060997b 169 $level_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levelid]', 'value' => $level['id']));
ab156741
MG
170 }
171 $score = html_writer::tag('span', $score, array('id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}-score'));
172 $level_template .= html_writer::tag('div', $definition, array('class' => 'definition', 'id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}-definition'));
173 $level_template .= html_writer::tag('div', $score. get_string('scorepostfix', 'gradingform_rubric'), array('class' => 'score'));
174 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
175 $value = get_string('leveldelete', 'gradingform_rubric');
176 $button = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][delete]', 'id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}-delete', 'value' => $value, 'title' => $value));
177 $level_template .= html_writer::tag('div', $button, array('class' => 'delete'));
178 }
8df55bbe 179 $level_template .= html_writer::end_tag('td'); // .level
ab156741
MG
180
181 $level_template = str_replace('{NAME}', $elementname, $level_template);
182 $level_template = str_replace('{CRITERION-id}', $criterionid, $level_template);
183 $level_template = str_replace('{LEVEL-id}', $level['id'], $level_template);
184 return $level_template;
185 }
186
fc5adc3b
MG
187 /**
188 * This function returns html code for displaying rubric template (content before and after
189 * criteria list). Depending on $mode it may be the code to edit rubric, to preview the rubric,
190 * to evaluate somebody or to review the evaluation.
191 *
192 * This function is called from display_rubric() to display the whole rubric.
193 *
194 * When overriding this function it is very important to remember that all elements of html
195 * form (in edit or evaluate mode) must have the name $elementname.
196 *
197 * Also JavaScript relies on the class names of elements and when developer changes them
198 * script might stop working.
199 *
200 * @param int $mode rubric display mode @see gradingform_rubric_controller
201 * @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
202 * @param string $criteria_str evaluated templates for this rubric's criteria
203 * @return string
204 */
205 protected function rubric_template($mode, $elementname, $criteria_str) {
ab156741
MG
206 $classsuffix = ''; // CSS suffix for class of the main div. Depends on the mode
207 switch ($mode) {
208 case gradingform_rubric_controller::DISPLAY_EDIT_FULL:
209 $classsuffix = ' editor editable'; break;
210 case gradingform_rubric_controller::DISPLAY_EDIT_FROZEN:
211 $classsuffix = ' editor frozen'; break;
212 case gradingform_rubric_controller::DISPLAY_PREVIEW:
213 $classsuffix = ' editor preview'; break;
214 case gradingform_rubric_controller::DISPLAY_EVAL:
215 $classsuffix = ' evaluate editable'; break;
216 case gradingform_rubric_controller::DISPLAY_EVAL_FROZEN:
217 $classsuffix = ' evaluate frozen'; break;
218 case gradingform_rubric_controller::DISPLAY_REVIEW:
219 $classsuffix = ' review'; break;
220 }
221
fc5adc3b 222 $rubric_template = html_writer::start_tag('div', array('id' => 'rubric-{NAME}', 'class' => 'clearfix gradingform_rubric'.$classsuffix));
8df55bbe 223 $rubric_template .= html_writer::tag('table', $criteria_str, array('class' => 'criteria', 'id' => '{NAME}-criteria'));
ab156741
MG
224 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
225 $value = get_string('addcriterion', 'gradingform_rubric');
226 $input = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[addcriterion]', 'id' => '{NAME}-addcriterion', 'value' => $value, 'title' => $value));
227 $rubric_template .= html_writer::tag('div', $input, array('class' => 'addcriterion'));
228 }
229 $rubric_template .= html_writer::end_tag('div');
230
231 return str_replace('{NAME}', $elementname, $rubric_template);
232 }
233
234 /**
fc5adc3b
MG
235 * This function returns html code for displaying rubric. Depending on $mode it may be the code
236 * to edit rubric, to preview the rubric, to evaluate somebody or to review the evaluation.
237 *
238 * It is very unlikely that this function needs to be overriden by theme. It does not produce
239 * any html code, it just prepares data about rubric design and evaluation, adds the CSS
240 * class to elements and calls the functions level_template, criterion_template and
241 * rubric_template
ab156741 242 *
fc5adc3b
MG
243 * @param array $criteria data about the rubric design
244 * @param int $mode rubric display mode @see gradingform_rubric_controller
245 * @param string $elementname the name of the form element (in editor mode) or the prefix for div ids (in view mode)
246 * @param array $values evaluation result
ab156741
MG
247 * @return string
248 */
249 public function display_rubric($criteria, $mode, $elementname = null, $values = null) {
250 $criteria_str = '';
251 $cnt = 0;
252 foreach ($criteria as $id => $criterion) {
253 $criterion['class'] = $this->get_css_class_suffix($cnt++, sizeof($criteria) -1);
254 $levels_str = '';
255 $levelcnt = 0;
5060997b
MG
256 if (isset($values['criteria'][$id])) {
257 $criterionvalue = $values['criteria'][$id];
258 } else {
259 $criterionvalue = null;
260 }
ab156741
MG
261 foreach ($criterion['levels'] as $levelid => $level) {
262 $level['score'] = (float)$level['score']; // otherwise the display will look like 1.00000
263 $level['class'] = $this->get_css_class_suffix($levelcnt++, sizeof($criterion['levels']) -1);
5060997b 264 $level['checked'] = (isset($criterionvalue['levelid']) && ((int)$criterionvalue['levelid'] === $levelid));
ab156741
MG
265 if ($level['checked'] && ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN || $mode == gradingform_rubric_controller::DISPLAY_REVIEW)) {
266 $level['class'] .= ' checked';
fc5adc3b 267 //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
ab156741
MG
268 }
269 $levels_str .= $this->level_template($mode, $elementname, $id, $level);
270 }
5060997b 271 $criteria_str .= $this->criterion_template($mode, $elementname, $criterion, $levels_str, $criterionvalue);
ab156741
MG
272 }
273 return $this->rubric_template($mode, $elementname, $criteria_str);
274 }
275
276 /**
fc5adc3b 277 * Help function to return CSS class names for element (first/last/even/odd) with leading space
ab156741 278 *
fc5adc3b
MG
279 * @param int $cnt
280 * @param int $maxcnt
ab156741
MG
281 * @return string
282 */
fc5adc3b 283 protected function get_css_class_suffix($cnt, $maxcnt) {
ab156741
MG
284 $class = '';
285 if ($cnt == 0) {
286 $class .= ' first';
287 }
288 if ($cnt == $maxcnt) {
289 $class .= ' last';
290 }
291 if ($cnt%2) {
292 $class .= ' odd';
293 } else {
294 $class .= ' even';
295 }
296 return $class;
297 }
36937f02
MG
298
299 /**
300 * Displays for the student the list of instances or default content if no instances found
301 *
302 * @param array $instances array of objects of type gradingform_rubric_instance
303 * @param string $defaultcontent default string that would be displayed without advanced grading
304 * @return string
305 */
306 public function display_instances($instances, $defaultcontent) {
307 if (sizeof($instances)) {
308 $rv = html_writer::start_tag('div', array('class' => 'advancedgrade'));
309 $idx = 0;
310 foreach ($instances as $instance) {
311 $rv .= $this->display_instance($instance, $idx++);
312 }
313 $rv .= html_writer::end_tag('div');
314 }
315 return $rv. $defaultcontent;
316 }
317
318 /**
319 * Displays one grading instance
320 *
321 * @param gradingform_rubric_instance $instance
322 * @param int idx unique number of instance on page
323 */
324 public function display_instance(gradingform_rubric_instance $instance, $idx) {
325 $criteria = $instance->get_controller()->get_definition()->rubric_criteria;
326 $values = $instance->get_rubric_filling();
327 return $this->display_rubric($criteria, gradingform_rubric_controller::DISPLAY_REVIEW, 'rubric'.$idx, $values);
328 }
c586d2bf 329}