MDL-52873 forms: Generate unique id attributes for modgrade elements
[moodle.git] / lib / form / modgrade.php
CommitLineData
c65795d3 1<?php
6c1fd304
RT
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/**
19 * Drop down form element to select the grade
20 *
21 * Contains HTML class for a drop down element to select the grade for an activity,
22 * used in mod update form
23 *
24 * @package core_form
25 * @copyright 2006 Jamie Pratt <me@jamiep.org>
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 */
28
c65795d3 29global $CFG;
30require_once "$CFG->libdir/form/select.php";
59766233
JM
31require_once("HTML/QuickForm/element.php");
32require_once($CFG->dirroot.'/lib/form/group.php');
33require_once($CFG->dirroot.'/lib/grade/grade_scale.php');
c65795d3 34
35/**
6c1fd304
RT
36 * Drop down form element to select the grade
37 *
c65795d3 38 * HTML class for a drop down element to select the grade for an activity,
39 * used in mod update form
40 *
6c1fd304
RT
41 * @package core_form
42 * @category form
43 * @copyright 2006 Jamie Pratt <me@jamiep.org>
44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
c65795d3 45 */
59766233 46class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
4a0e2e63 47
c65795d3 48 /**
59766233 49 * Constructor
c65795d3 50 *
59766233
JM
51 * @param string $elementname Element's name
52 * @param mixed $elementlabel Label(s) for an element
53 * @param array $options Options to control the element's display. Not used.
54 * @param mixed $attributes Either a typical HTML attribute string or an associative array
c65795d3 55 */
1a0df553 56 public function __construct($elementname = null, $elementlabel = null, $options = array(), $attributes = null) {
32fada5c 57 // TODO MDL-52313 Replace with the call to parent::__construct().
1a0df553 58 HTML_QuickForm_element::__construct($elementname, $elementlabel, $attributes);
59766233
JM
59 $this->_persistantFreeze = true;
60 $this->_appendName = true;
c65795d3 61 $this->_type = 'modgrade';
59766233
JM
62 }
63
1a0df553
MG
64 /**
65 * Old syntax of class constructor. Deprecated in PHP7.
66 *
67 * @deprecated since Moodle 3.1
68 */
69 public function MoodleQuickForm_modgrade($elementname = null, $elementlabel = null, $options = array(), $attributes = null) {
70 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
71 self::__construct($elementname, $elementlabel, $options, $attributes);
72 }
73
59766233
JM
74 /**
75 * Create elements for this group.
76 */
77 public function _createElements() {
78 global $COURSE, $CFG;
79 $attributes = $this->getAttributes();
80 if (is_null($attributes)) {
81 $attributes = array();
82 }
83
84 $this->_elements = array();
85
86 // Create main elements
87 // We have to create the scale and point elements first, as we need their IDs.
88
89 // Grade scale select box.
90 $scales = get_scales_menu($COURSE->id);
91 $langscale = get_string('modgradetypescale', 'grades');
92 $scaleselect = @MoodleQuickForm::createElement('select', 'modgrade_scale', $langscale, $scales, $attributes);
93 $scaleselect->setHiddenLabel = false;
d84c64b7
DM
94 $scaleselectid = $this->generate_modgrade_subelement_id('modgrade_scale');
95 $scaleselect->updateAttributes(array('id' => $scaleselectid));
59766233
JM
96
97 // Maximum grade textbox.
98 $langmaxgrade = get_string('modgrademaxgrade', 'grades');
99 $maxgrade = @MoodleQuickForm::createElement('text', 'modgrade_point', $langmaxgrade, array());
100 $maxgrade->setHiddenLabel = false;
d84c64b7
DM
101 $maxgradeid = $this->generate_modgrade_subelement_id('modgrade_point');
102 $maxgrade->updateAttributes(array('id' => $maxgradeid));
59766233
JM
103
104 // Grade type select box.
105 $gradetype = array(
106 'none' => get_string('modgradetypenone', 'grades'),
107 'scale' => get_string('modgradetypescale', 'grades'),
108 'point' => get_string('modgradetypepoint', 'grades'),
109 );
110 $langtype = get_string('modgradetype', 'grades');
111 $typeselect = @MoodleQuickForm::createElement('select', 'modgrade_type', $langtype, $gradetype, $attributes, true);
112 $typeselect->setHiddenLabel = false;
d84c64b7
DM
113 $typeselectid = $this->generate_modgrade_subelement_id('modgrade_type');
114 $typeselect->updateAttributes(array('id' => $typeselectid));
59766233
JM
115
116 // Add elements.
117
118 // Grade type select box.
119 $label = html_writer::tag('label', $typeselect->getLabel(), array('for' => $typeselect->getAttribute('id')));
120 $this->_elements[] = @MoodleQuickForm::createElement('static', 'gradetypelabel', '', '&nbsp;'.$label);
121 $this->_elements[] = $typeselect;
122 $this->_elements[] = @MoodleQuickForm::createElement('static', 'gradetypespacer', '', '<br />');
c65795d3 123
59766233
JM
124 // Grade scale select box.
125 $label = html_writer::tag('label', $scaleselect->getLabel(), array('for' => $scaleselectid));
126 $this->_elements[] = @MoodleQuickForm::createElement('static', 'scalelabel', '', $label);
127 $this->_elements[] = $scaleselect;
128 $this->_elements[] = @MoodleQuickForm::createElement('static', 'scalespacer', '', '<br />');
129
130 // Maximum grade textbox.
131 $label = html_writer::tag('label', $maxgrade->getLabel(), array('for' => $maxgradeid));
132 $this->_elements[] = @MoodleQuickForm::createElement('static', 'pointlabel', '', $label);
133 $this->_elements[] = $maxgrade;
134 $this->_elements[] = @MoodleQuickForm::createElement('static', 'pointspacer', '', '<br />');
6c1fd304 135 }
c65795d3 136
137 /**
59766233
JM
138 * Calculate the output value for the element as a whole.
139 *
140 * @param array $submitvalues The incoming values from the form.
141 * @param bool $notused Not used.
142 * @return array Return value for the element, formatted like field name => value.
143 */
144 public function exportValue(&$submitvalues, $notused = false) {
145 global $COURSE;
146
147 // Get the values from all the child elements.
148 $vals = array();
149 foreach ($this->_elements as $element) {
150 $thisexport = $element->exportValue($submitvalues[$this->getName()], true);
151 if (!is_null($thisexport)) {
152 $vals += $thisexport;
153 }
154 }
155
156 $type = (isset($vals['modgrade_type'])) ? $vals['modgrade_type'] : 'none';
157 $point = (isset($vals['modgrade_point'])) ? $vals['modgrade_point'] : null;
158 $scale = (isset($vals['modgrade_scale'])) ? $vals['modgrade_scale'] : null;
159 $return = $this->process_value($type, $scale, $point);
160 return array($this->getName() => $return);
161 }
162
163 /**
164 * Process the value for the group based on the selected grade type, and the input for the scale and point elements.
165 *
166 * @param string $type The value of the grade type select box. Can be 'none', 'scale', or 'point'
167 * @param string|int $scale The value of the scale select box.
168 * @param string|int $point The value of the point grade textbox.
169 * @return int The resulting value
170 */
171 protected function process_value($type='none', $scale=null, $point=null) {
172 global $COURSE;
173 $val = 0;
174 switch ($type) {
175 case 'point':
176 if ($this->validate_point($point) === true) {
177 $val = (int)$point;
178 }
179 break;
180
181 case 'scale':
182 if ($this->validate_scale($scale)) {
183 $val = (int)(-$scale);
184 }
185 break;
186 }
187 return $val;
188 }
189
190 /**
191 * Determines whether a given value is a valid scale selection.
192 *
193 * @param string|int $val The value to test.
194 * @return bool Valid or invalid
195 */
196 protected function validate_scale($val) {
197 global $COURSE;
198 $scales = get_scales_menu($COURSE->id);
199 return (!empty($val) && isset($scales[(int)$val])) ? true : false;
200 }
201
202 /**
203 * Determines whether a given value is a valid point selection.
204 *
205 * @param string|int $val The value to test.
206 * @return bool Valid or invalid
207 */
208 protected function validate_point($val) {
209 if (empty($val)) {
210 return false;
211 }
212 $maxgrade = (int)get_config('core', 'gradepointmax');
213 $isintlike = ((string)(int)$val === $val) ? true : false;
214 return ($isintlike === true && $val > 0 && $val <= $maxgrade) ? true : false;
215 }
216
217 /**
218 * Called by HTML_QuickForm whenever form event is made on this element.
c65795d3 219 *
6c1fd304
RT
220 * @param string $event Name of event
221 * @param mixed $arg event arguments
ed7605f4 222 * @param moodleform $caller calling object
6c1fd304 223 * @return mixed
c65795d3 224 */
59766233 225 public function onQuickFormEvent($event, $arg, &$caller) {
c65795d3 226 switch ($event) {
ed7605f4
SH
227 case 'createElement':
228 // The first argument is the name.
229 $name = $arg[0];
230
231 // Set disable actions.
232 $caller->disabledIf($name.'[modgrade_scale]', $name.'[modgrade_type]', 'neq', 'scale');
233 $caller->disabledIf($name.'[modgrade_point]', $name.'[modgrade_type]', 'neq', 'point');
234
235 // Set validation rules for the sub-elements belonging to this element.
236 // A handy note: the parent scope of a closure is the function in which the closure was declared.
237 // Because of this using $this is safe despite the closures being called statically.
238 // A nasty magic hack!
239 $checkmaxgrade = function($val) {
240 // Closure to validate a max points value. See the note above about scope if this confuses you.
241 if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'point') {
242 if (!isset($val['modgrade_point'])) {
243 return false;
244 }
245 return $this->validate_point($val['modgrade_point']);
246 }
247 return true;
248 };
249 $checkvalidscale = function($val) {
250 // Closure to validate a scale value. See the note above about scope if this confuses you.
251 if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'scale') {
252 if (!isset($val['modgrade_scale'])) {
253 return false;
254 }
255 return $this->validate_scale($val['modgrade_scale']);
256 }
257 return true;
258 };
259
260 $maxgradeexceeded = get_string('modgradeerrorbadpoint', 'grades', get_config('core', 'gradepointmax'));
261 $invalidscale = get_string('modgradeerrorbadscale', 'grades');
262 // When creating the rules the sixth arg is $force, we set it to true because otherwise the form
263 // will attempt to validate the existence of the element, we don't want this because the element
264 // is being created right now and doesn't actually exist as a registered element yet.
265 $caller->addRule($name, $maxgradeexceeded, 'callback', $checkmaxgrade, 'server', false, true);
266 $caller->addRule($name, $invalidscale, 'callback', $checkvalidscale, 'server', false, true);
267
268 break;
269
59766233 270 case 'updateValue':
ed7605f4
SH
271 // As this is a group element with no value of its own we are only interested in situations where the
272 // default value or a constant value are being provided to the actual element.
273 // In this case we expect an int that is going to translate to a scale if negative, or to max points
274 // if positive.
275
276 // A constant value should be given as an int.
277 // The default value should be an int and should really be $CFG->gradepointdefault.
59766233
JM
278 $value = $this->_findValue($caller->_constantValues);
279 if (null === $value) {
280 if ($caller->isSubmitted()) {
ed7605f4 281 break;
59766233 282 }
ed7605f4 283 $value = $this->_findValue($caller->_defaultValues);
c65795d3 284 }
59766233 285
ed7605f4
SH
286 if (!is_null($value) && !is_scalar($value)) {
287 // Something unexpected (likely an array of subelement values) has been given - this will be dealt
288 // with somewhere else - where exactly... likely the subelements.
289 debugging('An invalid value (type '.gettype($value).') has arrived at '.__METHOD__, DEBUG_DEVELOPER);
290 break;
291 }
59766233
JM
292
293 // Set element state for existing data.
ed7605f4
SH
294 // This is really a pretty hacky thing to do, when data is being set the group element is called
295 // with the data first and the subelements called afterwards.
296 // This means that the subelements data (inc const and default values) can be overridden by form code.
297 // So - when we call this code really we can't be sure that will be the end value for the element.
59766233
JM
298 if (!empty($this->_elements)) {
299 if (!empty($value)) {
300 if ($value < 0) {
301 $this->_elements[1]->setValue('scale');
302 $this->_elements[4]->setValue(($value * -1));
303 } else if ($value > 0) {
304 $this->_elements[1]->setValue('point');
305 $this->_elements[7]->setValue($value);
306 }
307 } else {
308 $this->_elements[1]->setValue('none');
309 $this->_elements[7]->setValue('');
310 }
c65795d3 311 }
59766233 312 break;
c65795d3 313 }
59766233 314
ed7605f4 315 // Always let the parent do its thing!
c65795d3 316 return parent::onQuickFormEvent($event, $arg, $caller);
317 }
318
d84c64b7
DM
319 /**
320 * Generates the id attribute for the subelement of the modgrade group.
321 *
322 * Uses algorithm similar to what {@link HTML_QuickForm_element::_generateId()}
323 * does but takes the name of the wrapping modgrade group into account.
324 *
325 * @param string $subname the name of the HTML_QuickForm_element in this modgrade group
326 * @return string
327 */
328 protected function generate_modgrade_subelement_id($subname) {
329 $gid = str_replace(array('[', ']'), array('_', ''), $this->getName());
330 return clean_param('id_'.$gid.'_'.$subname, PARAM_ALPHANUMEXT);
331 }
c65795d3 332}