Commit | Line | Data |
---|---|---|
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 |
25 | defined('MOODLE_INTERNAL') || die(); |
26 | ||
27 | /** | |
28 | * Grading method plugin renderer | |
29 | */ | |
ab156741 | 30 | class 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 | } |