[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 * formslib.php - library of classes for creating forms in Moodle, based on PEAR QuickForms. 19 * 20 * To use formslib then you will want to create a new file purpose_form.php eg. edit_form.php 21 * and you want to name your class something like {modulename}_{purpose}_form. Your class will 22 * extend moodleform overriding abstract classes definition and optionally defintion_after_data 23 * and validation. 24 * 25 * See examples of use of this library in course/edit.php and course/edit_form.php 26 * 27 * A few notes : 28 * form definition is used for both printing of form and processing and should be the same 29 * for both or you may lose some submitted data which won't be let through. 30 * you should be using setType for every form element except select, radio or checkbox 31 * elements, these elements clean themselves. 32 * 33 * @package core_form 34 * @copyright 2006 Jamie Pratt <me@jamiep.org> 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 38 defined('MOODLE_INTERNAL') || die(); 39 40 /** setup.php includes our hacked pear libs first */ 41 require_once 'HTML/QuickForm.php'; 42 require_once 'HTML/QuickForm/DHTMLRulesTableless.php'; 43 require_once 'HTML/QuickForm/Renderer/Tableless.php'; 44 require_once 'HTML/QuickForm/Rule.php'; 45 46 require_once $CFG->libdir.'/filelib.php'; 47 48 /** 49 * EDITOR_UNLIMITED_FILES - hard-coded value for the 'maxfiles' option 50 */ 51 define('EDITOR_UNLIMITED_FILES', -1); 52 53 /** 54 * Callback called when PEAR throws an error 55 * 56 * @param PEAR_Error $error 57 */ 58 function pear_handle_error($error){ 59 echo '<strong>'.$error->GetMessage().'</strong> '.$error->getUserInfo(); 60 echo '<br /> <strong>Backtrace </strong>:'; 61 print_object($error->backtrace); 62 } 63 64 if ($CFG->debugdeveloper) { 65 //TODO: this is a wrong place to init PEAR! 66 $GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_CALLBACK; 67 $GLOBALS['_PEAR_default_error_options'] = 'pear_handle_error'; 68 } 69 70 /** 71 * Initalize javascript for date type form element 72 * 73 * @staticvar bool $done make sure it gets initalize once. 74 * @global moodle_page $PAGE 75 */ 76 function form_init_date_js() { 77 global $PAGE; 78 static $done = false; 79 if (!$done) { 80 $calendar = \core_calendar\type_factory::get_calendar_instance(); 81 $module = 'moodle-form-dateselector'; 82 $function = 'M.form.dateselector.init_date_selectors'; 83 $defaulttimezone = date_default_timezone_get(); 84 85 $config = array(array( 86 'firstdayofweek' => $calendar->get_starting_weekday(), 87 'mon' => date_format_string(strtotime("Monday"), '%a', $defaulttimezone), 88 'tue' => date_format_string(strtotime("Tuesday"), '%a', $defaulttimezone), 89 'wed' => date_format_string(strtotime("Wednesday"), '%a', $defaulttimezone), 90 'thu' => date_format_string(strtotime("Thursday"), '%a', $defaulttimezone), 91 'fri' => date_format_string(strtotime("Friday"), '%a', $defaulttimezone), 92 'sat' => date_format_string(strtotime("Saturday"), '%a', $defaulttimezone), 93 'sun' => date_format_string(strtotime("Sunday"), '%a', $defaulttimezone), 94 'january' => date_format_string(strtotime("January 1"), '%B', $defaulttimezone), 95 'february' => date_format_string(strtotime("February 1"), '%B', $defaulttimezone), 96 'march' => date_format_string(strtotime("March 1"), '%B', $defaulttimezone), 97 'april' => date_format_string(strtotime("April 1"), '%B', $defaulttimezone), 98 'may' => date_format_string(strtotime("May 1"), '%B', $defaulttimezone), 99 'june' => date_format_string(strtotime("June 1"), '%B', $defaulttimezone), 100 'july' => date_format_string(strtotime("July 1"), '%B', $defaulttimezone), 101 'august' => date_format_string(strtotime("August 1"), '%B', $defaulttimezone), 102 'september' => date_format_string(strtotime("September 1"), '%B', $defaulttimezone), 103 'october' => date_format_string(strtotime("October 1"), '%B', $defaulttimezone), 104 'november' => date_format_string(strtotime("November 1"), '%B', $defaulttimezone), 105 'december' => date_format_string(strtotime("December 1"), '%B', $defaulttimezone) 106 )); 107 $PAGE->requires->yui_module($module, $function, $config); 108 $done = true; 109 } 110 } 111 112 /** 113 * Wrapper that separates quickforms syntax from moodle code 114 * 115 * Moodle specific wrapper that separates quickforms syntax from moodle code. You won't directly 116 * use this class you should write a class definition which extends this class or a more specific 117 * subclass such a moodleform_mod for each form you want to display and/or process with formslib. 118 * 119 * You will write your own definition() method which performs the form set up. 120 * 121 * @package core_form 122 * @copyright 2006 Jamie Pratt <me@jamiep.org> 123 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 124 * @todo MDL-19380 rethink the file scanning 125 */ 126 abstract class moodleform { 127 /** @var string name of the form */ 128 protected $_formname; // form name 129 130 /** @var MoodleQuickForm quickform object definition */ 131 protected $_form; 132 133 /** @var array globals workaround */ 134 protected $_customdata; 135 136 /** @var array submitted form data when using mforms with ajax */ 137 protected $_ajaxformdata; 138 139 /** @var object definition_after_data executed flag */ 140 protected $_definition_finalized = false; 141 142 /** 143 * The constructor function calls the abstract function definition() and it will then 144 * process and clean and attempt to validate incoming data. 145 * 146 * It will call your custom validate method to validate data and will also check any rules 147 * you have specified in definition using addRule 148 * 149 * The name of the form (id attribute of the form) is automatically generated depending on 150 * the name you gave the class extending moodleform. You should call your class something 151 * like 152 * 153 * @param mixed $action the action attribute for the form. If empty defaults to auto detect the 154 * current url. If a moodle_url object then outputs params as hidden variables. 155 * @param mixed $customdata if your form defintion method needs access to data such as $course 156 * $cm, etc. to construct the form definition then pass it in this array. You can 157 * use globals for somethings. 158 * @param string $method if you set this to anything other than 'post' then _GET and _POST will 159 * be merged and used as incoming data to the form. 160 * @param string $target target frame for form submission. You will rarely use this. Don't use 161 * it if you don't need to as the target attribute is deprecated in xhtml strict. 162 * @param mixed $attributes you can pass a string of html attributes here or an array. 163 * @param bool $editable 164 * @param array $ajaxformdata Forms submitted via ajax, must pass their data here, instead of relying on _GET and _POST. 165 */ 166 public function __construct($action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true, 167 $ajaxformdata=null) { 168 global $CFG, $FULLME; 169 // no standard mform in moodle should allow autocomplete with the exception of user signup 170 if (empty($attributes)) { 171 $attributes = array('autocomplete'=>'off'); 172 } else if (is_array($attributes)) { 173 $attributes['autocomplete'] = 'off'; 174 } else { 175 if (strpos($attributes, 'autocomplete') === false) { 176 $attributes .= ' autocomplete="off" '; 177 } 178 } 179 180 181 if (empty($action)){ 182 // do not rely on PAGE->url here because dev often do not setup $actualurl properly in admin_externalpage_setup() 183 $action = strip_querystring($FULLME); 184 if (!empty($CFG->sslproxy)) { 185 // return only https links when using SSL proxy 186 $action = preg_replace('/^http:/', 'https:', $action, 1); 187 } 188 //TODO: use following instead of FULLME - see MDL-33015 189 //$action = strip_querystring(qualified_me()); 190 } 191 // Assign custom data first, so that get_form_identifier can use it. 192 $this->_customdata = $customdata; 193 $this->_formname = $this->get_form_identifier(); 194 $this->_ajaxformdata = $ajaxformdata; 195 196 $this->_form = new MoodleQuickForm($this->_formname, $method, $action, $target, $attributes); 197 if (!$editable){ 198 $this->_form->hardFreeze(); 199 } 200 201 $this->definition(); 202 203 $this->_form->addElement('hidden', 'sesskey', null); // automatic sesskey protection 204 $this->_form->setType('sesskey', PARAM_RAW); 205 $this->_form->setDefault('sesskey', sesskey()); 206 $this->_form->addElement('hidden', '_qf__'.$this->_formname, null); // form submission marker 207 $this->_form->setType('_qf__'.$this->_formname, PARAM_RAW); 208 $this->_form->setDefault('_qf__'.$this->_formname, 1); 209 $this->_form->_setDefaultRuleMessages(); 210 211 // we have to know all input types before processing submission ;-) 212 $this->_process_submission($method); 213 } 214 215 /** 216 * Old syntax of class constructor. Deprecated in PHP7. 217 * 218 * @deprecated since Moodle 3.1 219 */ 220 public function moodleform($action=null, $customdata=null, $method='post', $target='', $attributes=null, $editable=true) { 221 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 222 self::__construct($action, $customdata, $method, $target, $attributes, $editable); 223 } 224 225 /** 226 * It should returns unique identifier for the form. 227 * Currently it will return class name, but in case two same forms have to be 228 * rendered on same page then override function to get unique form identifier. 229 * e.g This is used on multiple self enrollments page. 230 * 231 * @return string form identifier. 232 */ 233 protected function get_form_identifier() { 234 $class = get_class($this); 235 236 return preg_replace('/[^a-z0-9_]/i', '_', $class); 237 } 238 239 /** 240 * To autofocus on first form element or first element with error. 241 * 242 * @param string $name if this is set then the focus is forced to a field with this name 243 * @return string javascript to select form element with first error or 244 * first element if no errors. Use this as a parameter 245 * when calling print_header 246 */ 247 function focus($name=NULL) { 248 $form =& $this->_form; 249 $elkeys = array_keys($form->_elementIndex); 250 $error = false; 251 if (isset($form->_errors) && 0 != count($form->_errors)){ 252 $errorkeys = array_keys($form->_errors); 253 $elkeys = array_intersect($elkeys, $errorkeys); 254 $error = true; 255 } 256 257 if ($error or empty($name)) { 258 $names = array(); 259 while (empty($names) and !empty($elkeys)) { 260 $el = array_shift($elkeys); 261 $names = $form->_getElNamesRecursive($el); 262 } 263 if (!empty($names)) { 264 $name = array_shift($names); 265 } 266 } 267 268 $focus = ''; 269 if (!empty($name)) { 270 $focus = 'forms[\''.$form->getAttribute('id').'\'].elements[\''.$name.'\']'; 271 } 272 273 return $focus; 274 } 275 276 /** 277 * Internal method. Alters submitted data to be suitable for quickforms processing. 278 * Must be called when the form is fully set up. 279 * 280 * @param string $method name of the method which alters submitted data 281 */ 282 function _process_submission($method) { 283 $submission = array(); 284 if (!empty($this->_ajaxformdata)) { 285 $submission = $this->_ajaxformdata; 286 } else if ($method == 'post') { 287 if (!empty($_POST)) { 288 $submission = $_POST; 289 } 290 } else { 291 $submission = $_GET; 292 merge_query_params($submission, $_POST); // Emulate handling of parameters in xxxx_param(). 293 } 294 295 // following trick is needed to enable proper sesskey checks when using GET forms 296 // the _qf__.$this->_formname serves as a marker that form was actually submitted 297 if (array_key_exists('_qf__'.$this->_formname, $submission) and $submission['_qf__'.$this->_formname] == 1) { 298 if (!confirm_sesskey()) { 299 print_error('invalidsesskey'); 300 } 301 $files = $_FILES; 302 } else { 303 $submission = array(); 304 $files = array(); 305 } 306 $this->detectMissingSetType(); 307 308 $this->_form->updateSubmission($submission, $files); 309 } 310 311 /** 312 * Internal method - should not be used anywhere. 313 * @deprecated since 2.6 314 * @return array $_POST. 315 */ 316 protected function _get_post_params() { 317 return $_POST; 318 } 319 320 /** 321 * Internal method. Validates all old-style deprecated uploaded files. 322 * The new way is to upload files via repository api. 323 * 324 * @param array $files list of files to be validated 325 * @return bool|array Success or an array of errors 326 */ 327 function _validate_files(&$files) { 328 global $CFG, $COURSE; 329 330 $files = array(); 331 332 if (empty($_FILES)) { 333 // we do not need to do any checks because no files were submitted 334 // note: server side rules do not work for files - use custom verification in validate() instead 335 return true; 336 } 337 338 $errors = array(); 339 $filenames = array(); 340 341 // now check that we really want each file 342 foreach ($_FILES as $elname=>$file) { 343 $required = $this->_form->isElementRequired($elname); 344 345 if ($file['error'] == 4 and $file['size'] == 0) { 346 if ($required) { 347 $errors[$elname] = get_string('required'); 348 } 349 unset($_FILES[$elname]); 350 continue; 351 } 352 353 if (!empty($file['error'])) { 354 $errors[$elname] = file_get_upload_error($file['error']); 355 unset($_FILES[$elname]); 356 continue; 357 } 358 359 if (!is_uploaded_file($file['tmp_name'])) { 360 // TODO: improve error message 361 $errors[$elname] = get_string('error'); 362 unset($_FILES[$elname]); 363 continue; 364 } 365 366 if (!$this->_form->elementExists($elname) or !$this->_form->getElementType($elname)=='file') { 367 // hmm, this file was not requested 368 unset($_FILES[$elname]); 369 continue; 370 } 371 372 // NOTE: the viruses are scanned in file picker, no need to deal with them here. 373 374 $filename = clean_param($_FILES[$elname]['name'], PARAM_FILE); 375 if ($filename === '') { 376 // TODO: improve error message - wrong chars 377 $errors[$elname] = get_string('error'); 378 unset($_FILES[$elname]); 379 continue; 380 } 381 if (in_array($filename, $filenames)) { 382 // TODO: improve error message - duplicate name 383 $errors[$elname] = get_string('error'); 384 unset($_FILES[$elname]); 385 continue; 386 } 387 $filenames[] = $filename; 388 $_FILES[$elname]['name'] = $filename; 389 390 $files[$elname] = $_FILES[$elname]['tmp_name']; 391 } 392 393 // return errors if found 394 if (count($errors) == 0){ 395 return true; 396 397 } else { 398 $files = array(); 399 return $errors; 400 } 401 } 402 403 /** 404 * Internal method. Validates filepicker and filemanager files if they are 405 * set as required fields. Also, sets the error message if encountered one. 406 * 407 * @return bool|array with errors 408 */ 409 protected function validate_draft_files() { 410 global $USER; 411 $mform =& $this->_form; 412 413 $errors = array(); 414 //Go through all the required elements and make sure you hit filepicker or 415 //filemanager element. 416 foreach ($mform->_rules as $elementname => $rules) { 417 $elementtype = $mform->getElementType($elementname); 418 //If element is of type filepicker then do validation 419 if (($elementtype == 'filepicker') || ($elementtype == 'filemanager')){ 420 //Check if rule defined is required rule 421 foreach ($rules as $rule) { 422 if ($rule['type'] == 'required') { 423 $draftid = (int)$mform->getSubmitValue($elementname); 424 $fs = get_file_storage(); 425 $context = context_user::instance($USER->id); 426 if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) { 427 $errors[$elementname] = $rule['message']; 428 } 429 } 430 } 431 } 432 } 433 // Check all the filemanager elements to make sure they do not have too many 434 // files in them. 435 foreach ($mform->_elements as $element) { 436 if ($element->_type == 'filemanager') { 437 $maxfiles = $element->getMaxfiles(); 438 if ($maxfiles > 0) { 439 $draftid = (int)$element->getValue(); 440 $fs = get_file_storage(); 441 $context = context_user::instance($USER->id); 442 $files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, '', false); 443 if (count($files) > $maxfiles) { 444 $errors[$element->getName()] = get_string('err_maxfiles', 'form', $maxfiles); 445 } 446 } 447 } 448 } 449 if (empty($errors)) { 450 return true; 451 } else { 452 return $errors; 453 } 454 } 455 456 /** 457 * Load in existing data as form defaults. Usually new entry defaults are stored directly in 458 * form definition (new entry form); this function is used to load in data where values 459 * already exist and data is being edited (edit entry form). 460 * 461 * note: $slashed param removed 462 * 463 * @param stdClass|array $default_values object or array of default values 464 */ 465 function set_data($default_values) { 466 if (is_object($default_values)) { 467 $default_values = (array)$default_values; 468 } 469 $this->_form->setDefaults($default_values); 470 } 471 472 /** 473 * Check that form was submitted. Does not check validity of submitted data. 474 * 475 * @return bool true if form properly submitted 476 */ 477 function is_submitted() { 478 return $this->_form->isSubmitted(); 479 } 480 481 /** 482 * Checks if button pressed is not for submitting the form 483 * 484 * @staticvar bool $nosubmit keeps track of no submit button 485 * @return bool 486 */ 487 function no_submit_button_pressed(){ 488 static $nosubmit = null; // one check is enough 489 if (!is_null($nosubmit)){ 490 return $nosubmit; 491 } 492 $mform =& $this->_form; 493 $nosubmit = false; 494 if (!$this->is_submitted()){ 495 return false; 496 } 497 foreach ($mform->_noSubmitButtons as $nosubmitbutton){ 498 if (optional_param($nosubmitbutton, 0, PARAM_RAW)){ 499 $nosubmit = true; 500 break; 501 } 502 } 503 return $nosubmit; 504 } 505 506 507 /** 508 * Check that form data is valid. 509 * You should almost always use this, rather than {@link validate_defined_fields} 510 * 511 * @return bool true if form data valid 512 */ 513 function is_validated() { 514 //finalize the form definition before any processing 515 if (!$this->_definition_finalized) { 516 $this->_definition_finalized = true; 517 $this->definition_after_data(); 518 } 519 520 return $this->validate_defined_fields(); 521 } 522 523 /** 524 * Validate the form. 525 * 526 * You almost always want to call {@link is_validated} instead of this 527 * because it calls {@link definition_after_data} first, before validating the form, 528 * which is what you want in 99% of cases. 529 * 530 * This is provided as a separate function for those special cases where 531 * you want the form validated before definition_after_data is called 532 * for example, to selectively add new elements depending on a no_submit_button press, 533 * but only when the form is valid when the no_submit_button is pressed, 534 * 535 * @param bool $validateonnosubmit optional, defaults to false. The default behaviour 536 * is NOT to validate the form when a no submit button has been pressed. 537 * pass true here to override this behaviour 538 * 539 * @return bool true if form data valid 540 */ 541 function validate_defined_fields($validateonnosubmit=false) { 542 static $validated = null; // one validation is enough 543 $mform =& $this->_form; 544 if ($this->no_submit_button_pressed() && empty($validateonnosubmit)){ 545 return false; 546 } elseif ($validated === null) { 547 $internal_val = $mform->validate(); 548 549 $files = array(); 550 $file_val = $this->_validate_files($files); 551 //check draft files for validation and flag them if required files 552 //are not in draft area. 553 $draftfilevalue = $this->validate_draft_files(); 554 555 if ($file_val !== true && $draftfilevalue !== true) { 556 $file_val = array_merge($file_val, $draftfilevalue); 557 } else if ($draftfilevalue !== true) { 558 $file_val = $draftfilevalue; 559 } //default is file_val, so no need to assign. 560 561 if ($file_val !== true) { 562 if (!empty($file_val)) { 563 foreach ($file_val as $element=>$msg) { 564 $mform->setElementError($element, $msg); 565 } 566 } 567 $file_val = false; 568 } 569 570 $data = $mform->exportValues(); 571 $moodle_val = $this->validation($data, $files); 572 if ((is_array($moodle_val) && count($moodle_val)!==0)) { 573 // non-empty array means errors 574 foreach ($moodle_val as $element=>$msg) { 575 $mform->setElementError($element, $msg); 576 } 577 $moodle_val = false; 578 579 } else { 580 // anything else means validation ok 581 $moodle_val = true; 582 } 583 584 $validated = ($internal_val and $moodle_val and $file_val); 585 } 586 return $validated; 587 } 588 589 /** 590 * Return true if a cancel button has been pressed resulting in the form being submitted. 591 * 592 * @return bool true if a cancel button has been pressed 593 */ 594 function is_cancelled(){ 595 $mform =& $this->_form; 596 if ($mform->isSubmitted()){ 597 foreach ($mform->_cancelButtons as $cancelbutton){ 598 if (optional_param($cancelbutton, 0, PARAM_RAW)){ 599 return true; 600 } 601 } 602 } 603 return false; 604 } 605 606 /** 607 * Return submitted data if properly submitted or returns NULL if validation fails or 608 * if there is no submitted data. 609 * 610 * note: $slashed param removed 611 * 612 * @return object submitted data; NULL if not valid or not submitted or cancelled 613 */ 614 function get_data() { 615 $mform =& $this->_form; 616 617 if (!$this->is_cancelled() and $this->is_submitted() and $this->is_validated()) { 618 $data = $mform->exportValues(); 619 unset($data['sesskey']); // we do not need to return sesskey 620 unset($data['_qf__'.$this->_formname]); // we do not need the submission marker too 621 if (empty($data)) { 622 return NULL; 623 } else { 624 return (object)$data; 625 } 626 } else { 627 return NULL; 628 } 629 } 630 631 /** 632 * Return submitted data without validation or NULL if there is no submitted data. 633 * note: $slashed param removed 634 * 635 * @return object submitted data; NULL if not submitted 636 */ 637 function get_submitted_data() { 638 $mform =& $this->_form; 639 640 if ($this->is_submitted()) { 641 $data = $mform->exportValues(); 642 unset($data['sesskey']); // we do not need to return sesskey 643 unset($data['_qf__'.$this->_formname]); // we do not need the submission marker too 644 if (empty($data)) { 645 return NULL; 646 } else { 647 return (object)$data; 648 } 649 } else { 650 return NULL; 651 } 652 } 653 654 /** 655 * Save verified uploaded files into directory. Upload process can be customised from definition() 656 * 657 * @deprecated since Moodle 2.0 658 * @todo MDL-31294 remove this api 659 * @see moodleform::save_stored_file() 660 * @see moodleform::save_file() 661 * @param string $destination path where file should be stored 662 * @return bool Always false 663 */ 664 function save_files($destination) { 665 debugging('Not used anymore, please fix code! Use save_stored_file() or save_file() instead'); 666 return false; 667 } 668 669 /** 670 * Returns name of uploaded file. 671 * 672 * @param string $elname first element if null 673 * @return string|bool false in case of failure, string if ok 674 */ 675 function get_new_filename($elname=null) { 676 global $USER; 677 678 if (!$this->is_submitted() or !$this->is_validated()) { 679 return false; 680 } 681 682 if (is_null($elname)) { 683 if (empty($_FILES)) { 684 return false; 685 } 686 reset($_FILES); 687 $elname = key($_FILES); 688 } 689 690 if (empty($elname)) { 691 return false; 692 } 693 694 $element = $this->_form->getElement($elname); 695 696 if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) { 697 $values = $this->_form->exportValues($elname); 698 if (empty($values[$elname])) { 699 return false; 700 } 701 $draftid = $values[$elname]; 702 $fs = get_file_storage(); 703 $context = context_user::instance($USER->id); 704 if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) { 705 return false; 706 } 707 $file = reset($files); 708 return $file->get_filename(); 709 } 710 711 if (!isset($_FILES[$elname])) { 712 return false; 713 } 714 715 return $_FILES[$elname]['name']; 716 } 717 718 /** 719 * Save file to standard filesystem 720 * 721 * @param string $elname name of element 722 * @param string $pathname full path name of file 723 * @param bool $override override file if exists 724 * @return bool success 725 */ 726 function save_file($elname, $pathname, $override=false) { 727 global $USER; 728 729 if (!$this->is_submitted() or !$this->is_validated()) { 730 return false; 731 } 732 if (file_exists($pathname)) { 733 if ($override) { 734 if (!@unlink($pathname)) { 735 return false; 736 } 737 } else { 738 return false; 739 } 740 } 741 742 $element = $this->_form->getElement($elname); 743 744 if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) { 745 $values = $this->_form->exportValues($elname); 746 if (empty($values[$elname])) { 747 return false; 748 } 749 $draftid = $values[$elname]; 750 $fs = get_file_storage(); 751 $context = context_user::instance($USER->id); 752 if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) { 753 return false; 754 } 755 $file = reset($files); 756 757 return $file->copy_content_to($pathname); 758 759 } else if (isset($_FILES[$elname])) { 760 return copy($_FILES[$elname]['tmp_name'], $pathname); 761 } 762 763 return false; 764 } 765 766 /** 767 * Returns a temporary file, do not forget to delete after not needed any more. 768 * 769 * @param string $elname name of the elmenet 770 * @return string|bool either string or false 771 */ 772 function save_temp_file($elname) { 773 if (!$this->get_new_filename($elname)) { 774 return false; 775 } 776 if (!$dir = make_temp_directory('forms')) { 777 return false; 778 } 779 if (!$tempfile = tempnam($dir, 'tempup_')) { 780 return false; 781 } 782 if (!$this->save_file($elname, $tempfile, true)) { 783 // something went wrong 784 @unlink($tempfile); 785 return false; 786 } 787 788 return $tempfile; 789 } 790 791 /** 792 * Get draft files of a form element 793 * This is a protected method which will be used only inside moodleforms 794 * 795 * @param string $elname name of element 796 * @return array|bool|null 797 */ 798 protected function get_draft_files($elname) { 799 global $USER; 800 801 if (!$this->is_submitted()) { 802 return false; 803 } 804 805 $element = $this->_form->getElement($elname); 806 807 if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) { 808 $values = $this->_form->exportValues($elname); 809 if (empty($values[$elname])) { 810 return false; 811 } 812 $draftid = $values[$elname]; 813 $fs = get_file_storage(); 814 $context = context_user::instance($USER->id); 815 if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) { 816 return null; 817 } 818 return $files; 819 } 820 return null; 821 } 822 823 /** 824 * Save file to local filesystem pool 825 * 826 * @param string $elname name of element 827 * @param int $newcontextid id of context 828 * @param string $newcomponent name of the component 829 * @param string $newfilearea name of file area 830 * @param int $newitemid item id 831 * @param string $newfilepath path of file where it get stored 832 * @param string $newfilename use specified filename, if not specified name of uploaded file used 833 * @param bool $overwrite overwrite file if exists 834 * @param int $newuserid new userid if required 835 * @return mixed stored_file object or false if error; may throw exception if duplicate found 836 */ 837 function save_stored_file($elname, $newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath='/', 838 $newfilename=null, $overwrite=false, $newuserid=null) { 839 global $USER; 840 841 if (!$this->is_submitted() or !$this->is_validated()) { 842 return false; 843 } 844 845 if (empty($newuserid)) { 846 $newuserid = $USER->id; 847 } 848 849 $element = $this->_form->getElement($elname); 850 $fs = get_file_storage(); 851 852 if ($element instanceof MoodleQuickForm_filepicker) { 853 $values = $this->_form->exportValues($elname); 854 if (empty($values[$elname])) { 855 return false; 856 } 857 $draftid = $values[$elname]; 858 $context = context_user::instance($USER->id); 859 if (!$files = $fs->get_area_files($context->id, 'user' ,'draft', $draftid, 'id DESC', false)) { 860 return false; 861 } 862 $file = reset($files); 863 if (is_null($newfilename)) { 864 $newfilename = $file->get_filename(); 865 } 866 867 if ($overwrite) { 868 if ($oldfile = $fs->get_file($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) { 869 if (!$oldfile->delete()) { 870 return false; 871 } 872 } 873 } 874 875 $file_record = array('contextid'=>$newcontextid, 'component'=>$newcomponent, 'filearea'=>$newfilearea, 'itemid'=>$newitemid, 876 'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid); 877 return $fs->create_file_from_storedfile($file_record, $file); 878 879 } else if (isset($_FILES[$elname])) { 880 $filename = is_null($newfilename) ? $_FILES[$elname]['name'] : $newfilename; 881 882 if ($overwrite) { 883 if ($oldfile = $fs->get_file($newcontextid, $newcomponent, $newfilearea, $newitemid, $newfilepath, $newfilename)) { 884 if (!$oldfile->delete()) { 885 return false; 886 } 887 } 888 } 889 890 $file_record = array('contextid'=>$newcontextid, 'component'=>$newcomponent, 'filearea'=>$newfilearea, 'itemid'=>$newitemid, 891 'filepath'=>$newfilepath, 'filename'=>$newfilename, 'userid'=>$newuserid); 892 return $fs->create_file_from_pathname($file_record, $_FILES[$elname]['tmp_name']); 893 } 894 895 return false; 896 } 897 898 /** 899 * Get content of uploaded file. 900 * 901 * @param string $elname name of file upload element 902 * @return string|bool false in case of failure, string if ok 903 */ 904 function get_file_content($elname) { 905 global $USER; 906 907 if (!$this->is_submitted() or !$this->is_validated()) { 908 return false; 909 } 910 911 $element = $this->_form->getElement($elname); 912 913 if ($element instanceof MoodleQuickForm_filepicker || $element instanceof MoodleQuickForm_filemanager) { 914 $values = $this->_form->exportValues($elname); 915 if (empty($values[$elname])) { 916 return false; 917 } 918 $draftid = $values[$elname]; 919 $fs = get_file_storage(); 920 $context = context_user::instance($USER->id); 921 if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) { 922 return false; 923 } 924 $file = reset($files); 925 926 return $file->get_content(); 927 928 } else if (isset($_FILES[$elname])) { 929 return file_get_contents($_FILES[$elname]['tmp_name']); 930 } 931 932 return false; 933 } 934 935 /** 936 * Print html form. 937 */ 938 function display() { 939 //finalize the form definition if not yet done 940 if (!$this->_definition_finalized) { 941 $this->_definition_finalized = true; 942 $this->definition_after_data(); 943 } 944 945 $this->_form->display(); 946 } 947 948 /** 949 * Renders the html form (same as display, but returns the result). 950 * 951 * Note that you can only output this rendered result once per page, as 952 * it contains IDs which must be unique. 953 * 954 * @return string HTML code for the form 955 */ 956 public function render() { 957 ob_start(); 958 $this->display(); 959 $out = ob_get_contents(); 960 ob_end_clean(); 961 return $out; 962 } 963 964 /** 965 * Form definition. Abstract method - always override! 966 */ 967 protected abstract function definition(); 968 969 /** 970 * Dummy stub method - override if you need to setup the form depending on current 971 * values. This method is called after definition(), data submission and set_data(). 972 * All form setup that is dependent on form values should go in here. 973 */ 974 function definition_after_data(){ 975 } 976 977 /** 978 * Dummy stub method - override if you needed to perform some extra validation. 979 * If there are errors return array of errors ("fieldname"=>"error message"), 980 * otherwise true if ok. 981 * 982 * Server side rules do not work for uploaded files, implement serverside rules here if needed. 983 * 984 * @param array $data array of ("fieldname"=>value) of submitted data 985 * @param array $files array of uploaded files "element_name"=>tmp_file_path 986 * @return array of "element_name"=>"error_description" if there are errors, 987 * or an empty array if everything is OK (true allowed for backwards compatibility too). 988 */ 989 function validation($data, $files) { 990 return array(); 991 } 992 993 /** 994 * Helper used by {@link repeat_elements()}. 995 * 996 * @param int $i the index of this element. 997 * @param HTML_QuickForm_element $elementclone 998 * @param array $namecloned array of names 999 */ 1000 function repeat_elements_fix_clone($i, $elementclone, &$namecloned) { 1001 $name = $elementclone->getName(); 1002 $namecloned[] = $name; 1003 1004 if (!empty($name)) { 1005 $elementclone->setName($name."[$i]"); 1006 } 1007 1008 if (is_a($elementclone, 'HTML_QuickForm_header')) { 1009 $value = $elementclone->_text; 1010 $elementclone->setValue(str_replace('{no}', ($i+1), $value)); 1011 1012 } else if (is_a($elementclone, 'HTML_QuickForm_submit') || is_a($elementclone, 'HTML_QuickForm_button')) { 1013 $elementclone->setValue(str_replace('{no}', ($i+1), $elementclone->getValue())); 1014 1015 } else { 1016 $value=$elementclone->getLabel(); 1017 $elementclone->setLabel(str_replace('{no}', ($i+1), $value)); 1018 } 1019 } 1020 1021 /** 1022 * Method to add a repeating group of elements to a form. 1023 * 1024 * @param array $elementobjs Array of elements or groups of elements that are to be repeated 1025 * @param int $repeats no of times to repeat elements initially 1026 * @param array $options a nested array. The first array key is the element name. 1027 * the second array key is the type of option to set, and depend on that option, 1028 * the value takes different forms. 1029 * 'default' - default value to set. Can include '{no}' which is replaced by the repeat number. 1030 * 'type' - PARAM_* type. 1031 * 'helpbutton' - array containing the helpbutton params. 1032 * 'disabledif' - array containing the disabledIf() arguments after the element name. 1033 * 'rule' - array containing the addRule arguments after the element name. 1034 * 'expanded' - whether this section of the form should be expanded by default. (Name be a header element.) 1035 * 'advanced' - whether this element is hidden by 'Show more ...'. 1036 * @param string $repeathiddenname name for hidden element storing no of repeats in this form 1037 * @param string $addfieldsname name for button to add more fields 1038 * @param int $addfieldsno how many fields to add at a time 1039 * @param string $addstring name of button, {no} is replaced by no of blanks that will be added. 1040 * @param bool $addbuttoninside if true, don't call closeHeaderBefore($addfieldsname). Default false. 1041 * @return int no of repeats of element in this page 1042 */ 1043 function repeat_elements($elementobjs, $repeats, $options, $repeathiddenname, 1044 $addfieldsname, $addfieldsno=5, $addstring=null, $addbuttoninside=false){ 1045 if ($addstring===null){ 1046 $addstring = get_string('addfields', 'form', $addfieldsno); 1047 } else { 1048 $addstring = str_ireplace('{no}', $addfieldsno, $addstring); 1049 } 1050 $repeats = optional_param($repeathiddenname, $repeats, PARAM_INT); 1051 $addfields = optional_param($addfieldsname, '', PARAM_TEXT); 1052 if (!empty($addfields)){ 1053 $repeats += $addfieldsno; 1054 } 1055 $mform =& $this->_form; 1056 $mform->registerNoSubmitButton($addfieldsname); 1057 $mform->addElement('hidden', $repeathiddenname, $repeats); 1058 $mform->setType($repeathiddenname, PARAM_INT); 1059 //value not to be overridden by submitted value 1060 $mform->setConstants(array($repeathiddenname=>$repeats)); 1061 $namecloned = array(); 1062 for ($i = 0; $i < $repeats; $i++) { 1063 foreach ($elementobjs as $elementobj){ 1064 $elementclone = fullclone($elementobj); 1065 $this->repeat_elements_fix_clone($i, $elementclone, $namecloned); 1066 1067 if ($elementclone instanceof HTML_QuickForm_group && !$elementclone->_appendName) { 1068 foreach ($elementclone->getElements() as $el) { 1069 $this->repeat_elements_fix_clone($i, $el, $namecloned); 1070 } 1071 $elementclone->setLabel(str_replace('{no}', $i + 1, $elementclone->getLabel())); 1072 } 1073 1074 $mform->addElement($elementclone); 1075 } 1076 } 1077 for ($i=0; $i<$repeats; $i++) { 1078 foreach ($options as $elementname => $elementoptions){ 1079 $pos=strpos($elementname, '['); 1080 if ($pos!==FALSE){ 1081 $realelementname = substr($elementname, 0, $pos)."[$i]"; 1082 $realelementname .= substr($elementname, $pos); 1083 }else { 1084 $realelementname = $elementname."[$i]"; 1085 } 1086 foreach ($elementoptions as $option => $params){ 1087 1088 switch ($option){ 1089 case 'default' : 1090 $mform->setDefault($realelementname, str_replace('{no}', $i + 1, $params)); 1091 break; 1092 case 'helpbutton' : 1093 $params = array_merge(array($realelementname), $params); 1094 call_user_func_array(array(&$mform, 'addHelpButton'), $params); 1095 break; 1096 case 'disabledif' : 1097 foreach ($namecloned as $num => $name){ 1098 if ($params[0] == $name){ 1099 $params[0] = $params[0]."[$i]"; 1100 break; 1101 } 1102 } 1103 $params = array_merge(array($realelementname), $params); 1104 call_user_func_array(array(&$mform, 'disabledIf'), $params); 1105 break; 1106 case 'rule' : 1107 if (is_string($params)){ 1108 $params = array(null, $params, null, 'client'); 1109 } 1110 $params = array_merge(array($realelementname), $params); 1111 call_user_func_array(array(&$mform, 'addRule'), $params); 1112 break; 1113 1114 case 'type': 1115 $mform->setType($realelementname, $params); 1116 break; 1117 1118 case 'expanded': 1119 $mform->setExpanded($realelementname, $params); 1120 break; 1121 1122 case 'advanced' : 1123 $mform->setAdvanced($realelementname, $params); 1124 break; 1125 } 1126 } 1127 } 1128 } 1129 $mform->addElement('submit', $addfieldsname, $addstring); 1130 1131 if (!$addbuttoninside) { 1132 $mform->closeHeaderBefore($addfieldsname); 1133 } 1134 1135 return $repeats; 1136 } 1137 1138 /** 1139 * Adds a link/button that controls the checked state of a group of checkboxes. 1140 * 1141 * @param int $groupid The id of the group of advcheckboxes this element controls 1142 * @param string $text The text of the link. Defaults to selectallornone ("select all/none") 1143 * @param array $attributes associative array of HTML attributes 1144 * @param int $originalValue The original general state of the checkboxes before the user first clicks this element 1145 */ 1146 function add_checkbox_controller($groupid, $text = null, $attributes = null, $originalValue = 0) { 1147 global $CFG, $PAGE; 1148 1149 // Name of the controller button 1150 $checkboxcontrollername = 'nosubmit_checkbox_controller' . $groupid; 1151 $checkboxcontrollerparam = 'checkbox_controller'. $groupid; 1152 $checkboxgroupclass = 'checkboxgroup'.$groupid; 1153 1154 // Set the default text if none was specified 1155 if (empty($text)) { 1156 $text = get_string('selectallornone', 'form'); 1157 } 1158 1159 $mform = $this->_form; 1160 $selectvalue = optional_param($checkboxcontrollerparam, null, PARAM_INT); 1161 $contollerbutton = optional_param($checkboxcontrollername, null, PARAM_ALPHAEXT); 1162 1163 $newselectvalue = $selectvalue; 1164 if (is_null($selectvalue)) { 1165 $newselectvalue = $originalValue; 1166 } else if (!is_null($contollerbutton)) { 1167 $newselectvalue = (int) !$selectvalue; 1168 } 1169 // set checkbox state depending on orignal/submitted value by controoler button 1170 if (!is_null($contollerbutton) || is_null($selectvalue)) { 1171 foreach ($mform->_elements as $element) { 1172 if (($element instanceof MoodleQuickForm_advcheckbox) && 1173 $element->getAttribute('class') == $checkboxgroupclass && 1174 !$element->isFrozen()) { 1175 $mform->setConstants(array($element->getName() => $newselectvalue)); 1176 } 1177 } 1178 } 1179 1180 $mform->addElement('hidden', $checkboxcontrollerparam, $newselectvalue, array('id' => "id_".$checkboxcontrollerparam)); 1181 $mform->setType($checkboxcontrollerparam, PARAM_INT); 1182 $mform->setConstants(array($checkboxcontrollerparam => $newselectvalue)); 1183 1184 $PAGE->requires->yui_module('moodle-form-checkboxcontroller', 'M.form.checkboxcontroller', 1185 array( 1186 array('groupid' => $groupid, 1187 'checkboxclass' => $checkboxgroupclass, 1188 'checkboxcontroller' => $checkboxcontrollerparam, 1189 'controllerbutton' => $checkboxcontrollername) 1190 ) 1191 ); 1192 1193 require_once("$CFG->libdir/form/submit.php"); 1194 $submitlink = new MoodleQuickForm_submit($checkboxcontrollername, $attributes); 1195 $mform->addElement($submitlink); 1196 $mform->registerNoSubmitButton($checkboxcontrollername); 1197 $mform->setDefault($checkboxcontrollername, $text); 1198 } 1199 1200 /** 1201 * Use this method to a cancel and submit button to the end of your form. Pass a param of false 1202 * if you don't want a cancel button in your form. If you have a cancel button make sure you 1203 * check for it being pressed using is_cancelled() and redirecting if it is true before trying to 1204 * get data with get_data(). 1205 * 1206 * @param bool $cancel whether to show cancel button, default true 1207 * @param string $submitlabel label for submit button, defaults to get_string('savechanges') 1208 */ 1209 function add_action_buttons($cancel = true, $submitlabel=null){ 1210 if (is_null($submitlabel)){ 1211 $submitlabel = get_string('savechanges'); 1212 } 1213 $mform =& $this->_form; 1214 if ($cancel){ 1215 //when two elements we need a group 1216 $buttonarray=array(); 1217 $buttonarray[] = &$mform->createElement('submit', 'submitbutton', $submitlabel); 1218 $buttonarray[] = &$mform->createElement('cancel'); 1219 $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); 1220 $mform->closeHeaderBefore('buttonar'); 1221 } else { 1222 //no group needed 1223 $mform->addElement('submit', 'submitbutton', $submitlabel); 1224 $mform->closeHeaderBefore('submitbutton'); 1225 } 1226 } 1227 1228 /** 1229 * Adds an initialisation call for a standard JavaScript enhancement. 1230 * 1231 * This function is designed to add an initialisation call for a JavaScript 1232 * enhancement that should exist within javascript-static M.form.init_{enhancementname}. 1233 * 1234 * Current options: 1235 * - Selectboxes 1236 * - smartselect: Turns a nbsp indented select box into a custom drop down 1237 * control that supports multilevel and category selection. 1238 * $enhancement = 'smartselect'; 1239 * $options = array('selectablecategories' => true|false) 1240 * 1241 * @since Moodle 2.0 1242 * @param string|element $element form element for which Javascript needs to be initalized 1243 * @param string $enhancement which init function should be called 1244 * @param array $options options passed to javascript 1245 * @param array $strings strings for javascript 1246 */ 1247 function init_javascript_enhancement($element, $enhancement, array $options=array(), array $strings=null) { 1248 global $PAGE; 1249 if (is_string($element)) { 1250 $element = $this->_form->getElement($element); 1251 } 1252 if (is_object($element)) { 1253 $element->_generateId(); 1254 $elementid = $element->getAttribute('id'); 1255 $PAGE->requires->js_init_call('M.form.init_'.$enhancement, array($elementid, $options)); 1256 if (is_array($strings)) { 1257 foreach ($strings as $string) { 1258 if (is_array($string)) { 1259 call_user_func_array(array($PAGE->requires, 'string_for_js'), $string); 1260 } else { 1261 $PAGE->requires->string_for_js($string, 'moodle'); 1262 } 1263 } 1264 } 1265 } 1266 } 1267 1268 /** 1269 * Returns a JS module definition for the mforms JS 1270 * 1271 * @return array 1272 */ 1273 public static function get_js_module() { 1274 global $CFG; 1275 return array( 1276 'name' => 'mform', 1277 'fullpath' => '/lib/form/form.js', 1278 'requires' => array('base', 'node') 1279 ); 1280 } 1281 1282 /** 1283 * Detects elements with missing setType() declerations. 1284 * 1285 * Finds elements in the form which should a PARAM_ type set and throws a 1286 * developer debug warning for any elements without it. This is to reduce the 1287 * risk of potential security issues by developers mistakenly forgetting to set 1288 * the type. 1289 * 1290 * @return void 1291 */ 1292 private function detectMissingSetType() { 1293 global $CFG; 1294 1295 if (!$CFG->debugdeveloper) { 1296 // Only for devs. 1297 return; 1298 } 1299 1300 $mform = $this->_form; 1301 foreach ($mform->_elements as $element) { 1302 $group = false; 1303 $elements = array($element); 1304 1305 if ($element->getType() == 'group') { 1306 $group = $element; 1307 $elements = $element->getElements(); 1308 } 1309 1310 foreach ($elements as $index => $element) { 1311 switch ($element->getType()) { 1312 case 'hidden': 1313 case 'text': 1314 case 'url': 1315 if ($group) { 1316 $name = $group->getElementName($index); 1317 } else { 1318 $name = $element->getName(); 1319 } 1320 $key = $name; 1321 $found = array_key_exists($key, $mform->_types); 1322 // For repeated elements we need to look for 1323 // the "main" type, not for the one present 1324 // on each repetition. All the stuff in formslib 1325 // (repeat_elements(), updateSubmission()... seems 1326 // to work that way. 1327 while (!$found && strrpos($key, '[') !== false) { 1328 $pos = strrpos($key, '['); 1329 $key = substr($key, 0, $pos); 1330 $found = array_key_exists($key, $mform->_types); 1331 } 1332 if (!$found) { 1333 debugging("Did you remember to call setType() for '$name'? ". 1334 'Defaulting to PARAM_RAW cleaning.', DEBUG_DEVELOPER); 1335 } 1336 break; 1337 } 1338 } 1339 } 1340 } 1341 1342 /** 1343 * Used by tests to simulate submitted form data submission from the user. 1344 * 1345 * For form fields where no data is submitted the default for that field as set by set_data or setDefault will be passed to 1346 * get_data. 1347 * 1348 * This method sets $_POST or $_GET and $_FILES with the data supplied. Our unit test code empties all these 1349 * global arrays after each test. 1350 * 1351 * @param array $simulatedsubmitteddata An associative array of form values (same format as $_POST). 1352 * @param array $simulatedsubmittedfiles An associative array of files uploaded (same format as $_FILES). Can be omitted. 1353 * @param string $method 'post' or 'get', defaults to 'post'. 1354 * @param null $formidentifier the default is to use the class name for this class but you may need to provide 1355 * a different value here for some forms that are used more than once on the 1356 * same page. 1357 */ 1358 public static function mock_submit($simulatedsubmitteddata, $simulatedsubmittedfiles = array(), $method = 'post', 1359 $formidentifier = null) { 1360 $_FILES = $simulatedsubmittedfiles; 1361 if ($formidentifier === null) { 1362 $formidentifier = get_called_class(); 1363 } 1364 $simulatedsubmitteddata['_qf__'.$formidentifier] = 1; 1365 $simulatedsubmitteddata['sesskey'] = sesskey(); 1366 if (strtolower($method) === 'get') { 1367 $_GET = $simulatedsubmitteddata; 1368 } else { 1369 $_POST = $simulatedsubmitteddata; 1370 } 1371 } 1372 } 1373 1374 /** 1375 * MoodleQuickForm implementation 1376 * 1377 * You never extend this class directly. The class methods of this class are available from 1378 * the private $this->_form property on moodleform and its children. You generally only 1379 * call methods on this class from within abstract methods that you override on moodleform such 1380 * as definition and definition_after_data 1381 * 1382 * @package core_form 1383 * @category form 1384 * @copyright 2006 Jamie Pratt <me@jamiep.org> 1385 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1386 */ 1387 class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless { 1388 /** @var array type (PARAM_INT, PARAM_TEXT etc) of element value */ 1389 var $_types = array(); 1390 1391 /** @var array dependent state for the element/'s */ 1392 var $_dependencies = array(); 1393 1394 /** @var array Array of buttons that if pressed do not result in the processing of the form. */ 1395 var $_noSubmitButtons=array(); 1396 1397 /** @var array Array of buttons that if pressed do not result in the processing of the form. */ 1398 var $_cancelButtons=array(); 1399 1400 /** @var array Array whose keys are element names. If the key exists this is a advanced element */ 1401 var $_advancedElements = array(); 1402 1403 /** 1404 * Array whose keys are element names and values are the desired collapsible state. 1405 * True for collapsed, False for expanded. If not present, set to default in 1406 * {@link self::accept()}. 1407 * 1408 * @var array 1409 */ 1410 var $_collapsibleElements = array(); 1411 1412 /** 1413 * Whether to enable shortforms for this form 1414 * 1415 * @var boolean 1416 */ 1417 var $_disableShortforms = false; 1418 1419 /** @var bool whether to automatically initialise M.formchangechecker for this form. */ 1420 protected $_use_form_change_checker = true; 1421 1422 /** 1423 * The form name is derived from the class name of the wrapper minus the trailing form 1424 * It is a name with words joined by underscores whereas the id attribute is words joined by underscores. 1425 * @var string 1426 */ 1427 var $_formName = ''; 1428 1429 /** 1430 * String with the html for hidden params passed in as part of a moodle_url 1431 * object for the action. Output in the form. 1432 * @var string 1433 */ 1434 var $_pageparams = ''; 1435 1436 /** 1437 * Whether the form contains any client-side validation or not. 1438 * @var bool 1439 */ 1440 protected $clientvalidation = false; 1441 1442 /** 1443 * Class constructor - same parameters as HTML_QuickForm_DHTMLRulesTableless 1444 * 1445 * @staticvar int $formcounter counts number of forms 1446 * @param string $formName Form's name. 1447 * @param string $method Form's method defaults to 'POST' 1448 * @param string|moodle_url $action Form's action 1449 * @param string $target (optional)Form's target defaults to none 1450 * @param mixed $attributes (optional)Extra attributes for <form> tag 1451 */ 1452 public function __construct($formName, $method, $action, $target='', $attributes=null) { 1453 global $CFG, $OUTPUT; 1454 1455 static $formcounter = 1; 1456 1457 // TODO MDL-52313 Replace with the call to parent::__construct(). 1458 HTML_Common::__construct($attributes); 1459 $target = empty($target) ? array() : array('target' => $target); 1460 $this->_formName = $formName; 1461 if (is_a($action, 'moodle_url')){ 1462 $this->_pageparams = html_writer::input_hidden_params($action); 1463 $action = $action->out_omit_querystring(); 1464 } else { 1465 $this->_pageparams = ''; 1466 } 1467 // No 'name' atttribute for form in xhtml strict : 1468 $attributes = array('action' => $action, 'method' => $method, 'accept-charset' => 'utf-8') + $target; 1469 if (is_null($this->getAttribute('id'))) { 1470 $attributes['id'] = 'mform' . $formcounter; 1471 } 1472 $formcounter++; 1473 $this->updateAttributes($attributes); 1474 1475 // This is custom stuff for Moodle : 1476 $oldclass= $this->getAttribute('class'); 1477 if (!empty($oldclass)){ 1478 $this->updateAttributes(array('class'=>$oldclass.' mform')); 1479 }else { 1480 $this->updateAttributes(array('class'=>'mform')); 1481 } 1482 $this->_reqHTML = '<img class="req" title="'.get_string('requiredelement', 'form').'" alt="'.get_string('requiredelement', 'form').'" src="'.$OUTPUT->pix_url('req') .'" />'; 1483 $this->_advancedHTML = '<img class="adv" title="'.get_string('advancedelement', 'form').'" alt="'.get_string('advancedelement', 'form').'" src="'.$OUTPUT->pix_url('adv') .'" />'; 1484 $this->setRequiredNote(get_string('somefieldsrequired', 'form', '<img alt="'.get_string('requiredelement', 'form').'" src="'.$OUTPUT->pix_url('req') .'" />')); 1485 } 1486 1487 /** 1488 * Old syntax of class constructor. Deprecated in PHP7. 1489 * 1490 * @deprecated since Moodle 3.1 1491 */ 1492 public function MoodleQuickForm($formName, $method, $action, $target='', $attributes=null) { 1493 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 1494 self::__construct($formName, $method, $action, $target, $attributes); 1495 } 1496 1497 /** 1498 * Use this method to indicate an element in a form is an advanced field. If items in a form 1499 * are marked as advanced then 'Hide/Show Advanced' buttons will automatically be displayed in the 1500 * form so the user can decide whether to display advanced form controls. 1501 * 1502 * If you set a header element to advanced then all elements it contains will also be set as advanced. 1503 * 1504 * @param string $elementName group or element name (not the element name of something inside a group). 1505 * @param bool $advanced default true sets the element to advanced. False removes advanced mark. 1506 */ 1507 function setAdvanced($elementName, $advanced = true) { 1508 if ($advanced){ 1509 $this->_advancedElements[$elementName]=''; 1510 } elseif (isset($this->_advancedElements[$elementName])) { 1511 unset($this->_advancedElements[$elementName]); 1512 } 1513 } 1514 1515 /** 1516 * Use this method to indicate that the fieldset should be shown as expanded. 1517 * The method is applicable to header elements only. 1518 * 1519 * @param string $headername header element name 1520 * @param boolean $expanded default true sets the element to expanded. False makes the element collapsed. 1521 * @param boolean $ignoreuserstate override the state regardless of the state it was on when 1522 * the form was submitted. 1523 * @return void 1524 */ 1525 function setExpanded($headername, $expanded = true, $ignoreuserstate = false) { 1526 if (empty($headername)) { 1527 return; 1528 } 1529 $element = $this->getElement($headername); 1530 if ($element->getType() != 'header') { 1531 debugging('Cannot use setExpanded on non-header elements', DEBUG_DEVELOPER); 1532 return; 1533 } 1534 if (!$headerid = $element->getAttribute('id')) { 1535 $element->_generateId(); 1536 $headerid = $element->getAttribute('id'); 1537 } 1538 if ($this->getElementType('mform_isexpanded_' . $headerid) === false) { 1539 // See if the form has been submitted already. 1540 $formexpanded = optional_param('mform_isexpanded_' . $headerid, -1, PARAM_INT); 1541 if (!$ignoreuserstate && $formexpanded != -1) { 1542 // Override expanded state with the form variable. 1543 $expanded = $formexpanded; 1544 } 1545 // Create the form element for storing expanded state. 1546 $this->addElement('hidden', 'mform_isexpanded_' . $headerid); 1547 $this->setType('mform_isexpanded_' . $headerid, PARAM_INT); 1548 $this->setConstant('mform_isexpanded_' . $headerid, (int) $expanded); 1549 } 1550 $this->_collapsibleElements[$headername] = !$expanded; 1551 } 1552 1553 /** 1554 * Use this method to add show more/less status element required for passing 1555 * over the advanced elements visibility status on the form submission. 1556 * 1557 * @param string $headerName header element name. 1558 * @param boolean $showmore default false sets the advanced elements to be hidden. 1559 */ 1560 function addAdvancedStatusElement($headerid, $showmore=false){ 1561 // Add extra hidden element to store advanced items state for each section. 1562 if ($this->getElementType('mform_showmore_' . $headerid) === false) { 1563 // See if we the form has been submitted already. 1564 $formshowmore = optional_param('mform_showmore_' . $headerid, -1, PARAM_INT); 1565 if (!$showmore && $formshowmore != -1) { 1566 // Override showmore state with the form variable. 1567 $showmore = $formshowmore; 1568 } 1569 // Create the form element for storing advanced items state. 1570 $this->addElement('hidden', 'mform_showmore_' . $headerid); 1571 $this->setType('mform_showmore_' . $headerid, PARAM_INT); 1572 $this->setConstant('mform_showmore_' . $headerid, (int)$showmore); 1573 } 1574 } 1575 1576 /** 1577 * This function has been deprecated. Show advanced has been replaced by 1578 * "Show more.../Show less..." in the shortforms javascript module. 1579 * 1580 * @deprecated since Moodle 2.5 1581 * @param bool $showadvancedNow if true will show advanced elements. 1582 */ 1583 function setShowAdvanced($showadvancedNow = null){ 1584 debugging('Call to deprecated function setShowAdvanced. See "Show more.../Show less..." in shortforms yui module.'); 1585 } 1586 1587 /** 1588 * This function has been deprecated. Show advanced has been replaced by 1589 * "Show more.../Show less..." in the shortforms javascript module. 1590 * 1591 * @deprecated since Moodle 2.5 1592 * @return bool (Always false) 1593 */ 1594 function getShowAdvanced(){ 1595 debugging('Call to deprecated function setShowAdvanced. See "Show more.../Show less..." in shortforms yui module.'); 1596 return false; 1597 } 1598 1599 /** 1600 * Use this method to indicate that the form will not be using shortforms. 1601 * 1602 * @param boolean $disable default true, controls if the shortforms are disabled. 1603 */ 1604 function setDisableShortforms ($disable = true) { 1605 $this->_disableShortforms = $disable; 1606 } 1607 1608 /** 1609 * Call this method if you don't want the formchangechecker JavaScript to be 1610 * automatically initialised for this form. 1611 */ 1612 public function disable_form_change_checker() { 1613 $this->_use_form_change_checker = false; 1614 } 1615 1616 /** 1617 * If you have called {@link disable_form_change_checker()} then you can use 1618 * this method to re-enable it. It is enabled by default, so normally you don't 1619 * need to call this. 1620 */ 1621 public function enable_form_change_checker() { 1622 $this->_use_form_change_checker = true; 1623 } 1624 1625 /** 1626 * @return bool whether this form should automatically initialise 1627 * formchangechecker for itself. 1628 */ 1629 public function is_form_change_checker_enabled() { 1630 return $this->_use_form_change_checker; 1631 } 1632 1633 /** 1634 * Accepts a renderer 1635 * 1636 * @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object 1637 */ 1638 function accept(&$renderer) { 1639 if (method_exists($renderer, 'setAdvancedElements')){ 1640 //Check for visible fieldsets where all elements are advanced 1641 //and mark these headers as advanced as well. 1642 //Also mark all elements in a advanced header as advanced. 1643 $stopFields = $renderer->getStopFieldSetElements(); 1644 $lastHeader = null; 1645 $lastHeaderAdvanced = false; 1646 $anyAdvanced = false; 1647 $anyError = false; 1648 foreach (array_keys($this->_elements) as $elementIndex){ 1649 $element =& $this->_elements[$elementIndex]; 1650 1651 // if closing header and any contained element was advanced then mark it as advanced 1652 if ($element->getType()=='header' || in_array($element->getName(), $stopFields)){ 1653 if ($anyAdvanced && !is_null($lastHeader)) { 1654 $lastHeader->_generateId(); 1655 $this->setAdvanced($lastHeader->getName()); 1656 $this->addAdvancedStatusElement($lastHeader->getAttribute('id'), $anyError); 1657 } 1658 $lastHeaderAdvanced = false; 1659 unset($lastHeader); 1660 $lastHeader = null; 1661 } elseif ($lastHeaderAdvanced) { 1662 $this->setAdvanced($element->getName()); 1663 } 1664 1665 if ($element->getType()=='header'){ 1666 $lastHeader =& $element; 1667 $anyAdvanced = false; 1668 $anyError = false; 1669 $lastHeaderAdvanced = isset($this->_advancedElements[$element->getName()]); 1670 } elseif (isset($this->_advancedElements[$element->getName()])){ 1671 $anyAdvanced = true; 1672 if (isset($this->_errors[$element->getName()])) { 1673 $anyError = true; 1674 } 1675 } 1676 } 1677 // the last header may not be closed yet... 1678 if ($anyAdvanced && !is_null($lastHeader)){ 1679 $this->setAdvanced($lastHeader->getName()); 1680 $lastHeader->_generateId(); 1681 $this->addAdvancedStatusElement($lastHeader->getAttribute('id'), $anyError); 1682 } 1683 $renderer->setAdvancedElements($this->_advancedElements); 1684 } 1685 if (method_exists($renderer, 'setCollapsibleElements') && !$this->_disableShortforms) { 1686 1687 // Count the number of sections. 1688 $headerscount = 0; 1689 foreach (array_keys($this->_elements) as $elementIndex){ 1690 $element =& $this->_elements[$elementIndex]; 1691 if ($element->getType() == 'header') { 1692 $headerscount++; 1693 } 1694 } 1695 1696 $anyrequiredorerror = false; 1697 $headercounter = 0; 1698 $headername = null; 1699 foreach (array_keys($this->_elements) as $elementIndex){ 1700 $element =& $this->_elements[$elementIndex]; 1701 1702 if ($element->getType() == 'header') { 1703 $headercounter++; 1704 $element->_generateId(); 1705 $headername = $element->getName(); 1706 $anyrequiredorerror = false; 1707 } else if (in_array($element->getName(), $this->_required) || isset($this->_errors[$element->getName()])) { 1708 $anyrequiredorerror = true; 1709 } else { 1710 // Do not reset $anyrequiredorerror to false because we do not want any other element 1711 // in this header (fieldset) to possibly revert the state given. 1712 } 1713 1714 if ($element->getType() == 'header') { 1715 if ($headercounter === 1 && !isset($this->_collapsibleElements[$headername])) { 1716 // By default the first section is always expanded, except if a state has already been set. 1717 $this->setExpanded($headername, true); 1718 } else if (($headercounter === 2 && $headerscount === 2) && !isset($this->_collapsibleElements[$headername])) { 1719 // The second section is always expanded if the form only contains 2 sections), 1720 // except if a state has already been set. 1721 $this->setExpanded($headername, true); 1722 } 1723 } else if ($anyrequiredorerror) { 1724 // If any error or required field are present within the header, we need to expand it. 1725 $this->setExpanded($headername, true, true); 1726 } else if (!isset($this->_collapsibleElements[$headername])) { 1727 // Define element as collapsed by default. 1728 $this->setExpanded($headername, false); 1729 } 1730 } 1731 1732 // Pass the array to renderer object. 1733 $renderer->setCollapsibleElements($this->_collapsibleElements); 1734 } 1735 parent::accept($renderer); 1736 } 1737 1738 /** 1739 * Adds one or more element names that indicate the end of a fieldset 1740 * 1741 * @param string $elementName name of the element 1742 */ 1743 function closeHeaderBefore($elementName){ 1744 $renderer =& $this->defaultRenderer(); 1745 $renderer->addStopFieldsetElements($elementName); 1746 } 1747 1748 /** 1749 * Should be used for all elements of a form except for select, radio and checkboxes which 1750 * clean their own data. 1751 * 1752 * @param string $elementname 1753 * @param int $paramtype defines type of data contained in element. Use the constants PARAM_*. 1754 * {@link lib/moodlelib.php} for defined parameter types 1755 */ 1756 function setType($elementname, $paramtype) { 1757 $this->_types[$elementname] = $paramtype; 1758 } 1759 1760 /** 1761 * This can be used to set several types at once. 1762 * 1763 * @param array $paramtypes types of parameters. 1764 * @see MoodleQuickForm::setType 1765 */ 1766 function setTypes($paramtypes) { 1767 $this->_types = $paramtypes + $this->_types; 1768 } 1769 1770 /** 1771 * Return the type(s) to use to clean an element. 1772 * 1773 * In the case where the element has an array as a value, we will try to obtain a 1774 * type defined for that specific key, and recursively until done. 1775 * 1776 * This method does not work reverse, you cannot pass a nested element and hoping to 1777 * fallback on the clean type of a parent. This method intends to be used with the 1778 * main element, which will generate child types if needed, not the other way around. 1779 * 1780 * Example scenario: 1781 * 1782 * You have defined a new repeated element containing a text field called 'foo'. 1783 * By default there will always be 2 occurence of 'foo' in the form. Even though 1784 * you've set the type on 'foo' to be PARAM_INT, for some obscure reason, you want 1785 * the first value of 'foo', to be PARAM_FLOAT, which you set using setType: 1786 * $mform->setType('foo[0]', PARAM_FLOAT). 1787 * 1788 * Now if you call this method passing 'foo', along with the submitted values of 'foo': 1789 * array(0 => '1.23', 1 => '10'), you will get an array telling you that the key 0 is a 1790 * FLOAT and 1 is an INT. If you had passed 'foo[1]', along with its value '10', you would 1791 * get the default clean type returned (param $default). 1792 * 1793 * @param string $elementname name of the element. 1794 * @param mixed $value value that should be cleaned. 1795 * @param int $default default constant value to be returned (PARAM_...) 1796 * @return string|array constant value or array of constant values (PARAM_...) 1797 */ 1798 public function getCleanType($elementname, $value, $default = PARAM_RAW) { 1799 $type = $default; 1800 if (array_key_exists($elementname, $this->_types)) { 1801 $type = $this->_types[$elementname]; 1802 } 1803 if (is_array($value)) { 1804 $default = $type; 1805 $type = array(); 1806 foreach ($value as $subkey => $subvalue) { 1807 $typekey = "$elementname" . "[$subkey]"; 1808 if (array_key_exists($typekey, $this->_types)) { 1809 $subtype = $this->_types[$typekey]; 1810 } else { 1811 $subtype = $default; 1812 } 1813 if (is_array($subvalue)) { 1814 $type[$subkey] = $this->getCleanType($typekey, $subvalue, $subtype); 1815 } else { 1816 $type[$subkey] = $subtype; 1817 } 1818 } 1819 } 1820 return $type; 1821 } 1822 1823 /** 1824 * Return the cleaned value using the passed type(s). 1825 * 1826 * @param mixed $value value that has to be cleaned. 1827 * @param int|array $type constant value to use to clean (PARAM_...), typically returned by {@link self::getCleanType()}. 1828 * @return mixed cleaned up value. 1829 */ 1830 public function getCleanedValue($value, $type) { 1831 if (is_array($type) && is_array($value)) { 1832 foreach ($type as $key => $param) { 1833 $value[$key] = $this->getCleanedValue($value[$key], $param); 1834 } 1835 } else if (!is_array($type) && !is_array($value)) { 1836 $value = clean_param($value, $type); 1837 } else if (!is_array($type) && is_array($value)) { 1838 $value = clean_param_array($value, $type, true); 1839 } else { 1840 throw new coding_exception('Unexpected type or value received in MoodleQuickForm::getCleanedValue()'); 1841 } 1842 return $value; 1843 } 1844 1845 /** 1846 * Updates submitted values 1847 * 1848 * @param array $submission submitted values 1849 * @param array $files list of files 1850 */ 1851 function updateSubmission($submission, $files) { 1852 $this->_flagSubmitted = false; 1853 1854 if (empty($submission)) { 1855 $this->_submitValues = array(); 1856 } else { 1857 foreach ($submission as $key => $s) { 1858 $type = $this->getCleanType($key, $s); 1859 $submission[$key] = $this->getCleanedValue($s, $type); 1860 } 1861 $this->_submitValues = $submission; 1862 $this->_flagSubmitted = true; 1863 } 1864 1865 if (empty($files)) { 1866 $this->_submitFiles = array(); 1867 } else { 1868 $this->_submitFiles = $files; 1869 $this->_flagSubmitted = true; 1870 } 1871 1872 // need to tell all elements that they need to update their value attribute. 1873 foreach (array_keys($this->_elements) as $key) { 1874 $this->_elements[$key]->onQuickFormEvent('updateValue', null, $this); 1875 } 1876 } 1877 1878 /** 1879 * Returns HTML for required elements 1880 * 1881 * @return string 1882 */ 1883 function getReqHTML(){ 1884 return $this->_reqHTML; 1885 } 1886 1887 /** 1888 * Returns HTML for advanced elements 1889 * 1890 * @return string 1891 */ 1892 function getAdvancedHTML(){ 1893 return $this->_advancedHTML; 1894 } 1895 1896 /** 1897 * Initializes a default form value. Used to specify the default for a new entry where 1898 * no data is loaded in using moodleform::set_data() 1899 * 1900 * note: $slashed param removed 1901 * 1902 * @param string $elementName element name 1903 * @param mixed $defaultValue values for that element name 1904 */ 1905 function setDefault($elementName, $defaultValue){ 1906 $this->setDefaults(array($elementName=>$defaultValue)); 1907 } 1908 1909 /** 1910 * Add a help button to element, only one button per element is allowed. 1911 * 1912 * This is new, simplified and preferable method of setting a help icon on form elements. 1913 * It uses the new $OUTPUT->help_icon(). 1914 * 1915 * Typically, you will provide the same identifier and the component as you have used for the 1916 * label of the element. The string identifier with the _help suffix added is then used 1917 * as the help string. 1918 * 1919 * There has to be two strings defined: 1920 * 1/ get_string($identifier, $component) - the title of the help page 1921 * 2/ get_string($identifier.'_help', $component) - the actual help page text 1922 * 1923 * @since Moodle 2.0 1924 * @param string $elementname name of the element to add the item to 1925 * @param string $identifier help string identifier without _help suffix 1926 * @param string $component component name to look the help string in 1927 * @param string $linktext optional text to display next to the icon 1928 * @param bool $suppresscheck set to true if the element may not exist 1929 */ 1930 function addHelpButton($elementname, $identifier, $component = 'moodle', $linktext = '', $suppresscheck = false) { 1931 global $OUTPUT; 1932 if (array_key_exists($elementname, $this->_elementIndex)) { 1933 $element = $this->_elements[$this->_elementIndex[$elementname]]; 1934 $element->_helpbutton = $OUTPUT->help_icon($identifier, $component, $linktext); 1935 } else if (!$suppresscheck) { 1936 debugging(get_string('nonexistentformelements', 'form', $elementname)); 1937 } 1938 } 1939 1940 /** 1941 * Set constant value not overridden by _POST or _GET 1942 * note: this does not work for complex names with [] :-( 1943 * 1944 * @param string $elname name of element 1945 * @param mixed $value 1946 */ 1947 function setConstant($elname, $value) { 1948 $this->_constantValues = HTML_QuickForm::arrayMerge($this->_constantValues, array($elname=>$value)); 1949 $element =& $this->getElement($elname); 1950 $element->onQuickFormEvent('updateValue', null, $this); 1951 } 1952 1953 /** 1954 * export submitted values 1955 * 1956 * @param string $elementList list of elements in form 1957 * @return array 1958 */ 1959 function exportValues($elementList = null){ 1960 $unfiltered = array(); 1961 if (null === $elementList) { 1962 // iterate over all elements, calling their exportValue() methods 1963 foreach (array_keys($this->_elements) as $key) { 1964 if ($this->_elements[$key]->isFrozen() && !$this->_elements[$key]->_persistantFreeze) { 1965 $varname = $this->_elements[$key]->_attributes['name']; 1966 $value = ''; 1967 // If we have a default value then export it. 1968 if (isset($this->_defaultValues[$varname])) { 1969 $value = $this->prepare_fixed_value($varname, $this->_defaultValues[$varname]); 1970 } 1971 } else { 1972 $value = $this->_elements[$key]->exportValue($this->_submitValues, true); 1973 } 1974 1975 if (is_array($value)) { 1976 // This shit throws a bogus warning in PHP 4.3.x 1977 $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $value); 1978 } 1979 } 1980 } else { 1981 if (!is_array($elementList)) { 1982 $elementList = array_map('trim', explode(',', $elementList)); 1983 } 1984 foreach ($elementList as $elementName) { 1985 $value = $this->exportValue($elementName); 1986 if (@PEAR::isError($value)) { 1987 return $value; 1988 } 1989 //oh, stock QuickFOrm was returning array of arrays! 1990 $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $value); 1991 } 1992 } 1993 1994 if (is_array($this->_constantValues)) { 1995 $unfiltered = HTML_QuickForm::arrayMerge($unfiltered, $this->_constantValues); 1996 } 1997 return $unfiltered; 1998 } 1999 2000 /** 2001 * This is a bit of a hack, and it duplicates the code in 2002 * HTML_QuickForm_element::_prepareValue, but I could not think of a way or 2003 * reliably calling that code. (Think about date selectors, for example.) 2004 * @param string $name the element name. 2005 * @param mixed $value the fixed value to set. 2006 * @return mixed the appropriate array to add to the $unfiltered array. 2007 */ 2008 protected function prepare_fixed_value($name, $value) { 2009 if (null === $value) { 2010 return null; 2011 } else { 2012 if (!strpos($name, '[')) { 2013 return array($name => $value); 2014 } else { 2015 $valueAry = array(); 2016 $myIndex = "['" . str_replace(array(']', '['), array('', "']['"), $name) . "']"; 2017 eval("\$valueAry$myIndex = \$value;"); 2018 return $valueAry; 2019 } 2020 } 2021 } 2022 2023 /** 2024 * Adds a validation rule for the given field 2025 * 2026 * If the element is in fact a group, it will be considered as a whole. 2027 * To validate grouped elements as separated entities, 2028 * use addGroupRule instead of addRule. 2029 * 2030 * @param string $element Form element name 2031 * @param string $message Message to display for invalid data 2032 * @param string $type Rule type, use getRegisteredRules() to get types 2033 * @param string $format (optional)Required for extra rule data 2034 * @param string $validation (optional)Where to perform validation: "server", "client" 2035 * @param bool $reset Client-side validation: reset the form element to its original value if there is an error? 2036 * @param bool $force Force the rule to be applied, even if the target form element does not exist 2037 */ 2038 function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false) 2039 { 2040 parent::addRule($element, $message, $type, $format, $validation, $reset, $force); 2041 if ($validation == 'client') { 2042 $this->clientvalidation = true; 2043 } 2044 2045 } 2046 2047 /** 2048 * Adds a validation rule for the given group of elements 2049 * 2050 * Only groups with a name can be assigned a validation rule 2051 * Use addGroupRule when you need to validate elements inside the group. 2052 * Use addRule if you need to validate the group as a whole. In this case, 2053 * the same rule will be applied to all elements in the group. 2054 * Use addRule if you need to validate the group against a function. 2055 * 2056 * @param string $group Form group name 2057 * @param array|string $arg1 Array for multiple elements or error message string for one element 2058 * @param string $type (optional)Rule type use getRegisteredRules() to get types 2059 * @param string $format (optional)Required for extra rule data 2060 * @param int $howmany (optional)How many valid elements should be in the group 2061 * @param string $validation (optional)Where to perform validation: "server", "client" 2062 * @param bool $reset Client-side: whether to reset the element's value to its original state if validation failed. 2063 */ 2064 function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false) 2065 { 2066 parent::addGroupRule($group, $arg1, $type, $format, $howmany, $validation, $reset); 2067 if (is_array($arg1)) { 2068 foreach ($arg1 as $rules) { 2069 foreach ($rules as $rule) { 2070 $validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server'; 2071 if ($validation == 'client') { 2072 $this->clientvalidation = true; 2073 } 2074 } 2075 } 2076 } elseif (is_string($arg1)) { 2077 if ($validation == 'client') { 2078 $this->clientvalidation = true; 2079 } 2080 } 2081 } 2082 2083 /** 2084 * Returns the client side validation script 2085 * 2086 * The code here was copied from HTML_QuickForm_DHTMLRulesTableless who copied it from HTML_QuickForm 2087 * and slightly modified to run rules per-element 2088 * Needed to override this because of an error with client side validation of grouped elements. 2089 * 2090 * @return string Javascript to perform validation, empty string if no 'client' rules were added 2091 */ 2092 function getValidationScript() 2093 { 2094 if (empty($this->_rules) || $this->clientvalidation === false) { 2095 return ''; 2096 } 2097 2098 include_once('HTML/QuickForm/RuleRegistry.php'); 2099 $registry =& HTML_QuickForm_RuleRegistry::singleton(); 2100 $test = array(); 2101 $js_escape = array( 2102 "\r" => '\r', 2103 "\n" => '\n', 2104 "\t" => '\t', 2105 "'" => "\\'", 2106 '"' => '\"', 2107 '\\' => '\\\\' 2108 ); 2109 2110 foreach ($this->_rules as $elementName => $rules) { 2111 foreach ($rules as $rule) { 2112 if ('client' == $rule['validation']) { 2113 unset($element); //TODO: find out how to properly initialize it 2114 2115 $dependent = isset($rule['dependent']) && is_array($rule['dependent']); 2116 $rule['message'] = strtr($rule['message'], $js_escape); 2117 2118 if (isset($rule['group'])) { 2119 $group =& $this->getElement($rule['group']); 2120 // No JavaScript validation for frozen elements 2121 if ($group->isFrozen()) { 2122 continue 2; 2123 } 2124 $elements =& $group->getElements(); 2125 foreach (array_keys($elements) as $key) { 2126 if ($elementName == $group->getElementName($key)) { 2127 $element =& $elements[$key]; 2128 break; 2129 } 2130 } 2131 } elseif ($dependent) { 2132 $element = array(); 2133 $element[] =& $this->getElement($elementName); 2134 foreach ($rule['dependent'] as $elName) { 2135 $element[] =& $this->getElement($elName); 2136 } 2137 } else { 2138 $element =& $this->getElement($elementName); 2139 } 2140 // No JavaScript validation for frozen elements 2141 if (is_object($element) && $element->isFrozen()) { 2142 continue 2; 2143 } elseif (is_array($element)) { 2144 foreach (array_keys($element) as $key) { 2145 if ($element[$key]->isFrozen()) { 2146 continue 3; 2147 } 2148 } 2149 } 2150 //for editor element, [text] is appended to the name. 2151 $fullelementname = $elementName; 2152 if ($element->getType() == 'editor') { 2153 $fullelementname .= '[text]'; 2154 //Add format to rule as moodleform check which format is supported by browser 2155 //it is not set anywhere... So small hack to make sure we pass it down to quickform 2156 if (is_null($rule['format'])) { 2157 $rule['format'] = $element->getFormat(); 2158 } 2159 } 2160 // Fix for bug displaying errors for elements in a group 2161 $test[$fullelementname][0][] = $registry->getValidationScript($element, $fullelementname, $rule); 2162 $test[$fullelementname][1]=$element; 2163 //end of fix 2164 } 2165 } 2166 } 2167 2168 // Fix for MDL-9524. If you don't do this, then $element may be left as a reference to one of the fields in 2169 // the form, and then that form field gets corrupted by the code that follows. 2170 unset($element); 2171 2172 $js = ' 2173 <script type="text/javascript"> 2174 //<![CDATA[ 2175 2176 var skipClientValidation = false; 2177 2178 (function() { 2179 2180 function qf_errorHandler(element, _qfMsg, escapedName) { 2181 div = element.parentNode; 2182 2183 if ((div == undefined) || (element.name == undefined)) { 2184 //no checking can be done for undefined elements so let server handle it. 2185 return true; 2186 } 2187 2188 if (_qfMsg != \'\') { 2189 var errorSpan = document.getElementById(\'id_error_\' + escapedName); 2190 if (!errorSpan) { 2191 errorSpan = document.createElement("span"); 2192 errorSpan.id = \'id_error_\' + escapedName; 2193 errorSpan.className = "error"; 2194 element.parentNode.insertBefore(errorSpan, element.parentNode.firstChild); 2195 document.getElementById(errorSpan.id).setAttribute(\'TabIndex\', \'0\'); 2196 document.getElementById(errorSpan.id).focus(); 2197 } 2198 2199 while (errorSpan.firstChild) { 2200 errorSpan.removeChild(errorSpan.firstChild); 2201 } 2202 2203 errorSpan.appendChild(document.createTextNode(_qfMsg.substring(3))); 2204 2205 if (div.className.substr(div.className.length - 6, 6) != " error" 2206 && div.className != "error") { 2207 div.className += " error"; 2208 linebreak = document.createElement("br"); 2209 linebreak.className = "error"; 2210 linebreak.id = \'id_error_break_\' + escapedName; 2211 errorSpan.parentNode.insertBefore(linebreak, errorSpan.nextSibling); 2212 } 2213 2214 return false; 2215 } else { 2216 var errorSpan = document.getElementById(\'id_error_\' + escapedName); 2217 if (errorSpan) { 2218 errorSpan.parentNode.removeChild(errorSpan); 2219 } 2220 var linebreak = document.getElementById(\'id_error_break_\' + escapedName); 2221 if (linebreak) { 2222 linebreak.parentNode.removeChild(linebreak); 2223 } 2224 2225 if (div.className.substr(div.className.length - 6, 6) == " error") { 2226 div.className = div.className.substr(0, div.className.length - 6); 2227 } else if (div.className == "error") { 2228 div.className = ""; 2229 } 2230 2231 return true; 2232 } 2233 }'; 2234 $validateJS = ''; 2235 foreach ($test as $elementName => $jsandelement) { 2236 // Fix for bug displaying errors for elements in a group 2237 //unset($element); 2238 list($jsArr,$element)=$jsandelement; 2239 //end of fix 2240 $escapedElementName = preg_replace_callback( 2241 '/[_\[\]-]/', 2242 create_function('$matches', 'return sprintf("_%2x",ord($matches[0]));'), 2243 $elementName); 2244 $valFunc = 'validate_' . $this->_formName . '_' . $escapedElementName . '(ev.target, \''.$escapedElementName.'\')'; 2245 2246 $js .= ' 2247 function validate_' . $this->_formName . '_' . $escapedElementName . '(element, escapedName) { 2248 if (undefined == element) { 2249 //required element was not found, then let form be submitted without client side validation 2250 return true; 2251 } 2252 var value = \'\'; 2253 var errFlag = new Array(); 2254 var _qfGroups = {}; 2255 var _qfMsg = \'\'; 2256 var frm = element.parentNode; 2257 if ((undefined != element.name) && (frm != undefined)) { 2258 while (frm && frm.nodeName.toUpperCase() != "FORM") { 2259 frm = frm.parentNode; 2260 } 2261 ' . join("\n", $jsArr) . ' 2262 return qf_errorHandler(element, _qfMsg, escapedName); 2263 } else { 2264 //element name should be defined else error msg will not be displayed. 2265 return true; 2266 } 2267 } 2268 2269 document.getElementById(\'' . $element->_attributes['id'] . '\').addEventListener(\'blur\', function(ev) { 2270 ' . $valFunc . ' 2271 }); 2272 document.getElementById(\'' . $element->_attributes['id'] . '\').addEventListener(\'change\', function(ev) { 2273 ' . $valFunc . ' 2274 }); 2275 '; 2276 $validateJS .= ' 2277 ret = validate_' . $this->_formName . '_' . $escapedElementName.'(frm.elements[\''.$elementName.'\'], \''.$escapedElementName.'\') && ret; 2278 if (!ret && !first_focus) { 2279 first_focus = true; 2280 Y.use(\'moodle-core-event\', function() { 2281 Y.Global.fire(M.core.globalEvents.FORM_ERROR, {formid: \'' . $this->_attributes['id'] . '\', 2282 elementid: \'id_error_' . $escapedElementName . '\'}); 2283 document.getElementById(\'id_error_' . $escapedElementName . '\').focus(); 2284 }); 2285 } 2286 '; 2287 2288 // Fix for bug displaying errors for elements in a group 2289 //unset($element); 2290 //$element =& $this->getElement($elementName); 2291 //end of fix 2292 //$onBlur = $element->getAttribute('onBlur'); 2293 //$onChange = $element->getAttribute('onChange'); 2294 //$element->updateAttributes(array('onBlur' => $onBlur . $valFunc, 2295 //'onChange' => $onChange . $valFunc)); 2296 } 2297 // do not rely on frm function parameter, because htmlarea breaks it when overloading the onsubmit method 2298 $js .= ' 2299 2300 function validate_' . $this->_formName . '() { 2301 if (skipClientValidation) { 2302 return true; 2303 } 2304 var ret = true; 2305 2306 var frm = document.getElementById(\''. $this->_attributes['id'] .'\') 2307 var first_focus = false; 2308 ' . $validateJS . '; 2309 return ret; 2310 } 2311 2312 2313 document.getElementById(\'' . $this->_attributes['id'] . '\').addEventListener(\'submit\', function(ev) { 2314 try { 2315 var myValidator = validate_' . $this->_formName . '; 2316 } catch(e) { 2317 return true; 2318 } 2319 if (typeof window.tinyMCE !== \'undefined\') { 2320 window.tinyMCE.triggerSave(); 2321 } 2322 if (!myValidator()) { 2323 ev.preventDefault(); 2324 } 2325 }); 2326 })(); 2327 //]]> 2328 </script>'; 2329 return $js; 2330 } // end func getValidationScript 2331 2332 /** 2333 * Sets default error message 2334 */ 2335 function _setDefaultRuleMessages(){ 2336 foreach ($this->_rules as $field => $rulesarr){ 2337 foreach ($rulesarr as $key => $rule){ 2338 if ($rule['message']===null){ 2339 $a=new stdClass(); 2340 $a->format=$rule['format']; 2341 $str=get_string('err_'.$rule['type'], 'form', $a); 2342 if (strpos($str, '[[')!==0){ 2343 $this->_rules[$field][$key]['message']=$str; 2344 } 2345 } 2346 } 2347 } 2348 } 2349 2350 /** 2351 * Get list of attributes which have dependencies 2352 * 2353 * @return array 2354 */ 2355 function getLockOptionObject(){ 2356 $result = array(); 2357 foreach ($this->_dependencies as $dependentOn => $conditions){ 2358 $result[$dependentOn] = array(); 2359 foreach ($conditions as $condition=>$values) { 2360 $result[$dependentOn][$condition] = array(); 2361 foreach ($values as $value=>$dependents) { 2362 $result[$dependentOn][$condition][$value] = array(); 2363 $i = 0; 2364 foreach ($dependents as $dependent) { 2365 $elements = $this->_getElNamesRecursive($dependent); 2366 if (empty($elements)) { 2367 // probably element inside of some group 2368 $elements = array($dependent); 2369 } 2370 foreach($elements as $element) { 2371 if ($element == $dependentOn) { 2372 continue; 2373 } 2374 $result[$dependentOn][$condition][$value][] = $element; 2375 } 2376 } 2377 } 2378 } 2379 } 2380 return array($this->getAttribute('id'), $result); 2381 } 2382 2383 /** 2384 * Get names of element or elements in a group. 2385 * 2386 * @param HTML_QuickForm_group|element $element element group or element object 2387 * @return array 2388 */ 2389 function _getElNamesRecursive($element) { 2390 if (is_string($element)) { 2391 if (!$this->elementExists($element)) { 2392 return array(); 2393 } 2394 $element = $this->getElement($element); 2395 } 2396 2397 if (is_a($element, 'HTML_QuickForm_group')) { 2398 $elsInGroup = $element->getElements(); 2399 $elNames = array(); 2400 foreach ($elsInGroup as $elInGroup){ 2401 if (is_a($elInGroup, 'HTML_QuickForm_group')) { 2402 // not sure if this would work - groups nested in groups 2403 $elNames = array_merge($elNames, $this->_getElNamesRecursive($elInGroup)); 2404 } else { 2405 $elNames[] = $element->getElementName($elInGroup->getName()); 2406 } 2407 } 2408 2409 } else if (is_a($element, 'HTML_QuickForm_header')) { 2410 return array(); 2411 2412 } else if (is_a($element, 'HTML_QuickForm_hidden')) { 2413 return array(); 2414 2415 } else if (method_exists($element, 'getPrivateName') && 2416 !($element instanceof HTML_QuickForm_advcheckbox)) { 2417 // The advcheckbox element implements a method called getPrivateName, 2418 // but in a way that is not compatible with the generic API, so we 2419 // have to explicitly exclude it. 2420 return array($element->getPrivateName()); 2421 2422 } else { 2423 $elNames = array($element->getName()); 2424 } 2425 2426 return $elNames; 2427 } 2428 2429 /** 2430 * Adds a dependency for $elementName which will be disabled if $condition is met. 2431 * If $condition = 'notchecked' (default) then the condition is that the $dependentOn element 2432 * is not checked. If $condition = 'checked' then the condition is that the $dependentOn element 2433 * is checked. If $condition is something else (like "eq" for equals) then it is checked to see if the value 2434 * of the $dependentOn element is $condition (such as equal) to $value. 2435 * 2436 * When working with multiple selects, the dependentOn has to be the real name of the select, meaning that 2437 * it will most likely end up with '[]'. Also, the value should be an array of required values, or a string 2438 * containing the values separated by pipes: array('red', 'blue') or 'red|blue'. 2439 * 2440 * @param string $elementName the name of the element which will be disabled 2441 * @param string $dependentOn the name of the element whose state will be checked for condition 2442 * @param string $condition the condition to check 2443 * @param mixed $value used in conjunction with condition. 2444 */ 2445 function disabledIf($elementName, $dependentOn, $condition = 'notchecked', $value='1') { 2446 // Multiple selects allow for a multiple selection, we transform the array to string here as 2447 // an array cannot be used as a key in an associative array. 2448 if (is_array($value)) { 2449 $value = implode('|', $value); 2450 } 2451 if (!array_key_exists($dependentOn, $this->_dependencies)) { 2452 $this->_dependencies[$dependentOn] = array(); 2453 } 2454 if (!array_key_exists($condition, $this->_dependencies[$dependentOn])) { 2455 $this->_dependencies[$dependentOn][$condition] = array(); 2456 } 2457 if (!array_key_exists($value, $this->_dependencies[$dependentOn][$condition])) { 2458 $this->_dependencies[$dependentOn][$condition][$value] = array(); 2459 } 2460 $this->_dependencies[$dependentOn][$condition][$value][] = $elementName; 2461 } 2462 2463 /** 2464 * Registers button as no submit button 2465 * 2466 * @param string $buttonname name of the button 2467 */ 2468 function registerNoSubmitButton($buttonname){ 2469 $this->_noSubmitButtons[]=$buttonname; 2470 } 2471 2472 /** 2473 * Checks if button is a no submit button, i.e it doesn't submit form 2474 * 2475 * @param string $buttonname name of the button to check 2476 * @return bool 2477 */ 2478 function isNoSubmitButton($buttonname){ 2479 return (array_search($buttonname, $this->_noSubmitButtons)!==FALSE); 2480 } 2481 2482 /** 2483 * Registers a button as cancel button 2484 * 2485 * @param string $addfieldsname name of the button 2486 */ 2487 function _registerCancelButton($addfieldsname){ 2488 $this->_cancelButtons[]=$addfieldsname; 2489 } 2490 2491 /** 2492 * Displays elements without HTML input tags. 2493 * This method is different to freeze() in that it makes sure no hidden 2494 * elements are included in the form. 2495 * Note: If you want to make sure the submitted value is ignored, please use setDefaults(). 2496 * 2497 * This function also removes all previously defined rules. 2498 * 2499 * @param string|array $elementList array or string of element(s) to be frozen 2500 * @return object|bool if element list is not empty then return error object, else true 2501 */ 2502 function hardFreeze($elementList=null) 2503 { 2504 if (!isset($elementList)) { 2505 $this->_freezeAll = true; 2506 $elementList = array(); 2507 } else { 2508 if (!is_array($elementList)) { 2509 $elementList = preg_split('/[ ]*,[ ]*/', $elementList); 2510 } 2511 $elementList = array_flip($elementList); 2512 } 2513 2514 foreach (array_keys($this->_elements) as $key) { 2515 $name = $this->_elements[$key]->getName(); 2516 if ($this->_freezeAll || isset($elementList[$name])) { 2517 $this->_elements[$key]->freeze(); 2518 $this->_elements[$key]->setPersistantFreeze(false); 2519 unset($elementList[$name]); 2520 2521 // remove all rules 2522 $this->_rules[$name] = array(); 2523 // if field is required, remove the rule 2524 $unset = array_search($name, $this->_required); 2525 if ($unset !== false) { 2526 unset($this->_required[$unset]); 2527 } 2528 } 2529 } 2530 2531 if (!empty($elementList)) { 2532 return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Nonexistant element(s): '" . implode("', '", array_keys($elementList)) . "' in HTML_QuickForm::freeze()", 'HTML_QuickForm_Error', true); 2533 } 2534 return true; 2535 } 2536 2537 /** 2538 * Hard freeze all elements in a form except those whose names are in $elementList or hidden elements in a form. 2539 * 2540 * This function also removes all previously defined rules of elements it freezes. 2541 * 2542 * @throws HTML_QuickForm_Error 2543 * @param array $elementList array or string of element(s) not to be frozen 2544 * @return bool returns true 2545 */ 2546 function hardFreezeAllVisibleExcept($elementList) 2547 { 2548 $elementList = array_flip($elementList); 2549 foreach (array_keys($this->_elements) as $key) { 2550 $name = $this->_elements[$key]->getName(); 2551 $type = $this->_elements[$key]->getType(); 2552 2553 if ($type == 'hidden'){ 2554 // leave hidden types as they are 2555 } elseif (!isset($elementList[$name])) { 2556 $this->_elements[$key]->freeze(); 2557 $this->_elements[$key]->setPersistantFreeze(false); 2558 2559 // remove all rules 2560 $this->_rules[$name] = array(); 2561 // if field is required, remove the rule 2562 $unset = array_search($name, $this->_required); 2563 if ($unset !== false) { 2564 unset($this->_required[$unset]); 2565 } 2566 } 2567 } 2568 return true; 2569 } 2570 2571 /** 2572 * Tells whether the form was already submitted 2573 * 2574 * This is useful since the _submitFiles and _submitValues arrays 2575 * may be completely empty after the trackSubmit value is removed. 2576 * 2577 * @return bool 2578 */ 2579 function isSubmitted() 2580 { 2581 return parent::isSubmitted() && (!$this->isFrozen()); 2582 } 2583 } 2584 2585 /** 2586 * MoodleQuickForm renderer 2587 * 2588 * A renderer for MoodleQuickForm that only uses XHTML and CSS and no 2589 * table tags, extends PEAR class HTML_QuickForm_Renderer_Tableless 2590 * 2591 * Stylesheet is part of standard theme and should be automatically included. 2592 * 2593 * @package core_form 2594 * @copyright 2007 Jamie Pratt <me@jamiep.org> 2595 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2596 */ 2597 class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{ 2598 2599 /** @var array Element template array */ 2600 var $_elementTemplates; 2601 2602 /** 2603 * Template used when opening a hidden fieldset 2604 * (i.e. a fieldset that is opened when there is no header element) 2605 * @var string 2606 */ 2607 var $_openHiddenFieldsetTemplate = "\n\t<fieldset class=\"hidden\"><div>"; 2608 2609 /** @var string Header Template string */ 2610 var $_headerTemplate = 2611 "\n\t\t<legend class=\"ftoggler\">{header}</legend>\n\t\t<div class=\"fcontainer clearfix\">\n\t\t"; 2612 2613 /** @var string Template used when opening a fieldset */ 2614 var $_openFieldsetTemplate = "\n\t<fieldset class=\"{classes}\" {id}>"; 2615 2616 /** @var string Template used when closing a fieldset */ 2617 var $_closeFieldsetTemplate = "\n\t\t</div></fieldset>"; 2618 2619 /** @var string Required Note template string */ 2620 var $_requiredNoteTemplate = 2621 "\n\t\t<div class=\"fdescription required\">{requiredNote}</div>"; 2622 2623 /** 2624 * Collapsible buttons string template. 2625 * 2626 * Note that the <span> will be converted as a link. This is done so that the link is not yet clickable 2627 * until the Javascript has been fully loaded. 2628 * 2629 * @var string 2630 */ 2631 var $_collapseButtonsTemplate = 2632 "\n\t<div class=\"collapsible-actions\"><span class=\"collapseexpand\">{strexpandall}</span></div>"; 2633 2634 /** 2635 * Array whose keys are element names. If the key exists this is a advanced element 2636 * 2637 * @var array 2638 */ 2639 var $_advancedElements = array(); 2640 2641 /** 2642 * Array whose keys are element names and the the boolean values reflect the current state. If the key exists this is a collapsible element. 2643 * 2644 * @var array 2645 */ 2646 var $_collapsibleElements = array(); 2647 2648 /** 2649 * @var string Contains the collapsible buttons to add to the form. 2650 */ 2651 var $_collapseButtons = ''; 2652 2653 /** 2654 * Constructor 2655 */ 2656 public function __construct() { 2657 // switch next two lines for ol li containers for form items. 2658 // $this->_elementTemplates=array('default'=>"\n\t\t".'<li class="fitem"><label>{label}{help}<!-- BEGIN required -->{req}<!-- END required --></label><div class="qfelement<!-- BEGIN error --> error<!-- END error --> {type}"><!-- BEGIN error --><span class="error">{error}</span><br /><!-- END error -->{element}</div></li>'); 2659 $this->_elementTemplates = array( 2660 'default'=>"\n\t\t".'<div id="{id}" class="fitem {advanced}<!-- BEGIN required --> required<!-- END required --> fitem_{type} {emptylabel} {class}" {aria-live}><div class="fitemtitle"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg} </label>{help}</div><div class="felement {type}<!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error" tabindex="0">{error}</span><br /><!-- END error -->{element}</div></div>', 2661 2662 'actionbuttons'=>"\n\t\t".'<div id="{id}" class="fitem fitem_actionbuttons fitem_{type} {class}"><div class="felement {type}">{element}</div></div>', 2663 2664 'fieldset'=>"\n\t\t".'<div id="{id}" class="fitem {advanced} {class}<!-- BEGIN required --> required<!-- END required --> fitem_{type} {emptylabel}"><div class="fitemtitle"><div class="fgrouplabel"><label>{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg} </label>{help}</div></div><fieldset class="felement {type}<!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error" tabindex="0">{error}</span><br /><!-- END error -->{element}</fieldset></div>', 2665 2666 'static'=>"\n\t\t".'<div id="{id}" class="fitem {advanced} {emptylabel} {class}"><div class="fitemtitle"><div class="fstaticlabel">{label}<!-- BEGIN required -->{req}<!-- END required -->{advancedimg} {help}</div></div><div class="felement fstatic <!-- BEGIN error --> error<!-- END error -->"><!-- BEGIN error --><span class="error" tabindex="0">{error}</span><br /><!-- END error -->{element}</div></div>', 2667 2668 'warning'=>"\n\t\t".'<div id="{id}" class="fitem {advanced} {emptylabel} {class}">{element}</div>', 2669 2670 'nodisplay'=>''); 2671 2672 parent::__construct(); 2673 } 2674 2675 /** 2676 * Old syntax of class constructor. Deprecated in PHP7. 2677 * 2678 * @deprecated since Moodle 3.1 2679 */ 2680 public function MoodleQuickForm_Renderer() { 2681 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 2682 self::__construct(); 2683 } 2684 2685 /** 2686 * Set element's as adavance element 2687 * 2688 * @param array $elements form elements which needs to be grouped as advance elements. 2689 */ 2690 function setAdvancedElements($elements){ 2691 $this->_advancedElements = $elements; 2692 } 2693 2694 /** 2695 * Setting collapsible elements 2696 * 2697 * @param array $elements 2698 */ 2699 function setCollapsibleElements($elements) { 2700 $this->_collapsibleElements = $elements; 2701 } 2702 2703 /** 2704 * What to do when starting the form 2705 * 2706 * @param MoodleQuickForm $form reference of the form 2707 */ 2708 function startForm(&$form){ 2709 global $PAGE; 2710 $this->_reqHTML = $form->getReqHTML(); 2711 $this->_elementTemplates = str_replace('{req}', $this->_reqHTML, $this->_elementTemplates); 2712 $this->_advancedHTML = $form->getAdvancedHTML(); 2713 $this->_collapseButtons = ''; 2714 $formid = $form->getAttribute('id'); 2715 parent::startForm($form); 2716 // HACK to prevent browsers from automatically inserting the user's password into the wrong fields. 2717 $this->_hiddenHtml .= prevent_form_autofill_password(); 2718 if ($form->isFrozen()){ 2719 $this->_formTemplate = "\n<div class=\"mform frozen\">\n{content}\n</div>"; 2720 } else { 2721 $this->_formTemplate = "\n<form{attributes}>\n\t<div style=\"display: none;\">{hidden}</div>\n{collapsebtns}\n{content}\n</form>"; 2722 $this->_hiddenHtml .= $form->_pageparams; 2723 } 2724 2725 if ($form->is_form_change_checker_enabled()) { 2726 $PAGE->requires->yui_module('moodle-core-formchangechecker', 2727 'M.core_formchangechecker.init', 2728 array(array( 2729 'formid' => $formid 2730 )) 2731 ); 2732 $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle'); 2733 } 2734 if (!empty($this->_collapsibleElements)) { 2735 if (count($this->_collapsibleElements) > 1) { 2736 $this->_collapseButtons = $this->_collapseButtonsTemplate; 2737 $this->_collapseButtons = str_replace('{strexpandall}', get_string('expandall'), $this->_collapseButtons); 2738 $PAGE->requires->strings_for_js(array('collapseall', 'expandall'), 'moodle'); 2739 } 2740 $PAGE->requires->yui_module('moodle-form-shortforms', 'M.form.shortforms', array(array('formid' => $formid))); 2741 } 2742 if (!empty($this->_advancedElements)){ 2743 $PAGE->requires->strings_for_js(array('showmore', 'showless'), 'form'); 2744 $PAGE->requires->yui_module('moodle-form-showadvanced', 'M.form.showadvanced', array(array('formid' => $formid))); 2745 } 2746 } 2747 2748 /** 2749 * Create advance group of elements 2750 * 2751 * @param MoodleQuickForm_group $group Passed by reference 2752 * @param bool $required if input is required field 2753 * @param string $error error message to display 2754 */ 2755 function startGroup(&$group, $required, $error){ 2756 // Make sure the element has an id. 2757 $group->_generateId(); 2758 2759 // Prepend 'fgroup_' to the ID we generated. 2760 $groupid = 'fgroup_' . $group->getAttribute('id'); 2761 2762 // Update the ID. 2763 $group->updateAttributes(array('id' => $groupid)); 2764 2765 if (method_exists($group, 'getElementTemplateType')){ 2766 $html = $this->_elementTemplates[$group->getElementTemplateType()]; 2767 }else{ 2768 $html = $this->_elementTemplates['default']; 2769 2770 } 2771 2772 if (isset($this->_advancedElements[$group->getName()])){ 2773 $html =str_replace(' {advanced}', ' advanced', $html); 2774 $html =str_replace('{advancedimg}', $this->_advancedHTML, $html); 2775 } else { 2776 $html =str_replace(' {advanced}', '', $html); 2777 $html =str_replace('{advancedimg}', '', $html); 2778 } 2779 if (method_exists($group, 'getHelpButton')){ 2780 $html =str_replace('{help}', $group->getHelpButton(), $html); 2781 }else{ 2782 $html =str_replace('{help}', '', $html); 2783 } 2784 $html = str_replace('{id}', $group->getAttribute('id'), $html); 2785 $html =str_replace('{name}', $group->getName(), $html); 2786 $html =str_replace('{type}', 'fgroup', $html); 2787 $html =str_replace('{class}', $group->getAttribute('class'), $html); 2788 $emptylabel = ''; 2789 if ($group->getLabel() == '') { 2790 $emptylabel = 'femptylabel'; 2791 } 2792 $html = str_replace('{emptylabel}', $emptylabel, $html); 2793 2794 $this->_templates[$group->getName()]=$html; 2795 // Fix for bug in tableless quickforms that didn't allow you to stop a 2796 // fieldset before a group of elements. 2797 // if the element name indicates the end of a fieldset, close the fieldset 2798 if ( in_array($group->getName(), $this->_stopFieldsetElements) 2799 && $this->_fieldsetsOpen > 0 2800 ) { 2801 $this->_html .= $this->_closeFieldsetTemplate; 2802 $this->_fieldsetsOpen--; 2803 } 2804 parent::startGroup($group, $required, $error); 2805 } 2806 2807 /** 2808 * Renders element 2809 * 2810 * @param HTML_QuickForm_element $element element 2811 * @param bool $required if input is required field 2812 * @param string $error error message to display 2813 */ 2814 function renderElement(&$element, $required, $error){ 2815 // Make sure the element has an id. 2816 $element->_generateId(); 2817 2818 //adding stuff to place holders in template 2819 //check if this is a group element first 2820 if (($this->_inGroup) and !empty($this->_groupElementTemplate)) { 2821 // so it gets substitutions for *each* element 2822 $html = $this->_groupElementTemplate; 2823 } 2824 elseif (method_exists($element, 'getElementTemplateType')){ 2825 $html = $this->_elementTemplates[$element->getElementTemplateType()]; 2826 }else{ 2827 $html = $this->_elementTemplates['default']; 2828 } 2829 if (isset($this->_advancedElements[$element->getName()])){ 2830 $html = str_replace(' {advanced}', ' advanced', $html); 2831 $html = str_replace(' {aria-live}', ' aria-live="polite"', $html); 2832 } else { 2833 $html = str_replace(' {advanced}', '', $html); 2834 $html = str_replace(' {aria-live}', '', $html); 2835 } 2836 if (isset($this->_advancedElements[$element->getName()])||$element->getName() == 'mform_showadvanced'){ 2837 $html =str_replace('{advancedimg}', $this->_advancedHTML, $html); 2838 } else { 2839 $html =str_replace('{advancedimg}', '', $html); 2840 } 2841 $html =str_replace('{id}', 'fitem_' . $element->getAttribute('id'), $html); 2842 $html =str_replace('{type}', 'f'.$element->getType(), $html); 2843 $html =str_replace('{name}', $element->getName(), $html); 2844 $html =str_replace('{class}', $element->getAttribute('class'), $html); 2845 $emptylabel = ''; 2846 if ($element->getLabel() == '') { 2847 $emptylabel = 'femptylabel'; 2848 } 2849 $html = str_replace('{emptylabel}', $emptylabel, $html); 2850 if (method_exists($element, 'getHelpButton')){ 2851 $html = str_replace('{help}', $element->getHelpButton(), $html); 2852 }else{ 2853 $html = str_replace('{help}', '', $html); 2854 2855 } 2856 if (($this->_inGroup) and !empty($this->_groupElementTemplate)) { 2857 $this->_groupElementTemplate = $html; 2858 } 2859 elseif (!isset($this->_templates[$element->getName()])) { 2860 $this->_templates[$element->getName()] = $html; 2861 } 2862 2863 parent::renderElement($element, $required, $error); 2864 } 2865 2866 /** 2867 * Called when visiting a form, after processing all form elements 2868 * Adds required note, form attributes, validation javascript and form content. 2869 * 2870 * @global moodle_page $PAGE 2871 * @param moodleform $form Passed by reference 2872 */ 2873 function finishForm(&$form){ 2874 global $PAGE; 2875 if ($form->isFrozen()){ 2876 $this->_hiddenHtml = ''; 2877 } 2878 parent::finishForm($form); 2879 $this->_html = str_replace('{collapsebtns}', $this->_collapseButtons, $this->_html); 2880 if (!$form->isFrozen()) { 2881 $args = $form->getLockOptionObject(); 2882 if (count($args[1]) > 0) { 2883 $PAGE->requires->js_init_call('M.form.initFormDependencies', $args, true, moodleform::get_js_module()); 2884 } 2885 } 2886 } 2887 /** 2888 * Called when visiting a header element 2889 * 2890 * @param HTML_QuickForm_header $header An HTML_QuickForm_header element being visited 2891 * @global moodle_page $PAGE 2892 */ 2893 function renderHeader(&$header) { 2894 global $PAGE; 2895 2896 $header->_generateId(); 2897 $name = $header->getName(); 2898 2899 $id = empty($name) ? '' : ' id="' . $header->getAttribute('id') . '"'; 2900 if (is_null($header->_text)) { 2901 $header_html = ''; 2902 } elseif (!empty($name) && isset($this->_templates[$name])) { 2903 $header_html = str_replace('{header}', $header->toHtml(), $this->_templates[$name]); 2904 } else { 2905 $header_html = str_replace('{header}', $header->toHtml(), $this->_headerTemplate); 2906 } 2907 2908 if ($this->_fieldsetsOpen > 0) { 2909 $this->_html .= $this->_closeFieldsetTemplate; 2910 $this->_fieldsetsOpen--; 2911 } 2912 2913 // Define collapsible classes for fieldsets. 2914 $arialive = ''; 2915 $fieldsetclasses = array('clearfix'); 2916 if (isset($this->_collapsibleElements[$header->getName()])) { 2917 $fieldsetclasses[] = 'collapsible'; 2918 if ($this->_collapsibleElements[$header->getName()]) { 2919 $fieldsetclasses[] = 'collapsed'; 2920 } 2921 } 2922 2923 if (isset($this->_advancedElements[$name])){ 2924 $fieldsetclasses[] = 'containsadvancedelements'; 2925 } 2926 2927 $openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate); 2928 $openFieldsetTemplate = str_replace('{classes}', join(' ', $fieldsetclasses), $openFieldsetTemplate); 2929 2930 $this->_html .= $openFieldsetTemplate . $header_html; 2931 $this->_fieldsetsOpen++; 2932 } 2933 2934 /** 2935 * Return Array of element names that indicate the end of a fieldset 2936 * 2937 * @return array 2938 */ 2939 function getStopFieldsetElements(){ 2940 return $this->_stopFieldsetElements; 2941 } 2942 } 2943 2944 /** 2945 * Required elements validation 2946 * 2947 * This class overrides QuickForm validation since it allowed space or empty tag as a value 2948 * 2949 * @package core_form 2950 * @category form 2951 * @copyright 2006 Jamie Pratt <me@jamiep.org> 2952 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2953 */ 2954 class MoodleQuickForm_Rule_Required extends HTML_QuickForm_Rule { 2955 /** 2956 * Checks if an element is not empty. 2957 * This is a server-side validation, it works for both text fields and editor fields 2958 * 2959 * @param string $value Value to check 2960 * @param int|string|array $options Not used yet 2961 * @return bool true if value is not empty 2962 */ 2963 function validate($value, $options = null) { 2964 global $CFG; 2965 if (is_array($value) && array_key_exists('text', $value)) { 2966 $value = $value['text']; 2967 } 2968 if (is_array($value)) { 2969 // nasty guess - there has to be something in the array, hopefully nobody invents arrays in arrays 2970 $value = implode('', $value); 2971 } 2972 $stripvalues = array( 2973 '#</?(?!img|canvas|hr).*?>#im', // all tags except img, canvas and hr 2974 '#(\xc2\xa0|\s| )#', // Any whitespaces actually. 2975 ); 2976 if (!empty($CFG->strictformsrequired)) { 2977 $value = preg_replace($stripvalues, '', (string)$value); 2978 } 2979 if ((string)$value == '') { 2980 return false; 2981 } 2982 return true; 2983 } 2984 2985 /** 2986 * This function returns Javascript code used to build client-side validation. 2987 * It checks if an element is not empty. 2988 * 2989 * @param int $format format of data which needs to be validated. 2990 * @return array 2991 */ 2992 function getValidationScript($format = null) { 2993 global $CFG; 2994 if (!empty($CFG->strictformsrequired)) { 2995 if (!empty($format) && $format == FORMAT_HTML) { 2996 return array('', "{jsVar}.replace(/(<(?!img|hr|canvas)[^>]*>)| |\s+/ig, '') == ''"); 2997 } else { 2998 return array('', "{jsVar}.replace(/^\s+$/g, '') == ''"); 2999 } 3000 } else { 3001 return array('', "{jsVar} == ''"); 3002 } 3003 } 3004 } 3005 3006 /** 3007 * @global object $GLOBALS['_HTML_QuickForm_default_renderer'] 3008 * @name $_HTML_QuickForm_default_renderer 3009 */ 3010 $GLOBALS['_HTML_QuickForm_default_renderer'] = new MoodleQuickForm_Renderer(); 3011 3012 /** Please keep this list in alphabetical order. */ 3013 MoodleQuickForm::registerElementType('advcheckbox', "$CFG->libdir/form/advcheckbox.php", 'MoodleQuickForm_advcheckbox'); 3014 MoodleQuickForm::registerElementType('autocomplete', "$CFG->libdir/form/autocomplete.php", 'MoodleQuickForm_autocomplete'); 3015 MoodleQuickForm::registerElementType('button', "$CFG->libdir/form/button.php", 'MoodleQuickForm_button'); 3016 MoodleQuickForm::registerElementType('cancel', "$CFG->libdir/form/cancel.php", 'MoodleQuickForm_cancel'); 3017 MoodleQuickForm::registerElementType('course', "$CFG->libdir/form/course.php", 'MoodleQuickForm_course'); 3018 MoodleQuickForm::registerElementType('searchableselector', "$CFG->libdir/form/searchableselector.php", 'MoodleQuickForm_searchableselector'); 3019 MoodleQuickForm::registerElementType('checkbox', "$CFG->libdir/form/checkbox.php", 'MoodleQuickForm_checkbox'); 3020 MoodleQuickForm::registerElementType('date_selector', "$CFG->libdir/form/dateselector.php", 'MoodleQuickForm_date_selector'); 3021 MoodleQuickForm::registerElementType('date_time_selector', "$CFG->libdir/form/datetimeselector.php", 'MoodleQuickForm_date_time_selector'); 3022 MoodleQuickForm::registerElementType('duration', "$CFG->libdir/form/duration.php", 'MoodleQuickForm_duration'); 3023 MoodleQuickForm::registerElementType('editor', "$CFG->libdir/form/editor.php", 'MoodleQuickForm_editor'); 3024 MoodleQuickForm::registerElementType('filemanager', "$CFG->libdir/form/filemanager.php", 'MoodleQuickForm_filemanager'); 3025 MoodleQuickForm::registerElementType('filepicker', "$CFG->libdir/form/filepicker.php", 'MoodleQuickForm_filepicker'); 3026 MoodleQuickForm::registerElementType('grading', "$CFG->libdir/form/grading.php", 'MoodleQuickForm_grading'); 3027 MoodleQuickForm::registerElementType('group', "$CFG->libdir/form/group.php", 'MoodleQuickForm_group'); 3028 MoodleQuickForm::registerElementType('header', "$CFG->libdir/form/header.php", 'MoodleQuickForm_header'); 3029 MoodleQuickForm::registerElementType('hidden', "$CFG->libdir/form/hidden.php", 'MoodleQuickForm_hidden'); 3030 MoodleQuickForm::registerElementType('htmleditor', "$CFG->libdir/form/htmleditor.php", 'MoodleQuickForm_htmleditor'); 3031 MoodleQuickForm::registerElementType('listing', "$CFG->libdir/form/listing.php", 'MoodleQuickForm_listing'); 3032 MoodleQuickForm::registerElementType('modgrade', "$CFG->libdir/form/modgrade.php", 'MoodleQuickForm_modgrade'); 3033 MoodleQuickForm::registerElementType('modvisible', "$CFG->libdir/form/modvisible.php", 'MoodleQuickForm_modvisible'); 3034 MoodleQuickForm::registerElementType('password', "$CFG->libdir/form/password.php", 'MoodleQuickForm_password'); 3035 MoodleQuickForm::registerElementType('passwordunmask', "$CFG->libdir/form/passwordunmask.php", 'MoodleQuickForm_passwordunmask'); 3036 MoodleQuickForm::registerElementType('questioncategory', "$CFG->libdir/form/questioncategory.php", 'MoodleQuickForm_questioncategory'); 3037 MoodleQuickForm::registerElementType('radio', "$CFG->libdir/form/radio.php", 'MoodleQuickForm_radio'); 3038 MoodleQuickForm::registerElementType('recaptcha', "$CFG->libdir/form/recaptcha.php", 'MoodleQuickForm_recaptcha'); 3039 MoodleQuickForm::registerElementType('select', "$CFG->libdir/form/select.php", 'MoodleQuickForm_select'); 3040 MoodleQuickForm::registerElementType('selectgroups', "$CFG->libdir/form/selectgroups.php", 'MoodleQuickForm_selectgroups'); 3041 MoodleQuickForm::registerElementType('selectwithlink', "$CFG->libdir/form/selectwithlink.php", 'MoodleQuickForm_selectwithlink'); 3042 MoodleQuickForm::registerElementType('selectyesno', "$CFG->libdir/form/selectyesno.php", 'MoodleQuickForm_selectyesno'); 3043 MoodleQuickForm::registerElementType('static', "$CFG->libdir/form/static.php", 'MoodleQuickForm_static'); 3044 MoodleQuickForm::registerElementType('submit', "$CFG->libdir/form/submit.php", 'MoodleQuickForm_submit'); 3045 MoodleQuickForm::registerElementType('submitlink', "$CFG->libdir/form/submitlink.php", 'MoodleQuickForm_submitlink'); 3046 MoodleQuickForm::registerElementType('tags', "$CFG->libdir/form/tags.php", 'MoodleQuickForm_tags'); 3047 MoodleQuickForm::registerElementType('text', "$CFG->libdir/form/text.php", 'MoodleQuickForm_text'); 3048 MoodleQuickForm::registerElementType('textarea', "$CFG->libdir/form/textarea.php", 'MoodleQuickForm_textarea'); 3049 MoodleQuickForm::registerElementType('url', "$CFG->libdir/form/url.php", 'MoodleQuickForm_url'); 3050 MoodleQuickForm::registerElementType('warning', "$CFG->libdir/form/warning.php", 'MoodleQuickForm_warning'); 3051 3052 MoodleQuickForm::registerRule('required', null, 'MoodleQuickForm_Rule_Required', "$CFG->libdir/formslib.php");
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |