MDL-29481: start implementing rubric editor
[moodle.git] / grade / grading / form / rubric / rubriceditor.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
25defined('MOODLE_INTERNAL') || die();
26
27require_once("HTML/QuickForm/input.php");
28
29// register file-related rules
30if (class_exists('HTML_QuickForm')) {
31 HTML_QuickForm::registerRule('rubriceditorcompleted', 'callback', '_ruleIsCompleted', 'MoodleQuickForm_rubriceditor');
32}
33
34class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input {
35 public $_helpbutton = '';
36
37 function MoodleQuickForm_rubriceditor($elementName=null, $elementLabel=null, $attributes=null) {
38 parent::HTML_QuickForm_input($elementName, $elementLabel, $attributes);
39 }
40
41 function getHelpButton() {
42 return $this->_helpbutton;
43 }
44
45 function getElementTemplateType() {
46 return 'default';
47 }
48
49 function toHtml() {
50 global $PAGE;
51 $html = $this->_getTabs();
52
53 // Template for the whole rubric editor
54 $classsuffix = $this->_flagFrozen ? 'frozen' : 'editable';
55 $rubric_template = html_writer::start_tag('div', array('id' => 'rubriceditor-{NAME}', 'class' => 'clearfix form_rubric editor '.$classsuffix));
56 $rubric_template .= html_writer::tag('div', '{CRITERIA}', array('class' => 'criteria', 'id' => '{NAME}-criteria'));
57 if (!$this->_flagFrozen) {
58 $rubric_template .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[addcriterion]', 'id' => '{NAME}-addcriterion', 'value' => get_string('addcriterion', 'gradingform_rubric')));
59 }
60 $rubric_template .= html_writer::end_tag('div');
61
62 // Template for one criterion
63 $criterion_template = html_writer::start_tag('div', array('class' => 'clearfix criterion{CRITERION-class}', 'id' => '{NAME}-{CRITERION-id}'));
64 if (!$this->_flagFrozen) {
65 $criterion_template .= html_writer::start_tag('div', array('class' => 'controls'));
66 foreach (array('moveup', 'delete', 'movedown') as $key) {
67 $button = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[{CRITERION-id}]['.$key.']',
68 'id' => '{NAME}-{CRITERION-id}-'.$key, 'value' => get_string('criterion'.$key, 'gradingform_rubric')));
69 $criterion_template .= html_writer::tag('div', $button, array('class' => $key));
70 }
71 $criterion_template .= html_writer::end_tag('div'); // .controls
72 $criterion_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][sortorder]', 'value' => '{CRITERION-sortorder}'));
73 $description = html_writer::tag('textarea', '{CRITERION-description}', array('name' => '{NAME}[{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
74 } else {
75 if ($this->_persistantFreeze) {
76 $criterion_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][sortorder]', 'value' => '{CRITERION-sortorder}'));
77 $criterion_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][description]', 'value' => '{CRITERION-description}'));
78 }
79 $description = '{CRITERION-description}';
80 }
81 $criterion_template .= html_writer::tag('div', $description, array('class' => 'description', 'id' => '{NAME}-{CRITERION-id}-description'));
82 $criterion_template .= html_writer::tag('div', '{LEVELS}', array('class' => 'levels', 'id' => '{NAME}-{CRITERION-id}-levels'));
83 if (!$this->_flagFrozen) {
84 $button = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[{CRITERION-id}][levels][addlevel]',
85 'id' => '{NAME}-{CRITERION-id}-addlevel', 'value' => get_string('criterionaddlevel', 'gradingform_rubric'))); //TODO '{NAME}-{CRITERION-id}-levels-addlevel
86 $criterion_template .= html_writer::tag('div', $button, array('class' => 'addlevel'));
87 }
88 $criterion_template .= html_writer::end_tag('div'); // .criterion
89
90 // Template for one level within one criterion
91 $level_template = html_writer::start_tag('div', array('id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}', 'class' => 'level{LEVEL-class}'));
92 if (!$this->_flagFrozen) {
93 $definition = html_writer::tag('textarea', '{LEVEL-definition}', array('name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
94 $score = html_writer::empty_tag('input', array('type' => 'text', 'name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '4', 'value' => '{LEVEL-score}'));
95 } else {
96 if ($this->_persistantFreeze) {
97 $level_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => '{LEVEL-definition}'));
98 $level_template .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[{CRITERION-id}][levels][{LEVEL-id}][score]', 'value' => '{LEVEL-score}'));
99 }
100 $definition = '{LEVEL-definition}';
101 $score = '{LEVEL-score}';
102 }
103 $score = html_writer::tag('span', $score, array('id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}-score'));
104 $level_template .= html_writer::tag('div', $definition, array('class' => 'definition', 'id' => '{NAME}-{CRITERION-id}-levels-{LEVEL-id}-definition'));
105 $level_template .= html_writer::tag('div', $score. get_string('scorepostfix', 'gradingform_rubric'), array('class' => 'score'));
106 if (!$this->_flagFrozen) {
107 $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' => get_string('leveldelete', 'gradingform_rubric')));
108 $level_template .= html_writer::tag('div', $button, array('class' => 'delete'));
109 }
110 $level_template .= html_writer::end_tag('div'); // .level
111
112 $criterion_template = str_replace('{NAME}', $this->getName(), $criterion_template);
113 $level_template = str_replace('{NAME}', $this->getName(), $level_template);
114 $rubric_template = str_replace('{NAME}', $this->getName(), $rubric_template);
115
116 if (!$this->_flagFrozen) {
117 $module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js',
118 'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric')));
119 $PAGE->requires->js_init_call('M.gradingform_rubriceditor.init', array(array('name' => $this->getName(), 'criteriontemplate' => $criterion_template, 'leveltemplate' => $level_template)), true, $module);
120 }
121 $rubric_html = $rubric_template;
122 $criteria = $this->prepare_non_js_data();
123 $cnt = 0;
124 foreach ($criteria as $id => $criterion) {
125 $criterion_html = $criterion_template;
126 $levelcnt = 0;
127 foreach ($criterion['levels'] as $levelid => $level) {
128 $cell_html = $level_template;
129 $cell_html = str_replace('{LEVEL-id}', $levelid, $cell_html);
130 $cell_html = str_replace('{LEVEL-definition}', htmlspecialchars($level['definition']), $cell_html);
131 $cell_html = str_replace('{LEVEL-score}', htmlspecialchars($level['score']), $cell_html);
132 $cell_html = str_replace('{LEVEL-class}', $this->get_css_class_suffix($levelcnt++, sizeof($criterion['levels']) -1), $cell_html);
133 $criterion_html = str_replace('{LEVELS}', $cell_html.'{LEVELS}', $criterion_html);
134 }
135 $criterion_html = str_replace('{LEVELS}', '', $criterion_html);
136 $criterion_html = str_replace('{CRITERION-id}', $id, $criterion_html);
137 $criterion_html = str_replace('{CRITERION-description}', htmlspecialchars($criterion['description']), $criterion_html);
138 $criterion_html = str_replace('{CRITERION-sortorder}', htmlspecialchars($criterion['sortorder']), $criterion_html);
139 $criterion_html = str_replace('{CRITERION-class}', $this->get_css_class_suffix($cnt++, sizeof($criteria) -1), $criterion_html);
140 $rubric_html = str_replace('{CRITERIA}', $criterion_html.'{CRITERIA}', $rubric_html);
141 }
142 $rubric_html = str_replace('{CRITERIA}', '', $rubric_html);
143 $html .= $rubric_html;
144
145 return $html;
146 }
147
148 function get_css_class_suffix($cnt, $maxcnt) {
149 $class = '';
150 if ($cnt == 0) {
151 $class .= ' first';
152 }
153 if ($cnt == $maxcnt) {
154 $class .= ' last';
155 }
156 if ($cnt%2) {
157 $class .= ' odd';
158 } else {
159 $class .= ' even';
160 }
161 return $class;
162 }
163
164 function prepare_non_js_data() {
165 $return = array();
166 $criteria = $this->getValue();
167 if (empty($criteria)) {
168 $criteria = array();
169 }
170 $lastaction = null;
171 $lastid = null;
172 foreach ($criteria as $id => $criterion) {
173 if ($id == 'addcriterion') {
174 $id = $this->get_next_id(array_keys($criteria));
175 $criterion = array('description' => '');
176 }
177 $levels = array();
178 if (array_key_exists('levels', $criterion)) {
179 foreach ($criterion['levels'] as $levelid => $level) {
180 if ($levelid == 'addlevel') {
181 $levelid = $this->get_next_id(array_keys($criterion['levels']));
182 $level = array(
183 'definition' => '',
184 'score' => 0,
185 );
186 }
187 if (!array_key_exists('delete', $level)) {
188 $levels[$levelid] = $level;
189 }
190 }
191 }
192 $criterion['levels'] = $levels;
193 if (array_key_exists('moveup', $criterion) || $lastaction == 'movedown') {
194 unset($criterion['moveup']);
195 if ($lastid !== null) {
196 $lastcriterion = $return[$lastid];
197 unset($return[$lastid]);
198 $return[$id] = $criterion;
199 $return[$lastid] = $lastcriterion;
200 } else {
201 $return[$id] = $criterion;
202 }
203 $lastaction = null;
204 $lastid = $id;
205 } else if (array_key_exists('delete', $criterion)) {
206 } else {
207 if (array_key_exists('movedown', $criterion)) {
208 unset($criterion['movedown']);
209 $lastaction = 'movedown';
210 }
211 $return[$id] = $criterion;
212 $lastid = $id;
213 }
214 }
215 $csortorder = 1;
216 foreach (array_keys($return) as $id) {
217 $return[$id]['sortorder'] = $csortorder++;
218 }
219 return $return;
220 }
221
222 function get_next_id($ids) {
223 $maxid = 0;
224 foreach ($ids as $id) {
225 if (preg_match('/^NEWID(\d+)$/', $id, $matches) && ((int)$matches[1]) > $maxid) {
226 $maxid = (int)$matches[1];
227 }
228 }
229 return 'NEWID'.($maxid+1);
230 }
231
232 function _ruleIsCompleted($elementValue) {
233 //echo "_ruleIsCompleted";
234 if (is_array($elementValue)) {
235 foreach ($elementValue as $criterionid => $criterion) {
236 if ($criterionid == 'addcriterion') {
237 return false;
238 }
239 if (array_key_exists('moveup', $criterion) || array_key_exists('movedown', $criterion) || array_key_exists('delete', $criterion)) {
240 return false;
241 }
242 if (array_key_exists('levels', $criterion) && is_array($criterion['levels'])) {
243 foreach ($criterion['levels'] as $levelid => $level) {
244 if ($levelid == 'addlevel') {
245 return false;
246 }
247 if (array_key_exists('delete', $level)) {
248 return false;
249 }
250 }
251 }
252 }
253 }
254 //TODO check everything is filled
255 //echo "<pre>";print_r($elementValue);echo "</pre>";
256 return true;
257 }
258
259 function onQuickFormEvent($event, $arg, &$caller)
260 {
261 $name = $this->getName();
262 if ($name && $caller->elementExists($name)) {
263 $caller->addRule($name, '', 'rubriceditorcompleted');
264 }
265 return parent::onQuickFormEvent($event, $arg, $caller);
266 }
267
268}