Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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