See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4: */ 3 // +----------------------------------------------------------------------+ 4 // | PHP version 4.0 | 5 // +----------------------------------------------------------------------+ 6 // | Copyright (c) 1997-2003 The PHP Group | 7 // +----------------------------------------------------------------------+ 8 // | This source file is subject to version 2.0 of the PHP license, | 9 // | that is bundled with this package in the file LICENSE, and is | 10 // | available at through the world-wide-web at | 11 // | http://www.php.net/license/2_02.txt. | 12 // | If you did not receive a copy of the PHP license and are unable to | 13 // | obtain it through the world-wide-web, please send a note to | 14 // | license@php.net so we can mail you a copy immediately. | 15 // +----------------------------------------------------------------------+ 16 // | Authors: Adam Daniel <adaniel1@eesus.jnj.com> | 17 // | Bertrand Mansion <bmansion@mamasam.com> | 18 // +----------------------------------------------------------------------+ 19 // 20 // $Id$ 21 22 require_once('PEAR.php'); 23 require_once('HTML/Common.php'); 24 /** 25 * Static utility methods. 26 */ 27 require_once('HTML/QuickForm/utils.php'); 28 29 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = 30 array( 31 'group' =>array('HTML/QuickForm/group.php','HTML_QuickForm_group'), 32 'hidden' =>array('HTML/QuickForm/hidden.php','HTML_QuickForm_hidden'), 33 'reset' =>array('HTML/QuickForm/reset.php','HTML_QuickForm_reset'), 34 'checkbox' =>array('HTML/QuickForm/checkbox.php','HTML_QuickForm_checkbox'), 35 'file' =>array('HTML/QuickForm/file.php','HTML_QuickForm_file'), 36 'image' =>array('HTML/QuickForm/image.php','HTML_QuickForm_image'), 37 'password' =>array('HTML/QuickForm/password.php','HTML_QuickForm_password'), 38 'radio' =>array('HTML/QuickForm/radio.php','HTML_QuickForm_radio'), 39 'button' =>array('HTML/QuickForm/button.php','HTML_QuickForm_button'), 40 'submit' =>array('HTML/QuickForm/submit.php','HTML_QuickForm_submit'), 41 'select' =>array('HTML/QuickForm/select.php','HTML_QuickForm_select'), 42 'hiddenselect' =>array('HTML/QuickForm/hiddenselect.php','HTML_QuickForm_hiddenselect'), 43 'text' =>array('HTML/QuickForm/text.php','HTML_QuickForm_text'), 44 'textarea' =>array('HTML/QuickForm/textarea.php','HTML_QuickForm_textarea'), 45 'link' =>array('HTML/QuickForm/link.php','HTML_QuickForm_link'), 46 'advcheckbox' =>array('HTML/QuickForm/advcheckbox.php','HTML_QuickForm_advcheckbox'), 47 'date' =>array('HTML/QuickForm/date.php','HTML_QuickForm_date'), 48 'static' =>array('HTML/QuickForm/static.php','HTML_QuickForm_static'), 49 'header' =>array('HTML/QuickForm/header.php', 'HTML_QuickForm_header'), 50 'html' =>array('HTML/QuickForm/html.php', 'HTML_QuickForm_html'), 51 'hierselect' =>array('HTML/QuickForm/hierselect.php', 'HTML_QuickForm_hierselect'), 52 'autocomplete' =>array('HTML/QuickForm/autocomplete.php', 'HTML_QuickForm_autocomplete'), 53 'xbutton' =>array('HTML/QuickForm/xbutton.php','HTML_QuickForm_xbutton') 54 ); 55 56 $GLOBALS['_HTML_QuickForm_registered_rules'] = array( 57 'required' => array('html_quickform_rule_required', 'HTML/QuickForm/Rule/Required.php'), 58 'maxlength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'), 59 'minlength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'), 60 'rangelength' => array('html_quickform_rule_range', 'HTML/QuickForm/Rule/Range.php'), 61 'email' => array('html_quickform_rule_email', 'HTML/QuickForm/Rule/Email.php'), 62 'regex' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), 63 'lettersonly' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), 64 'alphanumeric' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), 65 'numeric' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), 66 'nopunctuation' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), 67 'nonzero' => array('html_quickform_rule_regex', 'HTML/QuickForm/Rule/Regex.php'), 68 'callback' => array('html_quickform_rule_callback', 'HTML/QuickForm/Rule/Callback.php'), 69 'compare' => array('html_quickform_rule_compare', 'HTML/QuickForm/Rule/Compare.php') 70 ); 71 72 // {{{ error codes 73 74 /* 75 * Error codes for the QuickForm interface, which will be mapped to textual messages 76 * in the QuickForm::errorMessage() function. If you are to add a new error code, be 77 * sure to add the textual messages to the QuickForm::errorMessage() function as well 78 */ 79 80 define('QUICKFORM_OK', 1); 81 define('QUICKFORM_ERROR', -1); 82 define('QUICKFORM_INVALID_RULE', -2); 83 define('QUICKFORM_NONEXIST_ELEMENT', -3); 84 define('QUICKFORM_INVALID_FILTER', -4); 85 define('QUICKFORM_UNREGISTERED_ELEMENT', -5); 86 define('QUICKFORM_INVALID_ELEMENT_NAME', -6); 87 define('QUICKFORM_INVALID_PROCESS', -7); 88 define('QUICKFORM_DEPRECATED', -8); 89 define('QUICKFORM_INVALID_DATASOURCE', -9); 90 91 // }}} 92 93 /** 94 * Create, validate and process HTML forms 95 * 96 * @author Adam Daniel <adaniel1@eesus.jnj.com> 97 * @author Bertrand Mansion <bmansion@mamasam.com> 98 * @version 2.0 99 * @since PHP 4.0.3pl1 100 */ 101 class HTML_QuickForm extends HTML_Common { 102 // {{{ properties 103 104 /** 105 * Array containing the form fields 106 * @since 1.0 107 * @var array 108 * @access private 109 */ 110 var $_elements = array(); 111 112 /** 113 * Array containing element name to index map 114 * @since 1.1 115 * @var array 116 * @access private 117 */ 118 var $_elementIndex = array(); 119 120 /** 121 * Array containing indexes of duplicate elements 122 * @since 2.10 123 * @var array 124 * @access private 125 */ 126 var $_duplicateIndex = array(); 127 128 /** 129 * Array containing required field IDs 130 * @since 1.0 131 * @var array 132 * @access private 133 */ 134 var $_required = array(); 135 136 /** 137 * Prefix message in javascript alert if error 138 * @since 1.0 139 * @var string 140 * @access public 141 */ 142 var $_jsPrefix = 'Invalid information entered.'; 143 144 /** 145 * Postfix message in javascript alert if error 146 * @since 1.0 147 * @var string 148 * @access public 149 */ 150 var $_jsPostfix = 'Please correct these fields.'; 151 152 /** 153 * Datasource object implementing the informal 154 * datasource protocol 155 * @since 3.3 156 * @var object 157 * @access private 158 */ 159 var $_datasource; 160 161 /** 162 * Array of default form values 163 * @since 2.0 164 * @var array 165 * @access private 166 */ 167 var $_defaultValues = array(); 168 169 /** 170 * Array of constant form values 171 * @since 2.0 172 * @var array 173 * @access private 174 */ 175 var $_constantValues = array(); 176 177 /** 178 * Array of submitted form values 179 * @since 1.0 180 * @var array 181 * @access private 182 */ 183 var $_submitValues = array(); 184 185 /** 186 * Array of submitted form files 187 * @since 1.0 188 * @var integer 189 * @access public 190 */ 191 var $_submitFiles = array(); 192 193 /** 194 * Value for maxfilesize hidden element if form contains file input 195 * @since 1.0 196 * @var integer 197 * @access public 198 */ 199 var $_maxFileSize = 1048576; // 1 Mb = 1048576 200 201 /** 202 * Flag to know if all fields are frozen 203 * @since 1.0 204 * @var boolean 205 * @access private 206 */ 207 var $_freezeAll = false; 208 209 /** 210 * Array containing the form rules 211 * @since 1.0 212 * @var array 213 * @access private 214 */ 215 var $_rules = array(); 216 217 /** 218 * Form rules, global variety 219 * @var array 220 * @access private 221 */ 222 var $_formRules = array(); 223 224 /** 225 * Array containing the validation errors 226 * @since 1.0 227 * @var array 228 * @access private 229 */ 230 var $_errors = array(); 231 232 /** 233 * Note for required fields in the form 234 * @var string 235 * @since 1.0 236 * @access private 237 */ 238 var $_requiredNote = '<span style="font-size:80%; color:#ff0000;">*</span><span style="font-size:80%;"> denotes required field</span>'; 239 240 /** 241 * Whether the form was submitted 242 * @var boolean 243 * @access private 244 */ 245 var $_flagSubmitted = false; 246 247 // }}} 248 // {{{ constructor 249 250 /** 251 * Class constructor 252 * @param string $formName Form's name. 253 * @param string $method (optional)Form's method defaults to 'POST' 254 * @param string $action (optional)Form's action 255 * @param string $target (optional)Form's target defaults to '_self' 256 * @param mixed $attributes (optional)Extra attributes for <form> tag 257 * @param bool $trackSubmit (optional)Whether to track if the form was submitted by adding a special hidden field 258 * @access public 259 */ 260 public function __construct($formName='', $method='post', $action='', $target='', $attributes=null, $trackSubmit = false) 261 { 262 parent::__construct($attributes); 263 $method = (strtoupper($method) == 'GET') ? 'get' : 'post'; 264 $action = ($action == '') ? $_SERVER['PHP_SELF'] : $action; 265 $target = empty($target) ? array() : array('target' => $target); 266 $attributes = array('action'=>$action, 'method'=>$method, 'name'=>$formName, 'id'=>$formName) + $target; 267 $this->updateAttributes($attributes); 268 if (!$trackSubmit || isset($_REQUEST['_qf__' . $formName])) { 269 $this->_submitValues = 'get' == $method? $_GET: $_POST; 270 $this->_submitFiles = $_FILES; 271 $this->_flagSubmitted = count($this->_submitValues) > 0 || count($this->_submitFiles) > 0; 272 } 273 if ($trackSubmit) { 274 unset($this->_submitValues['_qf__' . $formName]); 275 $this->addElement('hidden', '_qf__' . $formName, null); 276 } 277 if (preg_match('/^([0-9]+)([a-zA-Z]*)$/', ini_get('upload_max_filesize'), $matches)) { 278 // see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes 279 switch (strtoupper($matches['2'])) { 280 case 'G': 281 $this->_maxFileSize = $matches['1'] * 1073741824; 282 break; 283 case 'M': 284 $this->_maxFileSize = $matches['1'] * 1048576; 285 break; 286 case 'K': 287 $this->_maxFileSize = $matches['1'] * 1024; 288 break; 289 default: 290 $this->_maxFileSize = $matches['1']; 291 } 292 } 293 } // end constructor 294 295 /** 296 * Old syntax of class constructor. Deprecated in PHP7. 297 * 298 * @deprecated since Moodle 3.1 299 */ 300 public function HTML_QuickForm($formName='', $method='post', $action='', $target='', $attributes=null, $trackSubmit = false) { 301 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 302 self::__construct($formName, $method, $action, $target, $attributes, $trackSubmit); 303 } 304 305 // }}} 306 // {{{ apiVersion() 307 308 /** 309 * Returns the current API version 310 * 311 * @since 1.0 312 * @access public 313 * @return float 314 */ 315 function apiVersion() 316 { 317 return 3.2; 318 } // end func apiVersion 319 320 // }}} 321 // {{{ registerElementType() 322 323 /** 324 * Registers a new element type 325 * 326 * @param string $typeName Name of element type 327 * @param string $include Include path for element type 328 * @param string $className Element class name 329 * @since 1.0 330 * @access public 331 * @return void 332 */ 333 static function registerElementType($typeName, $include, $className) 334 { 335 $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][strtolower($typeName)] = array($include, $className); 336 } // end func registerElementType 337 338 // }}} 339 // {{{ registerRule() 340 341 /** 342 * Registers a new validation rule 343 * 344 * @param string $ruleName Name of validation rule 345 * @param string $type Either: 'regex', 'function' or 'rule' for an HTML_QuickForm_Rule object 346 * @param string $data1 Name of function, regular expression or HTML_QuickForm_Rule classname 347 * @param string $data2 Object parent of above function or HTML_QuickForm_Rule file path 348 * @since 1.0 349 * @access public 350 * @return void 351 */ 352 static function registerRule($ruleName, $type, $data1, $data2 = null) 353 { 354 include_once('HTML/QuickForm/RuleRegistry.php'); 355 $registry =& HTML_QuickForm_RuleRegistry::singleton(); 356 $registry->registerRule($ruleName, $type, $data1, $data2); 357 } // end func registerRule 358 359 // }}} 360 // {{{ elementExists() 361 362 /** 363 * Returns true if element is in the form 364 * 365 * @param string $element form name of element to check 366 * @since 1.0 367 * @access public 368 * @return boolean 369 */ 370 function elementExists($element=null) 371 { 372 return isset($this->_elementIndex[$element]); 373 } // end func elementExists 374 375 // }}} 376 // {{{ setDatasource() 377 378 /** 379 * Sets a datasource object for this form object 380 * 381 * Datasource default and constant values will feed the QuickForm object if 382 * the datasource implements defaultValues() and constantValues() methods. 383 * 384 * @param object $datasource datasource object implementing the informal datasource protocol 385 * @param mixed $defaultsFilter string or array of filter(s) to apply to default values 386 * @param mixed $constantsFilter string or array of filter(s) to apply to constants values 387 * @since 3.3 388 * @access public 389 * @return void 390 */ 391 function setDatasource(&$datasource, $defaultsFilter = null, $constantsFilter = null) 392 { 393 if (is_object($datasource)) { 394 $this->_datasource =& $datasource; 395 if (is_callable(array($datasource, 'defaultValues'))) { 396 $this->setDefaults($datasource->defaultValues($this), $defaultsFilter); 397 } 398 if (is_callable(array($datasource, 'constantValues'))) { 399 $this->setConstants($datasource->constantValues($this), $constantsFilter); 400 } 401 } else { 402 return self::raiseError(null, QUICKFORM_INVALID_DATASOURCE, null, E_USER_WARNING, "Datasource is not an object in QuickForm::setDatasource()", 'HTML_QuickForm_Error', true); 403 } 404 } // end func setDatasource 405 406 // }}} 407 // {{{ setDefaults() 408 409 /** 410 * Initializes default form values 411 * 412 * @param array $defaultValues values used to fill the form 413 * @param mixed $filter (optional) filter(s) to apply to all default values 414 * @since 1.0 415 * @access public 416 * @return void 417 */ 418 function setDefaults($defaultValues = null, $filter = null) 419 { 420 if (is_array($defaultValues)) { 421 if (isset($filter)) { 422 if (is_array($filter) && (2 != count($filter) || !is_callable($filter))) { 423 foreach ($filter as $val) { 424 if (!is_callable($val)) { 425 return self::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setDefaults()", 'HTML_QuickForm_Error', true); 426 } else { 427 $defaultValues = $this->_recursiveFilter($val, $defaultValues); 428 } 429 } 430 } elseif (!is_callable($filter)) { 431 return self::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setDefaults()", 'HTML_QuickForm_Error', true); 432 } else { 433 $defaultValues = $this->_recursiveFilter($filter, $defaultValues); 434 } 435 } 436 $this->_defaultValues = HTML_QuickForm::arrayMerge($this->_defaultValues, $defaultValues); 437 foreach (array_keys($this->_elements) as $key) { 438 $this->_elements[$key]->onQuickFormEvent('updateValue', null, $this); 439 } 440 } 441 } // end func setDefaults 442 443 // }}} 444 // {{{ setConstants() 445 446 /** 447 * Initializes constant form values. 448 * These values won't get overridden by POST or GET vars 449 * 450 * @param array $constantValues values used to fill the form 451 * @param mixed $filter (optional) filter(s) to apply to all default values 452 * 453 * @since 2.0 454 * @access public 455 * @return void 456 */ 457 function setConstants($constantValues = null, $filter = null) 458 { 459 if (is_array($constantValues)) { 460 if (isset($filter)) { 461 if (is_array($filter) && (2 != count($filter) || !is_callable($filter))) { 462 foreach ($filter as $val) { 463 if (!is_callable($val)) { 464 return self::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setConstants()", 'HTML_QuickForm_Error', true); 465 } else { 466 $constantValues = $this->_recursiveFilter($val, $constantValues); 467 } 468 } 469 } elseif (!is_callable($filter)) { 470 return self::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setConstants()", 'HTML_QuickForm_Error', true); 471 } else { 472 $constantValues = $this->_recursiveFilter($filter, $constantValues); 473 } 474 } 475 $this->_constantValues = HTML_QuickForm::arrayMerge($this->_constantValues, $constantValues); 476 foreach (array_keys($this->_elements) as $key) { 477 $this->_elements[$key]->onQuickFormEvent('updateValue', null, $this); 478 } 479 } 480 } // end func setConstants 481 482 // }}} 483 // {{{ setMaxFileSize() 484 485 /** 486 * Sets the value of MAX_FILE_SIZE hidden element 487 * 488 * @param int $bytes Size in bytes 489 * @since 3.0 490 * @access public 491 * @return void 492 */ 493 function setMaxFileSize($bytes = 0) 494 { 495 if ($bytes > 0) { 496 $this->_maxFileSize = $bytes; 497 } 498 if (!$this->elementExists('MAX_FILE_SIZE')) { 499 $this->addElement('hidden', 'MAX_FILE_SIZE', $this->_maxFileSize); 500 } else { 501 $el =& $this->getElement('MAX_FILE_SIZE'); 502 $el->updateAttributes(array('value' => $this->_maxFileSize)); 503 } 504 } // end func setMaxFileSize 505 506 // }}} 507 // {{{ getMaxFileSize() 508 509 /** 510 * Returns the value of MAX_FILE_SIZE hidden element 511 * 512 * @since 3.0 513 * @access public 514 * @return int max file size in bytes 515 */ 516 function getMaxFileSize() 517 { 518 return $this->_maxFileSize; 519 } // end func getMaxFileSize 520 521 // }}} 522 // {{{ &createElement() 523 524 /** 525 * Creates a new form element of the given type. 526 * 527 * This method accepts variable number of parameters, their 528 * meaning and count depending on $elementType 529 * 530 * @param string $elementType type of element to add (text, textarea, file...) 531 * @since 1.0 532 * @access public 533 * @return object extended class of HTML_element 534 * @throws HTML_QuickForm_Error 535 */ 536 function &createElement($elementType) 537 { 538 if (!isset($this) || !($this instanceof HTML_QuickForm)) { 539 // Several form elements in Moodle core before 3.2 were calling this method 540 // statically suppressing PHP notices. This debugging message should notify 541 // developers who copied such code and did not test their plugins on PHP 7.1. 542 // Example of fixing group form elements can be found in commit 543 // https://github.com/moodle/moodle/commit/721e2def56a48fab4f8d3ec7847af5cd03f5ec79 544 debugging('Function createElement() can not be called statically, ' . 545 'this will no longer work in PHP 7.1', 546 DEBUG_DEVELOPER); 547 } 548 $args = func_get_args(); 549 $element = self::_loadElement('createElement', $elementType, array_slice($args, 1)); 550 return $element; 551 } // end func createElement 552 553 // }}} 554 // {{{ _loadElement() 555 556 /** 557 * Returns a form element of the given type 558 * 559 * @param string $event event to send to newly created element ('createElement' or 'addElement') 560 * @param string $type element type 561 * @param array $args arguments for event 562 * @since 2.0 563 * @access private 564 * @return object a new element 565 * @throws HTML_QuickForm_Error 566 */ 567 function &_loadElement($event, $type, $args) 568 { 569 $type = strtolower($type); 570 if (!self::isTypeRegistered($type)) { 571 $error = self::raiseError(null, QUICKFORM_UNREGISTERED_ELEMENT, null, E_USER_WARNING, "Element '$type' does not exist in HTML_QuickForm::_loadElement()", 'HTML_QuickForm_Error', true); 572 return $error; 573 } 574 $className = $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][$type][1]; 575 $includeFile = $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][$type][0]; 576 include_once($includeFile); 577 $elementObject = new $className(); //Moodle: PHP 5.3 compatibility 578 for ($i = 0; $i < 5; $i++) { 579 if (!isset($args[$i])) { 580 $args[$i] = null; 581 } 582 } 583 $err = $elementObject->onQuickFormEvent($event, $args, $this); 584 if ($err !== true) { 585 return $err; 586 } 587 return $elementObject; 588 } // end func _loadElement 589 590 // }}} 591 // {{{ addElement() 592 593 /** 594 * Adds an element into the form 595 * 596 * If $element is a string representing element type, then this 597 * method accepts variable number of parameters, their meaning 598 * and count depending on $element 599 * 600 * @param mixed $element element object or type of element to add (text, textarea, file...) 601 * @since 1.0 602 * @return object reference to element 603 * @access public 604 * @throws HTML_QuickForm_Error 605 */ 606 function &addElement($element) 607 { 608 if (is_object($element) && is_subclass_of($element, 'html_quickform_element')) { 609 $elementObject = &$element; 610 $elementObject->onQuickFormEvent('updateValue', null, $this); 611 } else { 612 $args = func_get_args(); 613 $elementObject =& $this->_loadElement('addElement', $element, array_slice($args, 1)); 614 $pear = new PEAR(); 615 if ($pear->isError($elementObject)) { 616 return $elementObject; 617 } 618 } 619 $elementName = $elementObject->getName(); 620 621 // Add the element if it is not an incompatible duplicate 622 if (!empty($elementName) && isset($this->_elementIndex[$elementName])) { 623 if ($this->_elements[$this->_elementIndex[$elementName]]->getType() == 624 $elementObject->getType()) { 625 $this->_elements[] =& $elementObject; 626 $elKeys = array_keys($this->_elements); 627 $this->_duplicateIndex[$elementName][] = end($elKeys); 628 } else { 629 $error = self::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, "Element '$elementName' already exists in HTML_QuickForm::addElement()", 'HTML_QuickForm_Error', true); 630 return $error; 631 } 632 } else { 633 $this->_elements[] =& $elementObject; 634 $elKeys = array_keys($this->_elements); 635 $this->_elementIndex[$elementName] = end($elKeys); 636 } 637 if ($this->_freezeAll) { 638 $elementObject->freeze(); 639 } 640 641 return $elementObject; 642 } // end func addElement 643 644 // }}} 645 // {{{ insertElementBefore() 646 647 /** 648 * Inserts a new element right before the other element 649 * 650 * Warning: it is not possible to check whether the $element is already 651 * added to the form, therefore if you want to move the existing form 652 * element to a new position, you'll have to use removeElement(): 653 * $form->insertElementBefore($form->removeElement('foo', false), 'bar'); 654 * 655 * @access public 656 * @since 3.2.4 657 * @param object HTML_QuickForm_element Element to insert 658 * @param string Name of the element before which the new one is inserted 659 * @return object HTML_QuickForm_element reference to inserted element 660 * @throws HTML_QuickForm_Error 661 */ 662 function &insertElementBefore(&$element, $nameAfter) 663 { 664 if (!empty($this->_duplicateIndex[$nameAfter])) { 665 $error = self::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, 'Several elements named "' . $nameAfter . '" exist in HTML_QuickForm::insertElementBefore().', 'HTML_QuickForm_Error', true); 666 return $error; 667 } elseif (!$this->elementExists($nameAfter)) { 668 $error = self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$nameAfter' does not exist in HTML_QuickForm::insertElementBefore()", 'HTML_QuickForm_Error', true); 669 return $error; 670 } 671 $elementName = $element->getName(); 672 $targetIdx = $this->_elementIndex[$nameAfter]; 673 $duplicate = false; 674 // Like in addElement(), check that it's not an incompatible duplicate 675 if (!empty($elementName) && isset($this->_elementIndex[$elementName])) { 676 if ($this->_elements[$this->_elementIndex[$elementName]]->getType() != $element->getType()) { 677 $error = self::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, "Element '$elementName' already exists in HTML_QuickForm::insertElementBefore()", 'HTML_QuickForm_Error', true); 678 return $error; 679 } 680 $duplicate = true; 681 } 682 // Move all the elements after added back one place, reindex _elementIndex and/or _duplicateIndex 683 $elKeys = array_keys($this->_elements); 684 for ($i = end($elKeys); $i >= $targetIdx; $i--) { 685 if (isset($this->_elements[$i])) { 686 $currentName = $this->_elements[$i]->getName(); 687 $this->_elements[$i + 1] =& $this->_elements[$i]; 688 if ($this->_elementIndex[$currentName] == $i) { 689 $this->_elementIndex[$currentName] = $i + 1; 690 } else { 691 if (!empty($currentName)) { 692 $dupIdx = array_search($i, $this->_duplicateIndex[$currentName]); 693 $this->_duplicateIndex[$currentName][$dupIdx] = $i + 1; 694 } 695 } 696 unset($this->_elements[$i]); 697 } 698 } 699 // Put the element in place finally 700 $this->_elements[$targetIdx] =& $element; 701 if (!$duplicate) { 702 $this->_elementIndex[$elementName] = $targetIdx; 703 } else { 704 $this->_duplicateIndex[$elementName][] = $targetIdx; 705 } 706 $element->onQuickFormEvent('updateValue', null, $this); 707 if ($this->_freezeAll) { 708 $element->freeze(); 709 } 710 // If not done, the elements will appear in reverse order 711 ksort($this->_elements); 712 return $element; 713 } 714 715 // }}} 716 // {{{ addGroup() 717 718 /** 719 * Adds an element group 720 * @param array $elements array of elements composing the group 721 * @param string $name (optional)group name 722 * @param string $groupLabel (optional)group label 723 * @param string $separator (optional)string to separate elements 724 * @param string $appendName (optional)specify whether the group name should be 725 * used in the form element name ex: group[element] 726 * @return object reference to added group of elements 727 * @since 2.8 728 * @access public 729 * @throws PEAR_Error 730 */ 731 function &addGroup($elements, $name=null, $groupLabel='', $separator=null, $appendName = true) 732 { 733 static $anonGroups = 1; 734 735 if (0 == strlen($name)) { 736 $name = 'qf_group_' . $anonGroups++; 737 $appendName = false; 738 } 739 $group =& $this->addElement('group', $name, $groupLabel, $elements, $separator, $appendName); 740 return $group; 741 } // end func addGroup 742 743 // }}} 744 // {{{ &getElement() 745 746 /** 747 * Returns a reference to the element 748 * 749 * @param string $element Element name 750 * @since 2.0 751 * @access public 752 * @return object reference to element 753 * @throws HTML_QuickForm_Error 754 */ 755 function &getElement($element) 756 { 757 if (isset($this->_elementIndex[$element])) { 758 return $this->_elements[$this->_elementIndex[$element]]; 759 } else { 760 $error = self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElement()", 'HTML_QuickForm_Error', true); 761 return $error; 762 } 763 } // end func getElement 764 765 // }}} 766 // {{{ &getElementValue() 767 768 /** 769 * Returns the element's raw value 770 * 771 * This returns the value as submitted by the form (not filtered) 772 * or set via setDefaults() or setConstants() 773 * 774 * @param string $element Element name 775 * @since 2.0 776 * @access public 777 * @return mixed element value 778 * @throws HTML_QuickForm_Error 779 */ 780 function &getElementValue($element) 781 { 782 if (!isset($this->_elementIndex[$element])) { 783 $error = self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true); 784 return $error; 785 } 786 $value = $this->_elements[$this->_elementIndex[$element]]->getValue(); 787 if (isset($this->_duplicateIndex[$element])) { 788 foreach ($this->_duplicateIndex[$element] as $index) { 789 if (null !== ($v = $this->_elements[$index]->getValue())) { 790 if (is_array($value)) { 791 $value[] = $v; 792 } else { 793 $value = (null === $value)? $v: array($value, $v); 794 } 795 } 796 } 797 } 798 return $value; 799 } // end func getElementValue 800 801 // }}} 802 // {{{ getSubmitValue() 803 804 /** 805 * Returns the elements value after submit and filter 806 * 807 * @param string Element name 808 * @since 2.0 809 * @access public 810 * @return mixed submitted element value or null if not set 811 */ 812 function getSubmitValue($elementName) 813 { 814 $value = null; 815 if (isset($this->_submitValues[$elementName]) || isset($this->_submitFiles[$elementName])) { 816 $value = isset($this->_submitValues[$elementName])? $this->_submitValues[$elementName]: array(); 817 if (is_array($value) && isset($this->_submitFiles[$elementName])) { 818 foreach ($this->_submitFiles[$elementName] as $k => $v) { 819 $value = HTML_QuickForm::arrayMerge($value, $this->_reindexFiles($this->_submitFiles[$elementName][$k], $k)); 820 } 821 } 822 823 } elseif ('file' == $this->getElementType($elementName)) { 824 return $this->getElementValue($elementName); 825 826 } elseif (false !== ($pos = strpos($elementName, '['))) { 827 $base = substr($elementName, 0, $pos); 828 $keys = str_replace( 829 array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"), 830 substr($elementName, $pos + 1, -1) 831 ); 832 $idx = "['" . $keys . "']"; 833 $keyArray = explode("']['", $keys); 834 835 if (isset($this->_submitValues[$base])) { 836 $value = HTML_QuickForm_utils::recursiveValue($this->_submitValues[$base], $keyArray, NULL); 837 } 838 839 if ((is_array($value) || null === $value) && isset($this->_submitFiles[$base])) { 840 $props = array('name', 'type', 'size', 'tmp_name', 'error'); 841 $code = "if (!isset(\$this->_submitFiles['{$base}']['name']{$idx})) {\n" . 842 " return null;\n" . 843 "} else {\n" . 844 " \$v = array();\n"; 845 foreach ($props as $prop) { 846 $code .= " \$v = HTML_QuickForm::arrayMerge(\$v, \$this->_reindexFiles(\$this->_submitFiles['{$base}']['{$prop}']{$idx}, '{$prop}'));\n"; 847 } 848 $fileValue = eval($code . " return \$v;\n}\n"); 849 if (null !== $fileValue) { 850 $value = null === $value? $fileValue: HTML_QuickForm::arrayMerge($value, $fileValue); 851 } 852 } 853 } 854 855 // This is only supposed to work for groups with appendName = false 856 if (null === $value && 'group' == $this->getElementType($elementName)) { 857 $group =& $this->getElement($elementName); 858 $elements =& $group->getElements(); 859 foreach (array_keys($elements) as $key) { 860 $name = $group->getElementName($key); 861 // prevent endless recursion in case of radios and such 862 if ($name != $elementName) { 863 if (null !== ($v = $this->getSubmitValue($name))) { 864 $value[$name] = $v; 865 } 866 } 867 } 868 } 869 return $value; 870 } // end func getSubmitValue 871 872 // }}} 873 // {{{ _reindexFiles() 874 875 /** 876 * A helper function to change the indexes in $_FILES array 877 * 878 * @param mixed Some value from the $_FILES array 879 * @param string The key from the $_FILES array that should be appended 880 * @return array 881 */ 882 function _reindexFiles($value, $key) 883 { 884 if (!is_array($value)) { 885 return array($key => $value); 886 } else { 887 $ret = array(); 888 foreach ($value as $k => $v) { 889 $ret[$k] = $this->_reindexFiles($v, $key); 890 } 891 return $ret; 892 } 893 } 894 895 // }}} 896 // {{{ getElementError() 897 898 /** 899 * Returns error corresponding to validated element 900 * 901 * @param string $element Name of form element to check 902 * @since 1.0 903 * @access public 904 * @return string error message corresponding to checked element 905 */ 906 function getElementError($element) 907 { 908 if (isset($this->_errors[$element])) { 909 return $this->_errors[$element]; 910 } 911 } // end func getElementError 912 913 // }}} 914 // {{{ setElementError() 915 916 /** 917 * Set error message for a form element 918 * 919 * @param string $element Name of form element to set error for 920 * @param string $message Error message, if empty then removes the current error message 921 * @since 1.0 922 * @access public 923 * @return void 924 */ 925 function setElementError($element, $message = null) 926 { 927 if (!empty($message)) { 928 $this->_errors[$element] = $message; 929 } else { 930 unset($this->_errors[$element]); 931 } 932 } // end func setElementError 933 934 // }}} 935 // {{{ getElementType() 936 937 /** 938 * Returns the type of the given element 939 * 940 * @param string $element Name of form element 941 * @since 1.1 942 * @access public 943 * @return string Type of the element, false if the element is not found 944 */ 945 function getElementType($element) 946 { 947 if (isset($this->_elementIndex[$element])) { 948 return $this->_elements[$this->_elementIndex[$element]]->getType(); 949 } 950 return false; 951 } // end func getElementType 952 953 // }}} 954 // {{{ updateElementAttr() 955 956 /** 957 * Updates Attributes for one or more elements 958 * 959 * @param mixed $elements Array of element names/objects or string of elements to be updated 960 * @param mixed $attrs Array or sting of html attributes 961 * @since 2.10 962 * @access public 963 * @return void 964 */ 965 function updateElementAttr($elements, $attrs) 966 { 967 if (is_string($elements)) { 968 $elements = preg_split('/[ ]?,[ ]?/', $elements); 969 } 970 foreach (array_keys($elements) as $key) { 971 if (is_object($elements[$key]) && is_a($elements[$key], 'HTML_QuickForm_element')) { 972 $elements[$key]->updateAttributes($attrs); 973 } elseif (isset($this->_elementIndex[$elements[$key]])) { 974 $this->_elements[$this->_elementIndex[$elements[$key]]]->updateAttributes($attrs); 975 if (isset($this->_duplicateIndex[$elements[$key]])) { 976 foreach ($this->_duplicateIndex[$elements[$key]] as $index) { 977 $this->_elements[$index]->updateAttributes($attrs); 978 } 979 } 980 } 981 } 982 } // end func updateElementAttr 983 984 // }}} 985 // {{{ removeElement() 986 987 /** 988 * Removes an element 989 * 990 * The method "unlinks" an element from the form, returning the reference 991 * to the element object. If several elements named $elementName exist, 992 * it removes the first one, leaving the others intact. 993 * 994 * @param string $elementName The element name 995 * @param boolean $removeRules True if rules for this element are to be removed too 996 * @access public 997 * @since 2.0 998 * @return object HTML_QuickForm_element a reference to the removed element 999 * @throws HTML_QuickForm_Error 1000 */ 1001 function &removeElement($elementName, $removeRules = true) 1002 { 1003 if (!isset($this->_elementIndex[$elementName])) { 1004 $error = self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$elementName' does not exist in HTML_QuickForm::removeElement()", 'HTML_QuickForm_Error', true); 1005 return $error; 1006 } 1007 $el =& $this->_elements[$this->_elementIndex[$elementName]]; 1008 unset($this->_elements[$this->_elementIndex[$elementName]]); 1009 if (empty($this->_duplicateIndex[$elementName])) { 1010 unset($this->_elementIndex[$elementName]); 1011 } else { 1012 $this->_elementIndex[$elementName] = array_shift($this->_duplicateIndex[$elementName]); 1013 } 1014 if ($removeRules) { 1015 unset($this->_rules[$elementName], $this->_errors[$elementName]); 1016 } 1017 return $el; 1018 } // end func removeElement 1019 1020 // }}} 1021 // {{{ addRule() 1022 1023 /** 1024 * Adds a validation rule for the given field 1025 * 1026 * If the element is in fact a group, it will be considered as a whole. 1027 * To validate grouped elements as separated entities, 1028 * use addGroupRule instead of addRule. 1029 * 1030 * @param string $element Form element name 1031 * @param string $message Message to display for invalid data 1032 * @param string $type Rule type, use getRegisteredRules() to get types 1033 * @param string $format (optional)Required for extra rule data 1034 * @param string $validation (optional)Where to perform validation: "server", "client" 1035 * @param boolean $reset Client-side validation: reset the form element to its original value if there is an error? 1036 * @param boolean $force Force the rule to be applied, even if the target form element does not exist 1037 * @since 1.0 1038 * @access public 1039 * @throws HTML_QuickForm_Error 1040 */ 1041 function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false) 1042 { 1043 if (!$force) { 1044 if (!is_array($element) && !$this->elementExists($element)) { 1045 return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true); 1046 } elseif (is_array($element)) { 1047 foreach ($element as $el) { 1048 if (!$this->elementExists($el)) { 1049 return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$el' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true); 1050 } 1051 } 1052 } 1053 } 1054 if (false === ($newName = $this->isRuleRegistered($type, true))) { 1055 return self::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true); 1056 } elseif (is_string($newName)) { 1057 $type = $newName; 1058 } 1059 if (is_array($element)) { 1060 $dependent = $element; 1061 $element = array_shift($dependent); 1062 } else { 1063 $dependent = null; 1064 } 1065 if ($type == 'required' || $type == 'uploadedfile') { 1066 $this->_required[] = $element; 1067 } 1068 if (!isset($this->_rules[$element])) { 1069 $this->_rules[$element] = array(); 1070 } 1071 $this->_rules[$element][] = array( 1072 'type' => $type, 1073 'format' => $format, 1074 'message' => $message, 1075 'validation' => $validation, 1076 'reset' => $reset, 1077 'dependent' => $dependent 1078 ); 1079 } // end func addRule 1080 1081 // }}} 1082 // {{{ addGroupRule() 1083 1084 /** 1085 * Adds a validation rule for the given group of elements 1086 * 1087 * Only groups with a name can be assigned a validation rule 1088 * Use addGroupRule when you need to validate elements inside the group. 1089 * Use addRule if you need to validate the group as a whole. In this case, 1090 * the same rule will be applied to all elements in the group. 1091 * Use addRule if you need to validate the group against a function. 1092 * 1093 * @param string $group Form group name 1094 * @param mixed $arg1 Array for multiple elements or error message string for one element 1095 * @param string $type (optional)Rule type use getRegisteredRules() to get types 1096 * @param string $format (optional)Required for extra rule data 1097 * @param int $howmany (optional)How many valid elements should be in the group 1098 * @param string $validation (optional)Where to perform validation: "server", "client" 1099 * @param bool $reset Client-side: whether to reset the element's value to its original state if validation failed. 1100 * @since 2.5 1101 * @access public 1102 * @throws HTML_QuickForm_Error 1103 */ 1104 function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false) 1105 { 1106 if (!$this->elementExists($group)) { 1107 return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Group '$group' does not exist in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true); 1108 } 1109 1110 $groupObj =& $this->getElement($group); 1111 if (is_array($arg1)) { 1112 $required = 0; 1113 foreach ($arg1 as $elementIndex => $rules) { 1114 $elementName = $groupObj->getElementName($elementIndex); 1115 foreach ($rules as $rule) { 1116 $format = (isset($rule[2])) ? $rule[2] : null; 1117 $validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server'; 1118 $reset = isset($rule[4]) && $rule[4]; 1119 $type = $rule[1]; 1120 if (false === ($newName = $this->isRuleRegistered($type, true))) { 1121 return self::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true); 1122 } elseif (is_string($newName)) { 1123 $type = $newName; 1124 } 1125 1126 $this->_rules[$elementName][] = array( 1127 'type' => $type, 1128 'format' => $format, 1129 'message' => $rule[0], 1130 'validation' => $validation, 1131 'reset' => $reset, 1132 'group' => $group); 1133 1134 if ('required' == $type || 'uploadedfile' == $type) { 1135 $groupObj->_required[] = $elementName; 1136 $this->_required[] = $elementName; 1137 $required++; 1138 } 1139 } 1140 } 1141 if ($required > 0 && count($groupObj->getElements()) == $required) { 1142 $this->_required[] = $group; 1143 } 1144 } elseif (is_string($arg1)) { 1145 if (false === ($newName = $this->isRuleRegistered($type, true))) { 1146 return self::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true); 1147 } elseif (is_string($newName)) { 1148 $type = $newName; 1149 } 1150 1151 // addGroupRule() should also handle <select multiple> 1152 if (is_a($groupObj, 'html_quickform_group')) { 1153 // Radios need to be handled differently when required 1154 if ($type == 'required' && $groupObj->getGroupType() == 'radio') { 1155 $howmany = ($howmany == 0) ? 1 : $howmany; 1156 } else { 1157 $howmany = ($howmany == 0) ? count($groupObj->getElements()) : $howmany; 1158 } 1159 } 1160 1161 $this->_rules[$group][] = array('type' => $type, 1162 'format' => $format, 1163 'message' => $arg1, 1164 'validation' => $validation, 1165 'howmany' => $howmany, 1166 'reset' => $reset); 1167 if ($type == 'required') { 1168 $this->_required[] = $group; 1169 } 1170 } 1171 } // end func addGroupRule 1172 1173 // }}} 1174 // {{{ addFormRule() 1175 1176 /** 1177 * Adds a global validation rule 1178 * 1179 * This should be used when for a rule involving several fields or if 1180 * you want to use some completely custom validation for your form. 1181 * The rule function/method should return true in case of successful 1182 * validation and array('element name' => 'error') when there were errors. 1183 * 1184 * @access public 1185 * @param mixed Callback, either function name or array(&$object, 'method') 1186 * @throws HTML_QuickForm_Error 1187 */ 1188 function addFormRule($rule) 1189 { 1190 if (!is_callable($rule)) { 1191 return self::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, 'Callback function does not exist in HTML_QuickForm::addFormRule()', 'HTML_QuickForm_Error', true); 1192 } 1193 $this->_formRules[] = $rule; 1194 } 1195 1196 // }}} 1197 // {{{ applyFilter() 1198 1199 /** 1200 * Applies a data filter for the given field(s) 1201 * 1202 * @param mixed $element Form element name or array of such names 1203 * @param mixed $filter Callback, either function name or array(&$object, 'method') 1204 * @since 2.0 1205 * @access public 1206 */ 1207 function applyFilter($element, $filter) 1208 { 1209 if (!is_callable($filter)) { 1210 return self::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::applyFilter()", 'HTML_QuickForm_Error', true); 1211 } 1212 if ($element == '__ALL__') { 1213 $this->_submitValues = $this->_recursiveFilter($filter, $this->_submitValues); 1214 } else { 1215 if (!is_array($element)) { 1216 $element = array($element); 1217 } 1218 foreach ($element as $elName) { 1219 $value = $this->getSubmitValue($elName); 1220 if (null !== $value) { 1221 if (false === strpos($elName, '[')) { 1222 $this->_submitValues[$elName] = $this->_recursiveFilter($filter, $value); 1223 } else { 1224 $idx = "['" . str_replace(array(']', '['), array('', "']['"), $elName) . "']"; 1225 eval("\$this->_submitValues{$idx} = \$this->_recursiveFilter(\$filter, \$value);"); 1226 } 1227 } 1228 } 1229 } 1230 } // end func applyFilter 1231 1232 // }}} 1233 // {{{ _recursiveFilter() 1234 1235 /** 1236 * Recursively apply a filter function 1237 * 1238 * @param string $filter filter to apply 1239 * @param mixed $value submitted values 1240 * @since 2.0 1241 * @access private 1242 * @return cleaned values 1243 */ 1244 function _recursiveFilter($filter, $value) 1245 { 1246 if (is_array($value)) { 1247 $cleanValues = array(); 1248 foreach ($value as $k => $v) { 1249 $cleanValues[$k] = $this->_recursiveFilter($filter, $v); 1250 } 1251 return $cleanValues; 1252 } else { 1253 return call_user_func($filter, $value); 1254 } 1255 } // end func _recursiveFilter 1256 1257 // }}} 1258 // {{{ arrayMerge() 1259 1260 /** 1261 * Merges two arrays 1262 * 1263 * Merges two array like the PHP function array_merge but recursively. 1264 * The main difference is that existing keys will not be renumbered 1265 * if they are integers. 1266 * 1267 * @access puplic 1268 * @param array $a original array 1269 * @param array $b array which will be merged into first one 1270 * @return array merged array 1271 */ 1272 static function arrayMerge($a, $b) 1273 { 1274 if (is_null($a)) {$a = array();} 1275 if (is_null($b)) {$b = array();} 1276 foreach ($b as $k => $v) { 1277 if (is_array($v)) { 1278 if (isset($a[$k]) && !is_array($a[$k])) { 1279 $a[$k] = $v; 1280 } else { 1281 if (!isset($a[$k])) { 1282 $a[$k] = array(); 1283 } 1284 $a[$k] = HTML_QuickForm::arrayMerge($a[$k], $v); 1285 } 1286 } else { 1287 $a[$k] = $v; 1288 } 1289 } 1290 return $a; 1291 } // end func arrayMerge 1292 1293 // }}} 1294 // {{{ isTypeRegistered() 1295 1296 /** 1297 * Returns whether or not the form element type is supported 1298 * 1299 * @param string $type Form element type 1300 * @since 1.0 1301 * @access public 1302 * @return boolean 1303 */ 1304 function isTypeRegistered($type) 1305 { 1306 return isset($GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][strtolower($type)]); 1307 } // end func isTypeRegistered 1308 1309 // }}} 1310 // {{{ getRegisteredTypes() 1311 1312 /** 1313 * Returns an array of registered element types 1314 * 1315 * @since 1.0 1316 * @access public 1317 * @return array 1318 */ 1319 function getRegisteredTypes() 1320 { 1321 return array_keys($GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']); 1322 } // end func getRegisteredTypes 1323 1324 // }}} 1325 // {{{ isRuleRegistered() 1326 1327 /** 1328 * Returns whether or not the given rule is supported 1329 * 1330 * @param string $name Validation rule name 1331 * @param bool Whether to automatically register subclasses of HTML_QuickForm_Rule 1332 * @since 1.0 1333 * @access public 1334 * @return mixed true if previously registered, false if not, new rule name if auto-registering worked 1335 */ 1336 function isRuleRegistered($name, $autoRegister = false) 1337 { 1338 if (is_scalar($name) && isset($GLOBALS['_HTML_QuickForm_registered_rules'][$name])) { 1339 return true; 1340 } elseif (!$autoRegister) { 1341 return false; 1342 } 1343 // automatically register the rule if requested 1344 include_once 'HTML/QuickForm/RuleRegistry.php'; 1345 $ruleName = false; 1346 if (is_object($name) && is_a($name, 'html_quickform_rule')) { 1347 $ruleName = !empty($name->name)? $name->name: strtolower(get_class($name)); 1348 } elseif (is_string($name) && class_exists($name)) { 1349 $parent = strtolower($name); 1350 do { 1351 if ('html_quickform_rule' == strtolower($parent)) { 1352 $ruleName = strtolower($name); 1353 break; 1354 } 1355 } while ($parent = get_parent_class($parent)); 1356 } 1357 if ($ruleName) { 1358 $registry =& HTML_QuickForm_RuleRegistry::singleton(); 1359 $registry->registerRule($ruleName, null, $name); 1360 } 1361 return $ruleName; 1362 } // end func isRuleRegistered 1363 1364 // }}} 1365 // {{{ getRegisteredRules() 1366 1367 /** 1368 * Returns an array of registered validation rules 1369 * 1370 * @since 1.0 1371 * @access public 1372 * @return array 1373 */ 1374 function getRegisteredRules() 1375 { 1376 return array_keys($GLOBALS['_HTML_QuickForm_registered_rules']); 1377 } // end func getRegisteredRules 1378 1379 // }}} 1380 // {{{ isElementRequired() 1381 1382 /** 1383 * Returns whether or not the form element is required 1384 * 1385 * @param string $element Form element name 1386 * @since 1.0 1387 * @access public 1388 * @return boolean 1389 */ 1390 function isElementRequired($element) 1391 { 1392 return in_array($element, $this->_required, true); 1393 } // end func isElementRequired 1394 1395 // }}} 1396 // {{{ isElementFrozen() 1397 1398 /** 1399 * Returns whether or not the form element is frozen 1400 * 1401 * @param string $element Form element name 1402 * @since 1.0 1403 * @access public 1404 * @return boolean 1405 */ 1406 function isElementFrozen($element) 1407 { 1408 if (isset($this->_elementIndex[$element])) { 1409 return $this->_elements[$this->_elementIndex[$element]]->isFrozen(); 1410 } 1411 return false; 1412 } // end func isElementFrozen 1413 1414 // }}} 1415 // {{{ setJsWarnings() 1416 1417 /** 1418 * Sets JavaScript warning messages 1419 * 1420 * @param string $pref Prefix warning 1421 * @param string $post Postfix warning 1422 * @since 1.1 1423 * @access public 1424 * @return void 1425 */ 1426 function setJsWarnings($pref, $post) 1427 { 1428 $this->_jsPrefix = $pref; 1429 $this->_jsPostfix = $post; 1430 } // end func setJsWarnings 1431 1432 // }}} 1433 // {{{ setRequiredNote() 1434 1435 /** 1436 * Sets required-note 1437 * 1438 * @param string $note Message indicating some elements are required 1439 * @since 1.1 1440 * @access public 1441 * @return void 1442 */ 1443 function setRequiredNote($note) 1444 { 1445 $this->_requiredNote = $note; 1446 } // end func setRequiredNote 1447 1448 // }}} 1449 // {{{ getRequiredNote() 1450 1451 /** 1452 * Returns the required note 1453 * 1454 * @since 2.0 1455 * @access public 1456 * @return string 1457 */ 1458 function getRequiredNote() 1459 { 1460 return $this->_requiredNote; 1461 } // end func getRequiredNote 1462 1463 // }}} 1464 // {{{ validate() 1465 1466 /** 1467 * Performs the server side validation 1468 * @access public 1469 * @since 1.0 1470 * @return boolean true if no error found 1471 */ 1472 function validate() 1473 { 1474 if (count($this->_rules) == 0 && count($this->_formRules) == 0 && 1475 $this->isSubmitted()) { 1476 return (0 == count($this->_errors)); 1477 } elseif (!$this->isSubmitted()) { 1478 return false; 1479 } 1480 1481 include_once('HTML/QuickForm/RuleRegistry.php'); 1482 $registry =& HTML_QuickForm_RuleRegistry::singleton(); 1483 1484 foreach ($this->_rules as $target => $rules) { 1485 $submitValue = $this->getSubmitValue($target); 1486 1487 foreach ($rules as $rule) { 1488 if ((isset($rule['group']) && isset($this->_errors[$rule['group']])) || 1489 isset($this->_errors[$target])) { 1490 continue 2; 1491 } 1492 // If element is not required and is empty, we shouldn't validate it 1493 if (!$this->isElementRequired($target)) { 1494 if (!isset($submitValue) || '' == $submitValue) { 1495 continue 2; 1496 // Fix for bug #3501: we shouldn't validate not uploaded files, either. 1497 // Unfortunately, we can't just use $element->isUploadedFile() since 1498 // the element in question can be buried in group. Thus this hack. 1499 } elseif (is_array($submitValue)) { 1500 if (false === ($pos = strpos($target, '['))) { 1501 $isUpload = !empty($this->_submitFiles[$target]); 1502 } else { 1503 $base = substr($target, 0, $pos); 1504 $idx = "['" . str_replace(array(']', '['), array('', "']['"), substr($target, $pos + 1, -1)) . "']"; 1505 eval("\$isUpload = isset(\$this->_submitFiles['{$base}']['name']{$idx});"); 1506 } 1507 if ($isUpload && (!isset($submitValue['error']) || 0 != $submitValue['error'])) { 1508 continue 2; 1509 } 1510 } 1511 } 1512 if (isset($rule['dependent']) && is_array($rule['dependent'])) { 1513 $values = array($submitValue); 1514 foreach ($rule['dependent'] as $elName) { 1515 $values[] = $this->getSubmitValue($elName); 1516 } 1517 $result = $registry->validate($rule['type'], $values, $rule['format'], true); 1518 } elseif (is_array($submitValue) && !isset($rule['howmany'])) { 1519 $result = $registry->validate($rule['type'], $submitValue, $rule['format'], true); 1520 } else { 1521 $result = $registry->validate($rule['type'], $submitValue, $rule['format'], false); 1522 } 1523 1524 if (!$result || (!empty($rule['howmany']) && $rule['howmany'] > (int)$result)) { 1525 if (isset($rule['group'])) { 1526 $this->_errors[$rule['group']] = $rule['message']; 1527 } else { 1528 $this->_errors[$target] = $rule['message']; 1529 } 1530 } 1531 } 1532 } 1533 1534 // process the global rules now 1535 foreach ($this->_formRules as $rule) { 1536 if (true !== ($res = call_user_func($rule, $this->_submitValues, $this->_submitFiles))) { 1537 if (is_array($res)) { 1538 $this->_errors += $res; 1539 } else { 1540 return self::raiseError(null, QUICKFORM_ERROR, null, E_USER_WARNING, 'Form rule callback returned invalid value in HTML_QuickForm::validate()', 'HTML_QuickForm_Error', true); 1541 } 1542 } 1543 } 1544 1545 return (0 == count($this->_errors)); 1546 } // end func validate 1547 1548 // }}} 1549 // {{{ freeze() 1550 1551 /** 1552 * Displays elements without HTML input tags 1553 * 1554 * @param mixed $elementList array or string of element(s) to be frozen 1555 * @since 1.0 1556 * @access public 1557 * @throws HTML_QuickForm_Error 1558 */ 1559 function freeze($elementList=null) 1560 { 1561 if (!isset($elementList)) { 1562 $this->_freezeAll = true; 1563 $elementList = array(); 1564 } else { 1565 if (!is_array($elementList)) { 1566 $elementList = preg_split('/[ ]*,[ ]*/', $elementList); 1567 } 1568 $elementList = array_flip($elementList); 1569 } 1570 1571 foreach (array_keys($this->_elements) as $key) { 1572 $name = $this->_elements[$key]->getName(); 1573 if ($this->_freezeAll || isset($elementList[$name])) { 1574 $this->_elements[$key]->freeze(); 1575 unset($elementList[$name]); 1576 } 1577 } 1578 1579 if (!empty($elementList)) { 1580 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); 1581 } 1582 return true; 1583 } // end func freeze 1584 1585 // }}} 1586 // {{{ isFrozen() 1587 1588 /** 1589 * Returns whether or not the whole form is frozen 1590 * 1591 * @since 3.0 1592 * @access public 1593 * @return boolean 1594 */ 1595 function isFrozen() 1596 { 1597 return $this->_freezeAll; 1598 } // end func isFrozen 1599 1600 // }}} 1601 // {{{ process() 1602 1603 /** 1604 * Performs the form data processing 1605 * 1606 * @param mixed $callback Callback, either function name or array(&$object, 'method') 1607 * @param bool $mergeFiles Whether uploaded files should be processed too 1608 * @since 1.0 1609 * @access public 1610 * @throws HTML_QuickForm_Error 1611 */ 1612 function process($callback, $mergeFiles = true) 1613 { 1614 if (!is_callable($callback)) { 1615 return self::raiseError(null, QUICKFORM_INVALID_PROCESS, null, E_USER_WARNING, "Callback function does not exist in QuickForm::process()", 'HTML_QuickForm_Error', true); 1616 } 1617 $values = ($mergeFiles === true) ? HTML_QuickForm::arrayMerge($this->_submitValues, $this->_submitFiles) : $this->_submitValues; 1618 return call_user_func($callback, $values); 1619 } // end func process 1620 1621 // }}} 1622 // {{{ accept() 1623 1624 /** 1625 * Accepts a renderer 1626 * 1627 * @param object An HTML_QuickForm_Renderer object 1628 * @since 3.0 1629 * @access public 1630 * @return void 1631 */ 1632 function accept(&$renderer) 1633 { 1634 $renderer->startForm($this); 1635 foreach (array_keys($this->_elements) as $key) { 1636 $element =& $this->_elements[$key]; 1637 $elementName = $element->getName(); 1638 $required = ($this->isElementRequired($elementName) && !$element->isFrozen()); 1639 $error = $this->getElementError($elementName); 1640 $element->accept($renderer, $required, $error); 1641 } 1642 $renderer->finishForm($this); 1643 } // end func accept 1644 1645 // }}} 1646 // {{{ defaultRenderer() 1647 1648 /** 1649 * Returns a reference to default renderer object 1650 * 1651 * @access public 1652 * @since 3.0 1653 * @return object a default renderer object 1654 */ 1655 function &defaultRenderer() 1656 { 1657 if (!isset($GLOBALS['_HTML_QuickForm_default_renderer'])) { 1658 include_once('HTML/QuickForm/Renderer/Default.php'); 1659 $GLOBALS['_HTML_QuickForm_default_renderer'] = new HTML_QuickForm_Renderer_Default(); //Moodle: PHP 5.3 compatibility 1660 } 1661 return $GLOBALS['_HTML_QuickForm_default_renderer']; 1662 } // end func defaultRenderer 1663 1664 // }}} 1665 // {{{ toHtml () 1666 1667 /** 1668 * Returns an HTML version of the form 1669 * 1670 * @param string $in_data (optional) Any extra data to insert right 1671 * before form is rendered. Useful when using templates. 1672 * 1673 * @return string Html version of the form 1674 * @since 1.0 1675 * @access public 1676 */ 1677 function toHtml ($in_data = null) 1678 { 1679 if (!is_null($in_data)) { 1680 $this->addElement('html', $in_data); 1681 } 1682 $renderer =& $this->defaultRenderer(); 1683 $this->accept($renderer); 1684 return $renderer->toHtml(); 1685 } // end func toHtml 1686 1687 // }}} 1688 // {{{ getValidationScript() 1689 1690 /** 1691 * Returns the client side validation script 1692 * 1693 * @since 2.0 1694 * @access public 1695 * @return string Javascript to perform validation, empty string if no 'client' rules were added 1696 */ 1697 function getValidationScript() 1698 { 1699 if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) { 1700 return ''; 1701 } 1702 1703 include_once('HTML/QuickForm/RuleRegistry.php'); 1704 $registry =& HTML_QuickForm_RuleRegistry::singleton(); 1705 $test = array(); 1706 $js_escape = array( 1707 "\r" => '\r', 1708 "\n" => '\n', 1709 "\t" => '\t', 1710 "'" => "\\'", 1711 '"' => '\"', 1712 '\\' => '\\\\' 1713 ); 1714 1715 foreach ($this->_rules as $elementName => $rules) { 1716 foreach ($rules as $rule) { 1717 if ('client' == $rule['validation']) { 1718 unset($element); 1719 1720 $dependent = isset($rule['dependent']) && is_array($rule['dependent']); 1721 $rule['message'] = strtr($rule['message'], $js_escape); 1722 1723 if (isset($rule['group'])) { 1724 $group =& $this->getElement($rule['group']); 1725 // No JavaScript validation for frozen elements 1726 if ($group->isFrozen()) { 1727 continue 2; 1728 } 1729 $elements =& $group->getElements(); 1730 foreach (array_keys($elements) as $key) { 1731 if ($elementName == $group->getElementName($key)) { 1732 $element =& $elements[$key]; 1733 break; 1734 } 1735 } 1736 } elseif ($dependent) { 1737 $element = array(); 1738 $element[] =& $this->getElement($elementName); 1739 foreach ($rule['dependent'] as $elName) { 1740 $element[] =& $this->getElement($elName); 1741 } 1742 } else { 1743 $element =& $this->getElement($elementName); 1744 } 1745 // No JavaScript validation for frozen elements 1746 if (is_object($element) && $element->isFrozen()) { 1747 continue 2; 1748 } elseif (is_array($element)) { 1749 foreach (array_keys($element) as $key) { 1750 if ($element[$key]->isFrozen()) { 1751 continue 3; 1752 } 1753 } 1754 } 1755 1756 $test[] = $registry->getValidationScript($element, $elementName, $rule); 1757 } 1758 } 1759 } 1760 if (count($test) > 0) { 1761 return 1762 "\n<script type=\"text/javascript\">\n" . 1763 "//<![CDATA[\n" . 1764 "function validate_" . $this->_attributes['id'] . "(frm) {\n" . 1765 " var value = '';\n" . 1766 " var errFlag = new Array();\n" . 1767 " var _qfGroups = {};\n" . 1768 " _qfMsg = '';\n\n" . 1769 join("\n", $test) . 1770 "\n if (_qfMsg != '') {\n" . 1771 " _qfMsg = '" . strtr($this->_jsPrefix, $js_escape) . "' + _qfMsg;\n" . 1772 " _qfMsg = _qfMsg + '\\n" . strtr($this->_jsPostfix, $js_escape) . "';\n" . 1773 " alert(_qfMsg);\n" . 1774 " return false;\n" . 1775 " }\n" . 1776 " return true;\n" . 1777 "}\n" . 1778 "//]]>\n" . 1779 "</script>"; 1780 } 1781 return ''; 1782 } // end func getValidationScript 1783 1784 // }}} 1785 // {{{ getSubmitValues() 1786 1787 /** 1788 * Returns the values submitted by the form 1789 * 1790 * @since 2.0 1791 * @access public 1792 * @param bool Whether uploaded files should be returned too 1793 * @return array 1794 */ 1795 function getSubmitValues($mergeFiles = false) 1796 { 1797 return $mergeFiles? HTML_QuickForm::arrayMerge($this->_submitValues, $this->_submitFiles): $this->_submitValues; 1798 } // end func getSubmitValues 1799 1800 // }}} 1801 // {{{ toArray() 1802 1803 /** 1804 * Returns the form's contents in an array. 1805 * 1806 * The description of the array structure is in HTML_QuickForm_Renderer_Array docs 1807 * 1808 * @since 2.0 1809 * @access public 1810 * @param bool Whether to collect hidden elements (passed to the Renderer's constructor) 1811 * @return array of form contents 1812 */ 1813 function toArray($collectHidden = false) 1814 { 1815 include_once 'HTML/QuickForm/Renderer/Array.php'; 1816 $renderer = new HTML_QuickForm_Renderer_Array($collectHidden); //Moodle: PHP 5.3 compatibility 1817 $this->accept($renderer); 1818 return $renderer->toArray(); 1819 } // end func toArray 1820 1821 // }}} 1822 // {{{ exportValue() 1823 1824 /** 1825 * Returns a 'safe' element's value 1826 * 1827 * This method first tries to find a cleaned-up submitted value, 1828 * it will return a value set by setValue()/setDefaults()/setConstants() 1829 * if submitted value does not exist for the given element. 1830 * 1831 * @param string Name of an element 1832 * @access public 1833 * @return mixed 1834 */ 1835 function exportValue($element) 1836 { 1837 if (!isset($this->_elementIndex[$element])) { 1838 return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true); 1839 } 1840 $value = $this->_elements[$this->_elementIndex[$element]]->exportValue($this->_submitValues, false); 1841 if (isset($this->_duplicateIndex[$element])) { 1842 foreach ($this->_duplicateIndex[$element] as $index) { 1843 if (null !== ($v = $this->_elements[$index]->exportValue($this->_submitValues, false))) { 1844 if (is_array($value)) { 1845 $value[] = $v; 1846 } else { 1847 $value = (null === $value)? $v: array($value, $v); 1848 } 1849 } 1850 } 1851 } 1852 return $value; 1853 } 1854 1855 // }}} 1856 // {{{ exportValues() 1857 1858 /** 1859 * Returns 'safe' elements' values 1860 * 1861 * Unlike getSubmitValues(), this will return only the values 1862 * corresponding to the elements present in the form. 1863 * 1864 * @param mixed Array/string of element names, whose values we want. If not set then return all elements. 1865 * @access public 1866 * @return array An assoc array of elements' values 1867 * @throws HTML_QuickForm_Error 1868 */ 1869 function exportValues($elementList = null) 1870 { 1871 $values = array(); 1872 if (null === $elementList) { 1873 // iterate over all elements, calling their exportValue() methods 1874 foreach (array_keys($this->_elements) as $key) { 1875 $value = $this->_elements[$key]->exportValue($this->_submitValues, true); 1876 if (is_array($value)) { 1877 // This shit throws a bogus warning in PHP 4.3.x 1878 $values = HTML_QuickForm::arrayMerge($values, $value); 1879 } 1880 } 1881 } else { 1882 if (!is_array($elementList)) { 1883 $elementList = array_map('trim', explode(',', $elementList)); 1884 } 1885 foreach ($elementList as $elementName) { 1886 $value = $this->exportValue($elementName); 1887 $pear = new PEAR(); 1888 if ($pear->isError($value)) { 1889 return $value; 1890 } 1891 $values[$elementName] = $value; 1892 } 1893 } 1894 return $values; 1895 } 1896 1897 // }}} 1898 // {{{ isSubmitted() 1899 1900 /** 1901 * Tells whether the form was already submitted 1902 * 1903 * This is useful since the _submitFiles and _submitValues arrays 1904 * may be completely empty after the trackSubmit value is removed. 1905 * 1906 * @access public 1907 * @return bool 1908 */ 1909 function isSubmitted() 1910 { 1911 return $this->_flagSubmitted; 1912 } 1913 1914 1915 // }}} 1916 // {{{ isError() 1917 1918 /** 1919 * Tell whether a result from a QuickForm method is an error (an instance of HTML_QuickForm_Error) 1920 * 1921 * @access public 1922 * @param mixed result code 1923 * @return bool whether $value is an error 1924 */ 1925 static function isError($value) 1926 { 1927 return (is_object($value) && is_a($value, 'html_quickform_error')); 1928 } // end func isError 1929 1930 // }}} 1931 // {{{ errorMessage() 1932 1933 /** 1934 * Return a textual error message for an QuickForm error code 1935 * 1936 * @access public 1937 * @param int error code 1938 * @return string error message 1939 */ 1940 static function errorMessage($value) 1941 { 1942 // make the variable static so that it only has to do the defining on the first call 1943 static $errorMessages; 1944 1945 // define the varies error messages 1946 if (!isset($errorMessages)) { 1947 $errorMessages = array( 1948 QUICKFORM_OK => 'no error', 1949 QUICKFORM_ERROR => 'unknown error', 1950 QUICKFORM_INVALID_RULE => 'the rule does not exist as a registered rule', 1951 QUICKFORM_NONEXIST_ELEMENT => 'nonexistent html element', 1952 QUICKFORM_INVALID_FILTER => 'invalid filter', 1953 QUICKFORM_UNREGISTERED_ELEMENT => 'unregistered element', 1954 QUICKFORM_INVALID_ELEMENT_NAME => 'element already exists', 1955 QUICKFORM_INVALID_PROCESS => 'process callback does not exist', 1956 QUICKFORM_DEPRECATED => 'method is deprecated', 1957 QUICKFORM_INVALID_DATASOURCE => 'datasource is not an object' 1958 ); 1959 } 1960 1961 // If this is an error object, then grab the corresponding error code 1962 if (HTML_QuickForm::isError($value)) { 1963 $value = $value->getCode(); 1964 } 1965 1966 // return the textual error message corresponding to the code 1967 return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[QUICKFORM_ERROR]; 1968 } // end func errorMessage 1969 1970 // }}} 1971 } // end class HTML_QuickForm 1972 1973 class HTML_QuickForm_Error extends PEAR_Error { 1974 1975 // {{{ properties 1976 1977 /** 1978 * Prefix for all error messages 1979 * @var string 1980 */ 1981 var $error_message_prefix = 'QuickForm Error: '; 1982 1983 // }}} 1984 // {{{ constructor 1985 1986 /** 1987 * Creates a quickform error object, extending the PEAR_Error class 1988 * 1989 * @param int $code the error code 1990 * @param int $mode the reaction to the error, either return, die or trigger/callback 1991 * @param int $level intensity of the error (PHP error code) 1992 * @param mixed $debuginfo any information that can inform user as to nature of the error 1993 */ 1994 public function __construct($code = QUICKFORM_ERROR, $mode = PEAR_ERROR_RETURN, 1995 $level = E_USER_NOTICE, $debuginfo = null) 1996 { 1997 if (is_int($code)) { 1998 parent::__construct(HTML_QuickForm::errorMessage($code), $code, $mode, $level, $debuginfo); 1999 } else { 2000 parent::__construct("Invalid error code: $code", QUICKFORM_ERROR, $mode, $level, $debuginfo); 2001 } 2002 } 2003 2004 /** 2005 * Old syntax of class constructor. Deprecated in PHP7. 2006 * 2007 * @deprecated since Moodle 3.1 2008 */ 2009 public function HTML_QuickForm_Error($code = QUICKFORM_ERROR, $mode = PEAR_ERROR_RETURN, 2010 $level = E_USER_NOTICE, $debuginfo = null) { 2011 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 2012 self::__construct($code, $mode, $level, $debuginfo); 2013 } 2014 2015 // }}} 2016 } // end class HTML_QuickForm_Error 2017 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body