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 | ||
25 | defined('MOODLE_INTERNAL') || die(); | |
26 | ||
27 | require_once("HTML/QuickForm/input.php"); | |
28 | ||
c586d2bf MG |
29 | class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input { |
30 | public $_helpbutton = ''; | |
2ae7faf1 MG |
31 | protected $validationerrors = null; // null - undefined, false - no errors, string - error(s) text |
32 | protected $wasvalidated = false; // if element has already been validated | |
33 | protected $nonjsbuttonpressed = false; // null - unknown, true/false - button was/wasn't pressed | |
c586d2bf MG |
34 | |
35 | function MoodleQuickForm_rubriceditor($elementName=null, $elementLabel=null, $attributes=null) { | |
36 | parent::HTML_QuickForm_input($elementName, $elementLabel, $attributes); | |
37 | } | |
38 | ||
39 | function getHelpButton() { | |
40 | return $this->_helpbutton; | |
41 | } | |
42 | ||
43 | function getElementTemplateType() { | |
44 | return 'default'; | |
45 | } | |
46 | ||
47 | function toHtml() { | |
48 | global $PAGE; | |
49 | $html = $this->_getTabs(); | |
ab156741 | 50 | $renderer = $PAGE->get_renderer('gradingform_rubric'); |
2ae7faf1 | 51 | $data = $this->prepare_data(null, $this->wasvalidated); |
c586d2bf | 52 | if (!$this->_flagFrozen) { |
ab156741 MG |
53 | $mode = gradingform_rubric_controller::DISPLAY_EDIT_FULL; |
54 | $module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js', | |
55 | 'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric'), | |
56 | array('criterionempty', 'gradingform_rubric'), array('levelempty', 'gradingform_rubric') | |
57 | )); | |
58 | $PAGE->requires->js_init_call('M.gradingform_rubriceditor.init', array( | |
59 | array('name' => $this->getName(), | |
39c6f4b6 MG |
60 | 'criteriontemplate' => $renderer->criterion_template($mode, $data['options'], $this->getName()), |
61 | 'leveltemplate' => $renderer->level_template($mode, $data['options'], $this->getName()) | |
ab156741 MG |
62 | )), |
63 | true, $module); | |
c586d2bf | 64 | } else { |
39c6f4b6 | 65 | // Rubric is frozen, no javascript needed |
c586d2bf | 66 | if ($this->_persistantFreeze) { |
ab156741 MG |
67 | $mode = gradingform_rubric_controller::DISPLAY_EDIT_FROZEN; |
68 | } else { | |
69 | $mode = gradingform_rubric_controller::DISPLAY_PREVIEW; | |
c586d2bf | 70 | } |
c586d2bf | 71 | } |
2ae7faf1 MG |
72 | if ($this->validationerrors) { |
73 | $html .= $renderer->notification($this->validationerrors, 'error'); | |
74 | } | |
39c6f4b6 | 75 | $html .= $renderer->display_rubric($data['criteria'], $data['options'], $mode, $this->getName()); |
c586d2bf MG |
76 | return $html; |
77 | } | |
78 | ||
39c6f4b6 MG |
79 | /** |
80 | * Prepares the data passed in $_POST: | |
81 | * - processes the pressed buttons 'addlevel', 'addcriterion', 'moveup', 'movedown', 'delete' (when JavaScript is disabled) | |
2ae7faf1 | 82 | * sets $this->nonjsbuttonpressed to true/false if such button was pressed |
39c6f4b6 MG |
83 | * - if options not passed (i.e. we create a new rubric) fills the options array with the default values |
84 | * - if options are passed completes the options array with unchecked checkboxes | |
2ae7faf1 MG |
85 | * - if $withvalidation is set, adds 'error_xxx' attributes to elements that contain errors and creates an error string |
86 | * and stores it in $this->validationerrors | |
39c6f4b6 MG |
87 | * |
88 | * @param array $value | |
2ae7faf1 | 89 | * @param boolean $withvalidation whether to enable data validation |
39c6f4b6 MG |
90 | * @return array |
91 | */ | |
2ae7faf1 | 92 | function prepare_data($value = null, $withvalidation = false) { |
39c6f4b6 MG |
93 | if (null === $value) { |
94 | $value = $this->getValue(); | |
95 | } | |
2ae7faf1 MG |
96 | if ($this->nonjsbuttonpressed === null) { |
97 | $this->nonjsbuttonpressed = false; | |
98 | } | |
99 | $totalscore = 0; | |
100 | $errors = array(); | |
39c6f4b6 MG |
101 | $return = array('criteria' => array(), 'options' => gradingform_rubric_controller::get_default_options()); |
102 | if (!isset($value['criteria'])) { | |
103 | $value['criteria'] = array(); | |
2ae7faf1 | 104 | $errors['err_nocriteria'] = 1; |
39c6f4b6 | 105 | } |
2ae7faf1 | 106 | // If options are present in $value, replace default values with submitted values |
39c6f4b6 MG |
107 | if (!empty($value['options'])) { |
108 | foreach (array_keys($return['options']) as $option) { | |
109 | // special treatment for checkboxes | |
110 | if (!empty($value['options'][$option])) { | |
111 | $return['options'][$option] = $value['options'][$option]; | |
112 | } else { | |
113 | $return['options'][$option] = null; | |
114 | } | |
115 | } | |
c586d2bf | 116 | } |
2ae7faf1 MG |
117 | |
118 | // iterate through criteria | |
c586d2bf MG |
119 | $lastaction = null; |
120 | $lastid = null; | |
39c6f4b6 | 121 | foreach ($value['criteria'] as $id => $criterion) { |
c586d2bf | 122 | if ($id == 'addcriterion') { |
39c6f4b6 | 123 | $id = $this->get_next_id(array_keys($value['criteria'])); |
c586d2bf | 124 | $criterion = array('description' => ''); |
2ae7faf1 | 125 | $this->nonjsbuttonpressed = true; |
c586d2bf MG |
126 | } |
127 | $levels = array(); | |
2ae7faf1 | 128 | $maxscore = null; |
c586d2bf MG |
129 | if (array_key_exists('levels', $criterion)) { |
130 | foreach ($criterion['levels'] as $levelid => $level) { | |
131 | if ($levelid == 'addlevel') { | |
132 | $levelid = $this->get_next_id(array_keys($criterion['levels'])); | |
133 | $level = array( | |
134 | 'definition' => '', | |
135 | 'score' => 0, | |
136 | ); | |
2ae7faf1 | 137 | $this->nonjsbuttonpressed = true; |
c586d2bf MG |
138 | } |
139 | if (!array_key_exists('delete', $level)) { | |
2ae7faf1 MG |
140 | if ($withvalidation) { |
141 | if (empty($level['definition'])) { | |
142 | $errors['err_nodefinition'] = 1; | |
143 | $level['error_definition'] = true; | |
144 | } | |
145 | if (!preg_match('#^[\+]?\d*$#', trim($level['score'])) && !preg_match('#^[\+]?\d*[\.,]\d+$#', trim($level['score']))) { | |
146 | // TODO why we can't allow negative score for rubric? | |
147 | $errors['err_scoreformat'] = 1; | |
148 | $level['error_score'] = true; | |
149 | } | |
150 | } | |
c586d2bf | 151 | $levels[$levelid] = $level; |
2ae7faf1 MG |
152 | if ($maxscore === null || (float)$level['score'] > $maxscore) { |
153 | $maxscore = (float)$level['score']; | |
154 | } | |
155 | } else { | |
156 | $this->nonjsbuttonpressed = true; | |
c586d2bf MG |
157 | } |
158 | } | |
159 | } | |
2ae7faf1 | 160 | $totalscore += (float)$maxscore; |
c586d2bf | 161 | $criterion['levels'] = $levels; |
2ae7faf1 MG |
162 | if ($withvalidation && !array_key_exists('delete', $criterion)) { |
163 | if (count($levels)<2) { | |
164 | $errors['err_mintwolevels'] = 1; | |
165 | $criterion['error_levels'] = true; | |
166 | } | |
167 | if (empty($criterion['description'])) { | |
168 | $errors['err_nodescription'] = 1; | |
169 | $criterion['error_description'] = true; | |
170 | } | |
171 | } | |
c586d2bf MG |
172 | if (array_key_exists('moveup', $criterion) || $lastaction == 'movedown') { |
173 | unset($criterion['moveup']); | |
174 | if ($lastid !== null) { | |
39c6f4b6 MG |
175 | $lastcriterion = $return['criteria'][$lastid]; |
176 | unset($return['criteria'][$lastid]); | |
177 | $return['criteria'][$id] = $criterion; | |
178 | $return['criteria'][$lastid] = $lastcriterion; | |
c586d2bf | 179 | } else { |
39c6f4b6 | 180 | $return['criteria'][$id] = $criterion; |
c586d2bf MG |
181 | } |
182 | $lastaction = null; | |
183 | $lastid = $id; | |
2ae7faf1 | 184 | $this->nonjsbuttonpressed = true; |
c586d2bf | 185 | } else if (array_key_exists('delete', $criterion)) { |
2ae7faf1 | 186 | $this->nonjsbuttonpressed = true; |
c586d2bf MG |
187 | } else { |
188 | if (array_key_exists('movedown', $criterion)) { | |
189 | unset($criterion['movedown']); | |
190 | $lastaction = 'movedown'; | |
2ae7faf1 | 191 | $this->nonjsbuttonpressed = true; |
c586d2bf | 192 | } |
39c6f4b6 | 193 | $return['criteria'][$id] = $criterion; |
c586d2bf MG |
194 | $lastid = $id; |
195 | } | |
196 | } | |
2ae7faf1 MG |
197 | |
198 | if ($totalscore <= 0) { | |
199 | $errors['err_totalscore'] = 1; | |
200 | } | |
201 | ||
202 | // add sort order field to criteria | |
c586d2bf | 203 | $csortorder = 1; |
39c6f4b6 MG |
204 | foreach (array_keys($return['criteria']) as $id) { |
205 | $return['criteria'][$id]['sortorder'] = $csortorder++; | |
c586d2bf | 206 | } |
2ae7faf1 MG |
207 | |
208 | // create validation error string (if needed) | |
209 | if ($withvalidation) { | |
210 | if (count($errors)) { | |
211 | $rv = array(); | |
212 | foreach ($errors as $error => $v) { | |
213 | $rv[] = get_string($error, 'gradingform_rubric'); | |
214 | } | |
215 | $this->validationerrors = join('<br/ >', $rv); | |
216 | } else { | |
217 | $this->validationerrors = false; | |
218 | } | |
219 | $this->wasvalidated = true; | |
220 | } | |
c586d2bf MG |
221 | return $return; |
222 | } | |
223 | ||
224 | function get_next_id($ids) { | |
225 | $maxid = 0; | |
226 | foreach ($ids as $id) { | |
227 | if (preg_match('/^NEWID(\d+)$/', $id, $matches) && ((int)$matches[1]) > $maxid) { | |
228 | $maxid = (int)$matches[1]; | |
229 | } | |
230 | } | |
231 | return 'NEWID'.($maxid+1); | |
232 | } | |
233 | ||
2ae7faf1 MG |
234 | /** |
235 | * Checks if a submit button was pressed which is supposed to be processed on client side by JS | |
236 | * but user seem to have disabled JS in the browser. | |
237 | * (buttons 'add criteria', 'add level', 'move up', 'move down', etc.) | |
238 | * In this case the form containing this element is prevented from being submitted | |
239 | * | |
240 | * @param array $value | |
241 | * @return boolean true if non-submit button was pressed and not processed by JS | |
242 | */ | |
243 | function non_js_button_pressed($value) { | |
244 | if ($this->nonjsbuttonpressed === null) { | |
245 | $this->prepare_data($value); | |
c586d2bf | 246 | } |
2ae7faf1 | 247 | return $this->nonjsbuttonpressed; |
c586d2bf MG |
248 | } |
249 | ||
2ae7faf1 MG |
250 | /** |
251 | * Validates that rubric has at least one criterion, at least two levels within one criterion, | |
252 | * each level has a valid score, all levels have filled definitions and all criteria | |
253 | * have filled descriptions | |
254 | * | |
255 | * @param array $value | |
256 | * @return string|false error text or false if no errors found | |
257 | */ | |
258 | function validate($value) { | |
259 | if (!$this->wasvalidated) { | |
260 | $this->prepare_data($value, true); | |
c586d2bf | 261 | } |
2ae7faf1 | 262 | return $this->validationerrors; |
c586d2bf MG |
263 | } |
264 | ||
39c6f4b6 MG |
265 | /** |
266 | * Prepares the data for saving | |
2ae7faf1 | 267 | * @see prepare_data() |
39c6f4b6 MG |
268 | * |
269 | * @param array $submitValues | |
270 | * @param boolean $assoc | |
271 | * @return array | |
272 | */ | |
273 | function exportValue(&$submitValues, $assoc = false) { | |
2ae7faf1 | 274 | $value = $this->prepare_data($this->_findValue($submitValues)); |
39c6f4b6 MG |
275 | return $this->_prepareValue($value, $assoc); |
276 | } | |
c586d2bf | 277 | } |