See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 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 29 global $CFG; 30 require_once "$CFG->libdir/form/select.php"; 31 require_once("HTML/QuickForm/element.php"); 32 require_once($CFG->dirroot.'/lib/form/group.php'); 33 require_once($CFG->dirroot.'/lib/grade/grade_scale.php'); 34 35 /** 36 * Drop down form element to select the grade 37 * 38 * HTML class for a drop down element to select the grade for an activity, 39 * used in mod update form 40 * 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 45 */ 46 class MoodleQuickForm_modgrade extends MoodleQuickForm_group { 47 48 /** @var boolean $isupdate Is this an add or an update ? */ 49 public $isupdate = false; 50 51 /** @var float $currentgrade The current grademax for the grade_item */ 52 public $currentgrade = false; 53 54 /** @var boolean $hasgrades Has this grade_item got any real grades (with values) */ 55 public $hasgrades = false; 56 57 /** @var boolean $canrescale Does this activity support rescaling grades? */ 58 public $canrescale = false; 59 60 /** @var int $currentscaleid The current scale id */ 61 public $currentscaleid = null; 62 63 /** @var string $currentgradetype The current gradetype - can either be 'none', 'scale', or 'point' */ 64 public $currentgradetype = 'none'; 65 66 /** @var boolean $useratings Set to true if the activity is using ratings, false otherwise */ 67 public $useratings = false; 68 69 /** @var MoodleQuickForm_select $gradetypeformelement */ 70 private $gradetypeformelement; 71 72 /** @var MoodleQuickForm_select $scaleformelement */ 73 private $scaleformelement; 74 75 /** @var MoodleQuickForm_text $maxgradeformelement */ 76 private $maxgradeformelement; 77 78 /** 79 * Constructor 80 * 81 * @param string $elementname Element's name 82 * @param mixed $elementlabel Label(s) for an element 83 * @param array $options Options to control the element's display. Required - must contain the following options: 84 * 'isupdate' - is this a new module or are we editing an existing one? 85 * 'currentgrade' - the current grademax in the database for this gradeitem 86 * 'hasgrades' - whether or not the grade_item has existing grade_grades 87 * 'canrescale' - whether or not the activity supports rescaling grades 88 * @param mixed $attributes Either a typical HTML attribute string or an associative array 89 */ 90 public function __construct($elementname = null, $elementlabel = null, $options = array(), $attributes = null) { 91 // TODO MDL-52313 Replace with the call to parent::__construct(). 92 HTML_QuickForm_element::__construct($elementname, $elementlabel, $attributes); 93 $this->_persistantFreeze = true; 94 $this->_appendName = true; 95 $this->_type = 'modgrade'; 96 $this->isupdate = !empty($options['isupdate']); 97 if (isset($options['currentgrade'])) { 98 $this->currentgrade = $options['currentgrade']; 99 } 100 if (isset($options['currentgradetype'])) { 101 $gradetype = $options['currentgradetype']; 102 switch ($gradetype) { 103 case GRADE_TYPE_NONE : 104 $this->currentgradetype = 'none'; 105 break; 106 case GRADE_TYPE_SCALE : 107 $this->currentgradetype = 'scale'; 108 break; 109 case GRADE_TYPE_VALUE : 110 $this->currentgradetype = 'point'; 111 break; 112 } 113 } 114 if (isset($options['currentscaleid'])) { 115 $this->currentscaleid = $options['currentscaleid']; 116 } 117 $this->hasgrades = !empty($options['hasgrades']); 118 $this->canrescale = !empty($options['canrescale']); 119 $this->useratings = !empty($options['useratings']); 120 } 121 122 /** 123 * Old syntax of class constructor. Deprecated in PHP7. 124 * 125 * @deprecated since Moodle 3.1 126 */ 127 public function MoodleQuickForm_modgrade($elementname = null, $elementlabel = null, $options = array(), $attributes = null) { 128 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 129 self::__construct($elementname, $elementlabel, $options, $attributes); 130 } 131 132 /** 133 * Create elements for this group. 134 */ 135 public function _createElements() { 136 global $COURSE, $CFG, $OUTPUT; 137 $attributes = $this->getAttributes(); 138 if (is_null($attributes)) { 139 $attributes = array(); 140 } 141 142 $this->_elements = array(); 143 144 // Create main elements 145 // We have to create the scale and point elements first, as we need their IDs. 146 147 // Grade scale select box. 148 $scales = get_scales_menu($COURSE->id); 149 $langscale = get_string('modgradetypescale', 'grades'); 150 $this->scaleformelement = $this->createFormElement('select', 'modgrade_scale', $langscale, 151 $scales, $attributes); 152 $this->scaleformelement->setHiddenLabel(true); 153 $scaleformelementid = $this->generate_modgrade_subelement_id('modgrade_scale'); 154 $this->scaleformelement->updateAttributes(array('id' => $scaleformelementid)); 155 156 // Maximum grade textbox. 157 $langmaxgrade = get_string('modgrademaxgrade', 'grades'); 158 $this->maxgradeformelement = $this->createFormElement('text', 'modgrade_point', $langmaxgrade, array()); 159 $this->maxgradeformelement->setHiddenLabel(true); 160 $maxgradeformelementid = $this->generate_modgrade_subelement_id('modgrade_point'); 161 $this->maxgradeformelement->updateAttributes(array('id' => $maxgradeformelementid)); 162 163 // Grade type select box. 164 $gradetype = array( 165 'none' => get_string('modgradetypenone', 'grades'), 166 'scale' => get_string('modgradetypescale', 'grades'), 167 'point' => get_string('modgradetypepoint', 'grades'), 168 ); 169 $langtype = get_string('modgradetype', 'grades'); 170 $this->gradetypeformelement = $this->createFormElement('select', 'modgrade_type', $langtype, $gradetype, 171 $attributes, true); 172 $this->gradetypeformelement->setHiddenLabel(true); 173 $gradetypeformelementid = $this->generate_modgrade_subelement_id('modgrade_type'); 174 $this->gradetypeformelement->updateAttributes(array('id' => $gradetypeformelementid)); 175 176 if ($this->isupdate && $this->hasgrades) { 177 $this->gradetypeformelement->updateAttributes(array('disabled' => 'disabled')); 178 $this->scaleformelement->updateAttributes(array('disabled' => 'disabled')); 179 180 // Check box for options for processing existing grades. 181 if ($this->canrescale) { 182 $langrescalegrades = get_string('modgraderescalegrades', 'grades'); 183 $choices = array(); 184 $choices[''] = get_string('choose'); 185 $choices['no'] = get_string('no'); 186 $choices['yes'] = get_string('yes'); 187 $rescalegradesselect = $this->createFormElement('select', 188 'modgrade_rescalegrades', 189 $langrescalegrades, 190 $choices); 191 $rescalegradesselect->setHiddenLabel(true); 192 $rescalegradesselectid = $this->generate_modgrade_subelement_id('modgrade_rescalegrades'); 193 $rescalegradesselect->updateAttributes(array('id' => $rescalegradesselectid)); 194 } 195 } 196 197 // Add elements. 198 if ($this->isupdate && $this->hasgrades) { 199 // Set a message so the user knows why they can not alter the grade type or scale. 200 if ($this->currentgradetype == 'scale') { 201 $gradesexistmsg = get_string('modgradecantchangegradetyporscalemsg', 'grades'); 202 } else if ($this->canrescale) { 203 $gradesexistmsg = get_string('modgradecantchangegradetypemsg', 'grades'); 204 } else { 205 $gradesexistmsg = get_string('modgradecantchangegradetype', 'grades'); 206 } 207 208 $gradesexisthtml = '<div class=\'alert alert-warning\'>' . $gradesexistmsg . '</div>'; 209 $this->_elements[] = $this->createFormElement('static', 'gradesexistmsg', '', $gradesexisthtml); 210 } 211 212 // Grade type select box. 213 $label = html_writer::tag('label', $this->gradetypeformelement->getLabel(), 214 array('for' => $this->gradetypeformelement->getAttribute('id'))); 215 $this->_elements[] = $this->createFormElement('static', 'gradetypelabel', '', ' '.$label); 216 $this->_elements[] = $this->gradetypeformelement; 217 $this->_elements[] = $this->createFormElement('static', 'gradetypespacer', '', '<br />'); 218 219 // Only show the grade scale select box when applicable. 220 if (!$this->isupdate || !$this->hasgrades || $this->currentgradetype == 'scale') { 221 $label = html_writer::tag('label', $this->scaleformelement->getLabel(), 222 array('for' => $this->scaleformelement->getAttribute('id'))); 223 $this->_elements[] = $this->createFormElement('static', 'scalelabel', '', $label); 224 $this->_elements[] = $this->scaleformelement; 225 $this->_elements[] = $this->createFormElement('static', 'scalespacer', '', '<br />'); 226 } 227 228 if ($this->isupdate && $this->hasgrades && $this->canrescale && $this->currentgradetype == 'point') { 229 // We need to know how to apply any changes to maxgrade - ie to either update, or don't touch exising grades. 230 $label = html_writer::tag('label', $rescalegradesselect->getLabel(), 231 array('for' => $rescalegradesselect->getAttribute('id'))); 232 $labelhelp = new help_icon('modgraderescalegrades', 'grades'); 233 $this->_elements[] = $this->createFormElement('static', 'scalelabel', '', $label . $OUTPUT->render($labelhelp)); 234 $this->_elements[] = $rescalegradesselect; 235 $this->_elements[] = $this->createFormElement('static', 'scalespacer', '', '<br />'); 236 } 237 238 // Only show the max points form element when applicable. 239 if (!$this->isupdate || !$this->hasgrades || $this->currentgradetype == 'point') { 240 $label = html_writer::tag('label', $this->maxgradeformelement->getLabel(), 241 array('for' => $this->maxgradeformelement->getAttribute('id'))); 242 $this->_elements[] = $this->createFormElement('static', 'pointlabel', '', $label); 243 $this->_elements[] = $this->maxgradeformelement; 244 $this->_elements[] = $this->createFormElement('static', 'pointspacer', '', '<br />'); 245 } 246 } 247 248 /** 249 * Calculate the output value for the element as a whole. 250 * 251 * @param array $submitvalues The incoming values from the form. 252 * @param bool $notused Not used. 253 * @return array Return value for the element, formatted like field name => value. 254 */ 255 public function exportValue(&$submitvalues, $notused = false) { 256 global $COURSE; 257 258 // Get the values from all the child elements. 259 $vals = array(); 260 foreach ($this->_elements as $element) { 261 $thisexport = $element->exportValue($submitvalues[$this->getName()], true); 262 if (!is_null($thisexport)) { 263 $vals += $thisexport; 264 } 265 } 266 267 $type = (isset($vals['modgrade_type'])) ? $vals['modgrade_type'] : 'none'; 268 $point = (isset($vals['modgrade_point'])) ? $vals['modgrade_point'] : null; 269 $scale = (isset($vals['modgrade_scale'])) ? $vals['modgrade_scale'] : null; 270 $rescalegrades = (isset($vals['modgrade_rescalegrades'])) ? $vals['modgrade_rescalegrades'] : null; 271 272 $return = $this->process_value($type, $scale, $point, $rescalegrades); 273 return array($this->getName() => $return, $this->getName() . '_rescalegrades' => $rescalegrades); 274 } 275 276 /** 277 * Process the value for the group based on the selected grade type, and the input for the scale and point elements. 278 * 279 * @param string $type The value of the grade type select box. Can be 'none', 'scale', or 'point' 280 * @param string|int $scale The value of the scale select box. 281 * @param string|int $point The value of the point grade textbox. 282 * @param string $rescalegrades The value of the rescalegrades select. 283 * @return int The resulting value 284 */ 285 protected function process_value($type='none', $scale=null, $point=null, $rescalegrades=null) { 286 global $COURSE; 287 $val = 0; 288 289 // If the maxgrade field is disabled with javascript, no value is sent with the form and mform assumes the default. 290 if ($this->isupdate && $this->hasgrades && $this->currentgradetype == 'point') { 291 // If the user cannot rescale, then return the original. 292 $returnoriginal = !$this->canrescale; 293 294 // If the user was forced to choose a rescale option - and they haven't - prevent any changes to the max grade. 295 $returnoriginal = $returnoriginal || ($this->canrescale && empty($rescalegrades)); 296 297 if ($returnoriginal) { 298 return (string)unformat_float($this->currentgrade); 299 } 300 } 301 switch ($type) { 302 case 'point': 303 if ($this->validate_point($point) === true) { 304 $val = (int)$point; 305 } 306 break; 307 308 case 'scale': 309 if ($this->validate_scale($scale)) { 310 $val = (int)(-$scale); 311 } 312 break; 313 } 314 return $val; 315 } 316 317 /** 318 * Determines whether a given value is a valid scale selection. 319 * 320 * @param string|int $val The value to test. 321 * @return bool Valid or invalid 322 */ 323 protected function validate_scale($val) { 324 global $COURSE; 325 $scales = get_scales_menu($COURSE->id); 326 return (!empty($val) && isset($scales[(int)$val])) ? true : false; 327 } 328 329 /** 330 * Determines whether a given value is a valid point selection. 331 * 332 * @param string|int $val The value to test. 333 * @return bool Valid or invalid 334 */ 335 protected function validate_point($val) { 336 if (empty($val)) { 337 return false; 338 } 339 $maxgrade = (int)get_config('core', 'gradepointmax'); 340 $isintlike = ((string)(int)$val === $val) ? true : false; 341 return ($isintlike === true && $val > 0 && $val <= $maxgrade) ? true : false; 342 } 343 344 /** 345 * Called by HTML_QuickForm whenever form event is made on this element. 346 * 347 * @param string $event Name of event 348 * @param mixed $arg event arguments 349 * @param moodleform $caller calling object 350 * @return mixed 351 */ 352 public function onQuickFormEvent($event, $arg, &$caller) { 353 $this->setMoodleForm($caller); 354 switch ($event) { 355 case 'createElement': 356 // The first argument is the name. 357 $name = $arg[0]; 358 359 // Set disable actions. 360 $caller->hideIf($name.'[modgrade_scale]', $name.'[modgrade_type]', 'neq', 'scale'); 361 $caller->hideIf($name.'[modgrade_point]', $name.'[modgrade_type]', 'neq', 'point'); 362 $caller->hideIf($name.'[modgrade_rescalegrades]', $name.'[modgrade_type]', 'neq', 'point'); 363 364 // Set validation rules for the sub-elements belonging to this element. 365 // A handy note: the parent scope of a closure is the function in which the closure was declared. 366 // Because of this using $this is safe despite the closures being called statically. 367 // A nasty magic hack! 368 $checkgradetypechange = function($val) { 369 // Nothing is affected by changes to the grade type if there are no grades yet. 370 if (!$this->hasgrades) { 371 return true; 372 } 373 // Check if we are changing the grade type when grades are present. 374 if (isset($val['modgrade_type']) && $val['modgrade_type'] !== $this->currentgradetype) { 375 return false; 376 } 377 return true; 378 }; 379 $checkscalechange = function($val) { 380 // Nothing is affected by changes to the scale if there are no grades yet. 381 if (!$this->hasgrades) { 382 return true; 383 } 384 // Check if we are changing the scale type when grades are present. 385 // If modgrade_type is empty then use currentgradetype. 386 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 387 if ($gradetype === 'scale') { 388 if (isset($val['modgrade_scale']) && ($val['modgrade_scale'] !== $this->currentscaleid)) { 389 return false; 390 } 391 } 392 return true; 393 }; 394 $checkmaxgradechange = function($val) { 395 // Nothing is affected by changes to the max grade if there are no grades yet. 396 if (!$this->hasgrades) { 397 return true; 398 } 399 // If we are not using ratings we can change the max grade. 400 if (!$this->useratings) { 401 return true; 402 } 403 // Check if we are changing the max grade if we are using ratings and there is a grade. 404 // If modgrade_type is empty then use currentgradetype. 405 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 406 if ($gradetype === 'point') { 407 if (isset($val['modgrade_point']) && 408 grade_floats_different($this->currentgrade, $val['modgrade_point'])) { 409 return false; 410 } 411 } 412 return true; 413 }; 414 $checkmaxgrade = function($val) { 415 // Closure to validate a max points value. See the note above about scope if this confuses you. 416 // If modgrade_type is empty then use currentgradetype. 417 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 418 if ($gradetype === 'point') { 419 if (isset($val['modgrade_point'])) { 420 return $this->validate_point($val['modgrade_point']); 421 } 422 } 423 return true; 424 }; 425 $checkvalidscale = function($val) { 426 // Closure to validate a scale value. See the note above about scope if this confuses you. 427 // If modgrade_type is empty then use currentgradetype. 428 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 429 if ($gradetype === 'scale') { 430 if (isset($val['modgrade_scale'])) { 431 return $this->validate_scale($val['modgrade_scale']); 432 } 433 } 434 return true; 435 }; 436 437 $checkrescale = function($val) { 438 // Nothing is affected by changes to grademax if there are no grades yet. 439 if (!$this->isupdate || !$this->hasgrades || !$this->canrescale) { 440 return true; 441 } 442 // Closure to validate a scale value. See the note above about scope if this confuses you. 443 // If modgrade_type is empty then use currentgradetype. 444 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 445 if ($gradetype === 'point' && isset($val['modgrade_point'])) { 446 // Work out if the value was actually changed in the form. 447 if (grade_floats_different($this->currentgrade, $val['modgrade_point'])) { 448 if (empty($val['modgrade_rescalegrades'])) { 449 // This was an "edit", the grademax was changed and the process existing setting was not set. 450 return false; 451 } 452 } 453 } 454 return true; 455 }; 456 457 $cantchangegradetype = get_string('modgradecantchangegradetype', 'grades'); 458 $cantchangemaxgrade = get_string('modgradecantchangeratingmaxgrade', 'grades'); 459 $maxgradeexceeded = get_string('modgradeerrorbadpoint', 'grades', get_config('core', 'gradepointmax')); 460 $invalidscale = get_string('modgradeerrorbadscale', 'grades'); 461 $cantchangescale = get_string('modgradecantchangescale', 'grades'); 462 $mustchooserescale = get_string('mustchooserescaleyesorno', 'grades'); 463 // When creating the rules the sixth arg is $force, we set it to true because otherwise the form 464 // will attempt to validate the existence of the element, we don't want this because the element 465 // is being created right now and doesn't actually exist as a registered element yet. 466 $caller->addRule($name, $cantchangegradetype, 'callback', $checkgradetypechange, 'server', false, true); 467 $caller->addRule($name, $cantchangemaxgrade, 'callback', $checkmaxgradechange, 'server', false, true); 468 $caller->addRule($name, $maxgradeexceeded, 'callback', $checkmaxgrade, 'server', false, true); 469 $caller->addRule($name, $invalidscale, 'callback', $checkvalidscale, 'server', false, true); 470 $caller->addRule($name, $cantchangescale, 'callback', $checkscalechange, 'server', false, true); 471 $caller->addRule($name, $mustchooserescale, 'callback', $checkrescale, 'server', false, true); 472 473 break; 474 475 case 'updateValue': 476 // As this is a group element with no value of its own we are only interested in situations where the 477 // default value or a constant value are being provided to the actual element. 478 // In this case we expect an int that is going to translate to a scale if negative, or to max points 479 // if positive. 480 481 // Set the maximum points field to disabled if the rescale option has not been chosen and there are grades. 482 $caller->disabledIf($this->getName() . '[modgrade_point]', $this->getName() . 483 '[modgrade_rescalegrades]', 'eq', ''); 484 485 // A constant value should be given as an int. 486 // The default value should be an int and be either $CFG->gradepointdefault or whatever was set in set_data(). 487 $value = $this->_findValue($caller->_constantValues); 488 if (null === $value) { 489 if ($caller->isSubmitted() && $this->_findValue($caller->_submitValues) !== null) { 490 // Submitted values are array, one value for each individual element in this group. 491 // When there is submitted data let parent::onQuickFormEvent() process it. 492 break; 493 } 494 $value = $this->_findValue($caller->_defaultValues); 495 } 496 497 if (!is_null($value) && !is_scalar($value)) { 498 // Something unexpected (likely an array of subelement values) has been given - this will be dealt 499 // with somewhere else - where exactly... likely the subelements. 500 debugging('An invalid value (type '.gettype($value).') has arrived at '.__METHOD__, DEBUG_DEVELOPER); 501 break; 502 } 503 504 // Set element state for existing data. 505 // This is really a pretty hacky thing to do, when data is being set the group element is called 506 // with the data first and the subelements called afterwards. 507 // This means that the subelements data (inc const and default values) can be overridden by form code. 508 // So - when we call this code really we can't be sure that will be the end value for the element. 509 if (!empty($this->_elements)) { 510 if (!empty($value)) { 511 if ($value < 0) { 512 $this->gradetypeformelement->setValue('scale'); 513 $this->scaleformelement->setValue(($value * -1)); 514 } else if ($value > 0) { 515 $this->gradetypeformelement->setValue('point'); 516 $maxvalue = !empty($this->currentgrade) ? (string)unformat_float($this->currentgrade) : $value; 517 $this->maxgradeformelement->setValue($maxvalue); 518 } 519 } else { 520 $this->gradetypeformelement->setValue('none'); 521 $this->maxgradeformelement->setValue(100); 522 } 523 } 524 break; 525 } 526 527 // Always let the parent do its thing! 528 return parent::onQuickFormEvent($event, $arg, $caller); 529 } 530 531 /** 532 * Generates the id attribute for the subelement of the modgrade group. 533 * 534 * Uses algorithm similar to what {@link HTML_QuickForm_element::_generateId()} 535 * does but takes the name of the wrapping modgrade group into account. 536 * 537 * @param string $subname the name of the HTML_QuickForm_element in this modgrade group 538 * @return string 539 */ 540 protected function generate_modgrade_subelement_id($subname) { 541 $gid = str_replace(array('[', ']'), array('_', ''), $this->getName()); 542 return clean_param('id_'.$gid.'_'.$subname, PARAM_ALPHANUMEXT); 543 } 544 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body