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