See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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 244 // Check if there are grades and if so then disable maxgradeformelement. 245 if ($this->hasgrades) { 246 // If it has grades then disable it. 247 $this->maxgradeformelement->updateAttributes(['disabled' => 'disabled']); 248 } 249 250 $this->_elements[] = $this->maxgradeformelement; 251 $this->_elements[] = $this->createFormElement('static', 'pointspacer', '', '<br />'); 252 } 253 } 254 255 /** 256 * Calculate the output value for the element as a whole. 257 * 258 * @param array $submitvalues The incoming values from the form. 259 * @param bool $notused Not used. 260 * @return array Return value for the element, formatted like field name => value. 261 */ 262 public function exportValue(&$submitvalues, $notused = false) { 263 global $COURSE; 264 265 // Get the values from all the child elements. 266 $vals = array(); 267 foreach ($this->_elements as $element) { 268 $thisexport = $element->exportValue($submitvalues[$this->getName()], true); 269 if (!is_null($thisexport)) { 270 $vals += $thisexport; 271 } 272 } 273 274 $type = (isset($vals['modgrade_type'])) ? $vals['modgrade_type'] : 'none'; 275 $point = (isset($vals['modgrade_point'])) ? $vals['modgrade_point'] : null; 276 $scale = (isset($vals['modgrade_scale'])) ? $vals['modgrade_scale'] : null; 277 $rescalegrades = (isset($vals['modgrade_rescalegrades'])) ? $vals['modgrade_rescalegrades'] : null; 278 279 $return = $this->process_value($type, $scale, $point, $rescalegrades); 280 return array($this->getName() => $return, $this->getName() . '_rescalegrades' => $rescalegrades); 281 } 282 283 /** 284 * Process the value for the group based on the selected grade type, and the input for the scale and point elements. 285 * 286 * @param string $type The value of the grade type select box. Can be 'none', 'scale', or 'point' 287 * @param string|int $scale The value of the scale select box. 288 * @param string|int $point The value of the point grade textbox. 289 * @param string $rescalegrades The value of the rescalegrades select. 290 * @return int The resulting value 291 */ 292 protected function process_value($type='none', $scale=null, $point=null, $rescalegrades=null) { 293 global $COURSE; 294 $val = 0; 295 296 // If the maxgrade field is disabled with javascript, no value is sent with the form and mform assumes the default. 297 if ($this->isupdate && $this->hasgrades && $this->currentgradetype == 'point') { 298 // If the user cannot rescale, then return the original. 299 $returnoriginal = !$this->canrescale; 300 301 // If the user was forced to choose a rescale option - and they haven't - prevent any changes to the max grade. 302 $returnoriginal = $returnoriginal || ($this->canrescale && empty($rescalegrades)); 303 304 if ($returnoriginal) { 305 return (string)unformat_float($this->currentgrade); 306 } 307 } 308 switch ($type) { 309 case 'point': 310 if ($this->validate_point($point) === true) { 311 $val = (int)$point; 312 } 313 break; 314 315 case 'scale': 316 if ($this->validate_scale($scale)) { 317 $val = (int)(-$scale); 318 } 319 break; 320 } 321 return $val; 322 } 323 324 /** 325 * Determines whether a given value is a valid scale selection. 326 * 327 * @param string|int $val The value to test. 328 * @return bool Valid or invalid 329 */ 330 protected function validate_scale($val) { 331 global $COURSE; 332 $scales = get_scales_menu($COURSE->id); 333 return (!empty($val) && isset($scales[(int)$val])) ? true : false; 334 } 335 336 /** 337 * Determines whether a given value is a valid point selection. 338 * 339 * @param string|int $val The value to test. 340 * @return bool Valid or invalid 341 */ 342 protected function validate_point($val) { 343 if (empty($val)) { 344 return false; 345 } 346 $maxgrade = (int)get_config('core', 'gradepointmax'); 347 $isintlike = ((string)(int)$val === $val) ? true : false; 348 return ($isintlike === true && $val > 0 && $val <= $maxgrade) ? true : false; 349 } 350 351 /** 352 * Called by HTML_QuickForm whenever form event is made on this element. 353 * 354 * @param string $event Name of event 355 * @param mixed $arg event arguments 356 * @param moodleform $caller calling object 357 * @return mixed 358 */ 359 public function onQuickFormEvent($event, $arg, &$caller) { 360 $this->setMoodleForm($caller); 361 switch ($event) { 362 case 'createElement': 363 // The first argument is the name. 364 $name = $arg[0]; 365 366 // Set disable actions. 367 $caller->hideIf($name.'[modgrade_scale]', $name.'[modgrade_type]', 'neq', 'scale'); 368 $caller->hideIf($name.'[modgrade_point]', $name.'[modgrade_type]', 'neq', 'point'); 369 $caller->hideIf($name.'[modgrade_rescalegrades]', $name.'[modgrade_type]', 'neq', 'point'); 370 371 // Set validation rules for the sub-elements belonging to this element. 372 // A handy note: the parent scope of a closure is the function in which the closure was declared. 373 // Because of this using $this is safe despite the closures being called statically. 374 // A nasty magic hack! 375 $checkgradetypechange = function($val) { 376 // Nothing is affected by changes to the grade type if there are no grades yet. 377 if (!$this->hasgrades) { 378 return true; 379 } 380 // Check if we are changing the grade type when grades are present. 381 if (isset($val['modgrade_type']) && $val['modgrade_type'] !== $this->currentgradetype) { 382 return false; 383 } 384 return true; 385 }; 386 $checkscalechange = function($val) { 387 // Nothing is affected by changes to the scale if there are no grades yet. 388 if (!$this->hasgrades) { 389 return true; 390 } 391 // Check if we are changing the scale type when grades are present. 392 // If modgrade_type is empty then use currentgradetype. 393 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 394 if ($gradetype === 'scale') { 395 if (isset($val['modgrade_scale']) && ($val['modgrade_scale'] !== $this->currentscaleid)) { 396 return false; 397 } 398 } 399 return true; 400 }; 401 $checkmaxgradechange = function($val) { 402 // Nothing is affected by changes to the max grade if there are no grades yet. 403 if (!$this->hasgrades) { 404 return true; 405 } 406 // If we are not using ratings we can change the max grade. 407 if (!$this->useratings) { 408 return true; 409 } 410 // Check if we are changing the max grade if we are using ratings and there is a grade. 411 // If modgrade_type is empty then use currentgradetype. 412 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 413 if ($gradetype === 'point') { 414 if (isset($val['modgrade_point']) && 415 grade_floats_different($this->currentgrade, $val['modgrade_point'])) { 416 return false; 417 } 418 } 419 return true; 420 }; 421 $checkmaxgrade = function($val) { 422 // Closure to validate a max points value. See the note above about scope if this confuses you. 423 // If modgrade_type is empty then use currentgradetype. 424 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 425 if ($gradetype === 'point') { 426 if (isset($val['modgrade_point'])) { 427 return $this->validate_point($val['modgrade_point']); 428 } 429 } 430 return true; 431 }; 432 $checkvalidscale = function($val) { 433 // Closure to validate a scale value. See the note above about scope if this confuses you. 434 // If modgrade_type is empty then use currentgradetype. 435 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 436 if ($gradetype === 'scale') { 437 if (isset($val['modgrade_scale'])) { 438 return $this->validate_scale($val['modgrade_scale']); 439 } 440 } 441 return true; 442 }; 443 444 $checkrescale = function($val) { 445 // Nothing is affected by changes to grademax if there are no grades yet. 446 if (!$this->isupdate || !$this->hasgrades || !$this->canrescale) { 447 return true; 448 } 449 // Closure to validate a scale value. See the note above about scope if this confuses you. 450 // If modgrade_type is empty then use currentgradetype. 451 $gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype; 452 if ($gradetype === 'point' && isset($val['modgrade_point'])) { 453 // Work out if the value was actually changed in the form. 454 if (grade_floats_different($this->currentgrade, $val['modgrade_point'])) { 455 if (empty($val['modgrade_rescalegrades'])) { 456 // This was an "edit", the grademax was changed and the process existing setting was not set. 457 return false; 458 } 459 } 460 } 461 return true; 462 }; 463 464 $cantchangegradetype = get_string('modgradecantchangegradetype', 'grades'); 465 $cantchangemaxgrade = get_string('modgradecantchangeratingmaxgrade', 'grades'); 466 $maxgradeexceeded = get_string('modgradeerrorbadpoint', 'grades', get_config('core', 'gradepointmax')); 467 $invalidscale = get_string('modgradeerrorbadscale', 'grades'); 468 $cantchangescale = get_string('modgradecantchangescale', 'grades'); 469 $mustchooserescale = get_string('mustchooserescaleyesorno', 'grades'); 470 // When creating the rules the sixth arg is $force, we set it to true because otherwise the form 471 // will attempt to validate the existence of the element, we don't want this because the element 472 // is being created right now and doesn't actually exist as a registered element yet. 473 $caller->addRule($name, $cantchangegradetype, 'callback', $checkgradetypechange, 'server', false, true); 474 $caller->addRule($name, $cantchangemaxgrade, 'callback', $checkmaxgradechange, 'server', false, true); 475 $caller->addRule($name, $maxgradeexceeded, 'callback', $checkmaxgrade, 'server', false, true); 476 $caller->addRule($name, $invalidscale, 'callback', $checkvalidscale, 'server', false, true); 477 $caller->addRule($name, $cantchangescale, 'callback', $checkscalechange, 'server', false, true); 478 $caller->addRule($name, $mustchooserescale, 'callback', $checkrescale, 'server', false, true); 479 480 break; 481 482 case 'updateValue': 483 // As this is a group element with no value of its own we are only interested in situations where the 484 // default value or a constant value are being provided to the actual element. 485 // In this case we expect an int that is going to translate to a scale if negative, or to max points 486 // if positive. 487 488 // Set the maximum points field to disabled if the rescale option has not been chosen and there are grades. 489 $caller->disabledIf($this->getName() . '[modgrade_point]', $this->getName() . 490 '[modgrade_rescalegrades]', 'eq', ''); 491 492 // A constant value should be given as an int. 493 // The default value should be an int and be either $CFG->gradepointdefault or whatever was set in set_data(). 494 $value = $this->_findValue($caller->_constantValues); 495 if (null === $value) { 496 if ($caller->isSubmitted() && $this->_findValue($caller->_submitValues) !== null) { 497 // Submitted values are array, one value for each individual element in this group. 498 // When there is submitted data let parent::onQuickFormEvent() process it. 499 break; 500 } 501 $value = $this->_findValue($caller->_defaultValues); 502 } 503 504 if (!is_null($value) && !is_scalar($value)) { 505 // Something unexpected (likely an array of subelement values) has been given - this will be dealt 506 // with somewhere else - where exactly... likely the subelements. 507 debugging('An invalid value (type '.gettype($value).') has arrived at '.__METHOD__, DEBUG_DEVELOPER); 508 break; 509 } 510 511 // Set element state for existing data. 512 // This is really a pretty hacky thing to do, when data is being set the group element is called 513 // with the data first and the subelements called afterwards. 514 // This means that the subelements data (inc const and default values) can be overridden by form code. 515 // So - when we call this code really we can't be sure that will be the end value for the element. 516 if (!empty($this->_elements)) { 517 if (!empty($value)) { 518 if ($value < 0) { 519 $this->gradetypeformelement->setValue('scale'); 520 $this->scaleformelement->setValue(($value * -1)); 521 } else if ($value > 0) { 522 $this->gradetypeformelement->setValue('point'); 523 $maxvalue = !empty($this->currentgrade) ? (string)unformat_float($this->currentgrade) : $value; 524 $this->maxgradeformelement->setValue($maxvalue); 525 } 526 } else { 527 $this->gradetypeformelement->setValue('none'); 528 $this->maxgradeformelement->setValue(100); 529 } 530 } 531 break; 532 } 533 534 // Always let the parent do its thing! 535 return parent::onQuickFormEvent($event, $arg, $caller); 536 } 537 538 /** 539 * Generates the id attribute for the subelement of the modgrade group. 540 * 541 * Uses algorithm similar to what {@link HTML_QuickForm_element::_generateId()} 542 * does but takes the name of the wrapping modgrade group into account. 543 * 544 * @param string $subname the name of the HTML_QuickForm_element in this modgrade group 545 * @return string 546 */ 547 protected function generate_modgrade_subelement_id($subname) { 548 $gid = str_replace(array('[', ']'), array('_', ''), $this->getName()); 549 return clean_param('id_'.$gid.'_'.$subname, PARAM_ALPHANUMEXT); 550 } 551 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body