Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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 < 6; $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    bool       $appendName     (optional)specify whether the group name should be
 726       *                                      used in the form element name ex: group[element]
 727       * @param     mixed     $attributes     Either a typical HTML attribute string or an associative array
 728       * @return   object     reference to added group of elements
 729       * @since    2.8
 730       * @access   public
 731       * @throws   PEAR_Error
 732       */
 733      function &addGroup($elements, $name = null, $groupLabel = '', $separator = null, $appendName = true, $attributes = null)
 734      {
 735          static $anonGroups = 1;
 736  
 737          if (0 == strlen($name ?? '')) {
 738              $name       = 'qf_group_' . $anonGroups++;
 739              $appendName = false;
 740          }
 741          $group =& $this->addElement('group', $name, $groupLabel, $elements, $separator, $appendName, $attributes);
 742          return $group;
 743      } // end func addGroup
 744  
 745      // }}}
 746      // {{{ &getElement()
 747  
 748      /**
 749       * Returns a reference to the element
 750       *
 751       * @param     string     $element    Element name
 752       * @since     2.0
 753       * @access    public
 754       * @return    object     reference to element
 755       * @throws    HTML_QuickForm_Error
 756       */
 757      function &getElement($element)
 758      {
 759          if (isset($this->_elementIndex[$element])) {
 760              return $this->_elements[$this->_elementIndex[$element]];
 761          } else {
 762              $error = self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElement()", 'HTML_QuickForm_Error', true);
 763              return $error;
 764          }
 765      } // end func getElement
 766  
 767      // }}}
 768      // {{{ &getElementValue()
 769  
 770      /**
 771       * Returns the element's raw value
 772       *
 773       * This returns the value as submitted by the form (not filtered)
 774       * or set via setDefaults() or setConstants()
 775       *
 776       * @param     string     $element    Element name
 777       * @since     2.0
 778       * @access    public
 779       * @return    mixed     element value
 780       * @throws    HTML_QuickForm_Error
 781       */
 782      function &getElementValue($element)
 783      {
 784          if (!isset($this->_elementIndex[$element])) {
 785              $error = self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true);
 786              return $error;
 787          }
 788          $value = $this->_elements[$this->_elementIndex[$element]]->getValue();
 789          if (isset($this->_duplicateIndex[$element])) {
 790              foreach ($this->_duplicateIndex[$element] as $index) {
 791                  if (null !== ($v = $this->_elements[$index]->getValue())) {
 792                      if (is_array($value)) {
 793                          $value[] = $v;
 794                      } else {
 795                          $value = (null === $value)? $v: array($value, $v);
 796                      }
 797                  }
 798              }
 799          }
 800          return $value;
 801      } // end func getElementValue
 802  
 803      // }}}
 804      // {{{ getSubmitValue()
 805  
 806      /**
 807       * Returns the elements value after submit and filter
 808       *
 809       * @param     string     Element name
 810       * @since     2.0
 811       * @access    public
 812       * @return    mixed     submitted element value or null if not set
 813       */
 814      function getSubmitValue($elementName)
 815      {
 816          $value = null;
 817          $elementName = $elementName ?? '';
 818          if (isset($this->_submitValues[$elementName]) || isset($this->_submitFiles[$elementName])) {
 819              $value = isset($this->_submitValues[$elementName])? $this->_submitValues[$elementName]: array();
 820              if (is_array($value) && isset($this->_submitFiles[$elementName])) {
 821                  foreach ($this->_submitFiles[$elementName] as $k => $v) {
 822                      $value = HTML_QuickForm::arrayMerge($value, $this->_reindexFiles($this->_submitFiles[$elementName][$k], $k));
 823                  }
 824              }
 825  
 826          } elseif ('file' == $this->getElementType($elementName)) {
 827              return $this->getElementValue($elementName);
 828  
 829          } elseif (false !== ($pos = strpos($elementName, '['))) {
 830              $base = substr($elementName, 0, $pos);
 831              $keys = str_replace(
 832                  array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"),
 833                  substr($elementName, $pos + 1, -1)
 834              );
 835              $idx  = "['" . $keys . "']";
 836              $keyArray = explode("']['", $keys);
 837  
 838              if (isset($this->_submitValues[$base])) {
 839                  $value = HTML_QuickForm_utils::recursiveValue($this->_submitValues[$base], $keyArray, NULL);
 840              }
 841  
 842              if ((is_array($value) || null === $value) && isset($this->_submitFiles[$base])) {
 843                  $props = array('name', 'type', 'size', 'tmp_name', 'error');
 844                  $code  = "if (!isset(\$this->_submitFiles['{$base}']['name']{$idx})) {\n" .
 845                           "    return null;\n" .
 846                           "} else {\n" .
 847                           "    \$v = array();\n";
 848                  foreach ($props as $prop) {
 849                      $code .= "    \$v = HTML_QuickForm::arrayMerge(\$v, \$this->_reindexFiles(\$this->_submitFiles['{$base}']['{$prop}']{$idx}, '{$prop}'));\n";
 850                  }
 851                  $fileValue = eval($code . "    return \$v;\n}\n");
 852                  if (null !== $fileValue) {
 853                      $value = null === $value? $fileValue: HTML_QuickForm::arrayMerge($value, $fileValue);
 854                  }
 855              }
 856          }
 857  
 858          // This is only supposed to work for groups with appendName = false
 859          if (null === $value && 'group' == $this->getElementType($elementName)) {
 860              $group    =& $this->getElement($elementName);
 861              $elements =& $group->getElements();
 862              foreach (array_keys($elements) as $key) {
 863                  $name = $group->getElementName($key);
 864                  // prevent endless recursion in case of radios and such
 865                  if ($name != $elementName) {
 866                      if (null !== ($v = $this->getSubmitValue($name))) {
 867                          $value[$name] = $v;
 868                      }
 869                  }
 870              }
 871          }
 872          return $value;
 873      } // end func getSubmitValue
 874  
 875      // }}}
 876      // {{{ _reindexFiles()
 877  
 878     /**
 879      * A helper function to change the indexes in $_FILES array
 880      *
 881      * @param  mixed   Some value from the $_FILES array
 882      * @param  string  The key from the $_FILES array that should be appended
 883      * @return array
 884      */
 885      function _reindexFiles($value, $key)
 886      {
 887          if (!is_array($value)) {
 888              return array($key => $value);
 889          } else {
 890              $ret = array();
 891              foreach ($value as $k => $v) {
 892                  $ret[$k] = $this->_reindexFiles($v, $key);
 893              }
 894              return $ret;
 895          }
 896      }
 897  
 898      // }}}
 899      // {{{ getElementError()
 900  
 901      /**
 902       * Returns error corresponding to validated element
 903       *
 904       * @param     string    $element        Name of form element to check
 905       * @since     1.0
 906       * @access    public
 907       * @return    string    error message corresponding to checked element
 908       */
 909      function getElementError($element)
 910      {
 911          if (isset($this->_errors[$element])) {
 912              return $this->_errors[$element];
 913          }
 914      } // end func getElementError
 915  
 916      // }}}
 917      // {{{ setElementError()
 918  
 919      /**
 920       * Set error message for a form element
 921       *
 922       * @param     string    $element    Name of form element to set error for
 923       * @param     string    $message    Error message, if empty then removes the current error message
 924       * @since     1.0
 925       * @access    public
 926       * @return    void
 927       */
 928      function setElementError($element, $message = null)
 929      {
 930          if (!empty($message)) {
 931              $this->_errors[$element] = $message;
 932          } else {
 933              unset($this->_errors[$element]);
 934          }
 935      } // end func setElementError
 936  
 937       // }}}
 938       // {{{ getElementType()
 939  
 940       /**
 941        * Returns the type of the given element
 942        *
 943        * @param      string    $element    Name of form element
 944        * @since      1.1
 945        * @access     public
 946        * @return     string    Type of the element, false if the element is not found
 947        */
 948       function getElementType($element)
 949       {
 950           if (isset($this->_elementIndex[$element])) {
 951               return $this->_elements[$this->_elementIndex[$element]]->getType();
 952           }
 953           return false;
 954       } // end func getElementType
 955  
 956       // }}}
 957       // {{{ updateElementAttr()
 958  
 959      /**
 960       * Updates Attributes for one or more elements
 961       *
 962       * @param      mixed    $elements   Array of element names/objects or string of elements to be updated
 963       * @param      mixed    $attrs      Array or sting of html attributes
 964       * @since      2.10
 965       * @access     public
 966       * @return     void
 967       */
 968      function updateElementAttr($elements, $attrs)
 969      {
 970          if (is_string($elements)) {
 971              $elements = preg_split('/[ ]?,[ ]?/', $elements);
 972          }
 973          foreach (array_keys($elements) as $key) {
 974              if (is_object($elements[$key]) && is_a($elements[$key], 'HTML_QuickForm_element')) {
 975                  $elements[$key]->updateAttributes($attrs);
 976              } elseif (isset($this->_elementIndex[$elements[$key]])) {
 977                  $this->_elements[$this->_elementIndex[$elements[$key]]]->updateAttributes($attrs);
 978                  if (isset($this->_duplicateIndex[$elements[$key]])) {
 979                      foreach ($this->_duplicateIndex[$elements[$key]] as $index) {
 980                          $this->_elements[$index]->updateAttributes($attrs);
 981                      }
 982                  }
 983              }
 984          }
 985      } // end func updateElementAttr
 986  
 987      // }}}
 988      // {{{ removeElement()
 989  
 990      /**
 991       * Removes an element
 992       *
 993       * The method "unlinks" an element from the form, returning the reference
 994       * to the element object. If several elements named $elementName exist,
 995       * it removes the first one, leaving the others intact.
 996       *
 997       * @param string    $elementName The element name
 998       * @param boolean   $removeRules True if rules for this element are to be removed too
 999       * @access public
1000       * @since 2.0
1001       * @return object HTML_QuickForm_element    a reference to the removed element
1002       * @throws HTML_QuickForm_Error
1003       */
1004      function &removeElement($elementName, $removeRules = true)
1005      {
1006          if (!isset($this->_elementIndex[$elementName])) {
1007              $error = self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$elementName' does not exist in HTML_QuickForm::removeElement()", 'HTML_QuickForm_Error', true);
1008              return $error;
1009          }
1010          $el =& $this->_elements[$this->_elementIndex[$elementName]];
1011          unset($this->_elements[$this->_elementIndex[$elementName]]);
1012          if (empty($this->_duplicateIndex[$elementName])) {
1013              unset($this->_elementIndex[$elementName]);
1014          } else {
1015              $this->_elementIndex[$elementName] = array_shift($this->_duplicateIndex[$elementName]);
1016          }
1017          if ($removeRules) {
1018              unset($this->_rules[$elementName], $this->_errors[$elementName]);
1019          }
1020          return $el;
1021      } // end func removeElement
1022  
1023      // }}}
1024      // {{{ addRule()
1025  
1026      /**
1027       * Adds a validation rule for the given field
1028       *
1029       * If the element is in fact a group, it will be considered as a whole.
1030       * To validate grouped elements as separated entities,
1031       * use addGroupRule instead of addRule.
1032       *
1033       * @param    string     $element       Form element name
1034       * @param    string     $message       Message to display for invalid data
1035       * @param    string     $type          Rule type, use getRegisteredRules() to get types
1036       * @param    string     $format        (optional)Required for extra rule data
1037       * @param    string     $validation    (optional)Where to perform validation: "server", "client"
1038       * @param    boolean    $reset         Client-side validation: reset the form element to its original value if there is an error?
1039       * @param    boolean    $force         Force the rule to be applied, even if the target form element does not exist
1040       * @since    1.0
1041       * @access   public
1042       * @throws   HTML_QuickForm_Error
1043       */
1044      function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false)
1045      {
1046          if (!$force) {
1047              if (!is_array($element) && !$this->elementExists($element)) {
1048                  return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
1049              } elseif (is_array($element)) {
1050                  foreach ($element as $el) {
1051                      if (!$this->elementExists($el)) {
1052                          return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$el' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
1053                      }
1054                  }
1055              }
1056          }
1057          if (false === ($newName = $this->isRuleRegistered($type, true))) {
1058              return self::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
1059          } elseif (is_string($newName)) {
1060              $type = $newName;
1061          }
1062          if (is_array($element)) {
1063              $dependent = $element;
1064              $element   = array_shift($dependent);
1065          } else {
1066              $dependent = null;
1067          }
1068          if ($type == 'required' || $type == 'uploadedfile') {
1069              $this->_required[] = $element;
1070          }
1071          if (!isset($this->_rules[$element])) {
1072              $this->_rules[$element] = array();
1073          }
1074          $this->_rules[$element][] = array(
1075              'type'        => $type,
1076              'format'      => $format,
1077              'message'     => $message,
1078              'validation'  => $validation,
1079              'reset'       => $reset,
1080              'dependent'   => $dependent
1081          );
1082      } // end func addRule
1083  
1084      // }}}
1085      // {{{ addGroupRule()
1086  
1087      /**
1088       * Adds a validation rule for the given group of elements
1089       *
1090       * Only groups with a name can be assigned a validation rule
1091       * Use addGroupRule when you need to validate elements inside the group.
1092       * Use addRule if you need to validate the group as a whole. In this case,
1093       * the same rule will be applied to all elements in the group.
1094       * Use addRule if you need to validate the group against a function.
1095       *
1096       * @param    string     $group         Form group name
1097       * @param    mixed      $arg1          Array for multiple elements or error message string for one element
1098       * @param    string     $type          (optional)Rule type use getRegisteredRules() to get types
1099       * @param    string     $format        (optional)Required for extra rule data
1100       * @param    int        $howmany       (optional)How many valid elements should be in the group
1101       * @param    string     $validation    (optional)Where to perform validation: "server", "client"
1102       * @param    bool       $reset         Client-side: whether to reset the element's value to its original state if validation failed.
1103       * @since    2.5
1104       * @access   public
1105       * @throws   HTML_QuickForm_Error
1106       */
1107      function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false)
1108      {
1109          if (!$this->elementExists($group)) {
1110              return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Group '$group' does not exist in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
1111          }
1112  
1113          $groupObj =& $this->getElement($group);
1114          if (is_array($arg1)) {
1115              $required = 0;
1116              foreach ($arg1 as $elementIndex => $rules) {
1117                  $elementName = $groupObj->getElementName($elementIndex);
1118                  foreach ($rules as $rule) {
1119                      $format = (isset($rule[2])) ? $rule[2] : null;
1120                      $validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server';
1121                      $reset = isset($rule[4]) && $rule[4];
1122                      $type = $rule[1];
1123                      if (false === ($newName = $this->isRuleRegistered($type, true))) {
1124                          return self::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
1125                      } elseif (is_string($newName)) {
1126                          $type = $newName;
1127                      }
1128  
1129                      $this->_rules[$elementName][] = array(
1130                                                          'type'        => $type,
1131                                                          'format'      => $format,
1132                                                          'message'     => $rule[0],
1133                                                          'validation'  => $validation,
1134                                                          'reset'       => $reset,
1135                                                          'group'       => $group);
1136  
1137                      if ('required' == $type || 'uploadedfile' == $type) {
1138                          $groupObj->_required[] = $elementName;
1139                          $this->_required[] = $elementName;
1140                          $required++;
1141                      }
1142                  }
1143              }
1144              if ($required > 0 && count($groupObj->getElements()) == $required) {
1145                  $this->_required[] = $group;
1146              }
1147          } elseif (is_string($arg1)) {
1148              if (false === ($newName = $this->isRuleRegistered($type, true))) {
1149                  return self::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
1150              } elseif (is_string($newName)) {
1151                  $type = $newName;
1152              }
1153  
1154              // addGroupRule() should also handle <select multiple>
1155              if (is_a($groupObj, 'html_quickform_group')) {
1156                  // Radios need to be handled differently when required
1157                  if ($type == 'required' && $groupObj->getGroupType() == 'radio') {
1158                      $howmany = ($howmany == 0) ? 1 : $howmany;
1159                  } else {
1160                      $howmany = ($howmany == 0) ? count($groupObj->getElements()) : $howmany;
1161                  }
1162              }
1163  
1164              $this->_rules[$group][] = array('type'       => $type,
1165                                              'format'     => $format,
1166                                              'message'    => $arg1,
1167                                              'validation' => $validation,
1168                                              'howmany'    => $howmany,
1169                                              'reset'      => $reset);
1170              if ($type == 'required') {
1171                  $this->_required[] = $group;
1172              }
1173          }
1174      } // end func addGroupRule
1175  
1176      // }}}
1177      // {{{ addFormRule()
1178  
1179     /**
1180      * Adds a global validation rule
1181      *
1182      * This should be used when for a rule involving several fields or if
1183      * you want to use some completely custom validation for your form.
1184      * The rule function/method should return true in case of successful
1185      * validation and array('element name' => 'error') when there were errors.
1186      *
1187      * @access   public
1188      * @param    mixed   Callback, either function name or array(&$object, 'method')
1189      * @throws   HTML_QuickForm_Error
1190      */
1191      function addFormRule($rule)
1192      {
1193          if (!is_callable($rule)) {
1194              return self::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, 'Callback function does not exist in HTML_QuickForm::addFormRule()', 'HTML_QuickForm_Error', true);
1195          }
1196          $this->_formRules[] = $rule;
1197      }
1198  
1199      // }}}
1200      // {{{ applyFilter()
1201  
1202      /**
1203       * Applies a data filter for the given field(s)
1204       *
1205       * @param    mixed     $element       Form element name or array of such names
1206       * @param    mixed     $filter        Callback, either function name or array(&$object, 'method')
1207       * @since    2.0
1208       * @access   public
1209       */
1210      function applyFilter($element, $filter)
1211      {
1212          if (!is_callable($filter)) {
1213              return self::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::applyFilter()", 'HTML_QuickForm_Error', true);
1214          }
1215          if ($element == '__ALL__') {
1216              $this->_submitValues = $this->_recursiveFilter($filter, $this->_submitValues);
1217          } else {
1218              if (!is_array($element)) {
1219                  $element = array($element);
1220              }
1221              foreach ($element as $elName) {
1222                  $value = $this->getSubmitValue($elName);
1223                  if (null !== $value) {
1224                      if (false === strpos($elName, '[')) {
1225                          $this->_submitValues[$elName] = $this->_recursiveFilter($filter, $value);
1226                      } else {
1227                          $idx  = "['" . str_replace(array(']', '['), array('', "']['"), $elName) . "']";
1228                          eval("\$this->_submitValues{$idx} = \$this->_recursiveFilter(\$filter, \$value);");
1229                      }
1230                  }
1231              }
1232          }
1233      } // end func applyFilter
1234  
1235      // }}}
1236      // {{{ _recursiveFilter()
1237  
1238      /**
1239       * Recursively apply a filter function
1240       *
1241       * @param     string   $filter    filter to apply
1242       * @param     mixed    $value     submitted values
1243       * @since     2.0
1244       * @access    private
1245       * @return    cleaned values
1246       */
1247      function _recursiveFilter($filter, $value)
1248      {
1249          if (is_array($value)) {
1250              $cleanValues = array();
1251              foreach ($value as $k => $v) {
1252                  $cleanValues[$k] = $this->_recursiveFilter($filter, $v);
1253              }
1254              return $cleanValues;
1255          } else {
1256              return call_user_func($filter, $value);
1257          }
1258      } // end func _recursiveFilter
1259  
1260      // }}}
1261      // {{{ arrayMerge()
1262  
1263     /**
1264      * Merges two arrays
1265      *
1266      * Merges two array like the PHP function array_merge but recursively.
1267      * The main difference is that existing keys will not be renumbered
1268      * if they are integers.
1269      *
1270      * @access   puplic
1271      * @param    array   $a  original array
1272      * @param    array   $b  array which will be merged into first one
1273      * @return   array   merged array
1274      */
1275      static function arrayMerge($a, $b)
1276      {
1277          if (is_null($a)) {$a = array();}
1278          if (is_null($b)) {$b = array();}
1279          foreach ($b as $k => $v) {
1280              if (is_array($v)) {
1281                  if (isset($a[$k]) && !is_array($a[$k])) {
1282                      $a[$k] = $v;
1283                  } else {
1284                      if (!isset($a[$k])) {
1285                          $a[$k] = array();
1286                      }
1287                      $a[$k] = HTML_QuickForm::arrayMerge($a[$k], $v);
1288                  }
1289              } else {
1290                  $a[$k] = $v;
1291              }
1292          }
1293          return $a;
1294      } // end func arrayMerge
1295  
1296      // }}}
1297      // {{{ isTypeRegistered()
1298  
1299      /**
1300       * Returns whether or not the form element type is supported
1301       *
1302       * @param     string   $type     Form element type
1303       * @since     1.0
1304       * @access    public
1305       * @return    boolean
1306       */
1307      function isTypeRegistered($type)
1308      {
1309          return isset($GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][strtolower($type)]);
1310      } // end func isTypeRegistered
1311  
1312      // }}}
1313      // {{{ getRegisteredTypes()
1314  
1315      /**
1316       * Returns an array of registered element types
1317       *
1318       * @since     1.0
1319       * @access    public
1320       * @return    array
1321       */
1322      function getRegisteredTypes()
1323      {
1324          return array_keys($GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']);
1325      } // end func getRegisteredTypes
1326  
1327      // }}}
1328      // {{{ isRuleRegistered()
1329  
1330      /**
1331       * Returns whether or not the given rule is supported
1332       *
1333       * @param     string   $name    Validation rule name
1334       * @param     bool     Whether to automatically register subclasses of HTML_QuickForm_Rule
1335       * @since     1.0
1336       * @access    public
1337       * @return    mixed    true if previously registered, false if not, new rule name if auto-registering worked
1338       */
1339      function isRuleRegistered($name, $autoRegister = false)
1340      {
1341          if (is_scalar($name) && isset($GLOBALS['_HTML_QuickForm_registered_rules'][$name])) {
1342              return true;
1343          } elseif (!$autoRegister) {
1344              return false;
1345          }
1346          // automatically register the rule if requested
1347          include_once 'HTML/QuickForm/RuleRegistry.php';
1348          $ruleName = false;
1349          if (is_object($name) && is_a($name, 'html_quickform_rule')) {
1350              $ruleName = !empty($name->name)? $name->name: strtolower(get_class($name));
1351          } elseif (is_string($name) && class_exists($name)) {
1352              $parent = strtolower($name);
1353              do {
1354                  if ('html_quickform_rule' == strtolower($parent)) {
1355                      $ruleName = strtolower($name);
1356                      break;
1357                  }
1358              } while ($parent = get_parent_class($parent));
1359          }
1360          if ($ruleName) {
1361              $registry =& HTML_QuickForm_RuleRegistry::singleton();
1362              $registry->registerRule($ruleName, null, $name);
1363          }
1364          return $ruleName;
1365      } // end func isRuleRegistered
1366  
1367      // }}}
1368      // {{{ getRegisteredRules()
1369  
1370      /**
1371       * Returns an array of registered validation rules
1372       *
1373       * @since     1.0
1374       * @access    public
1375       * @return    array
1376       */
1377      function getRegisteredRules()
1378      {
1379          return array_keys($GLOBALS['_HTML_QuickForm_registered_rules']);
1380      } // end func getRegisteredRules
1381  
1382      // }}}
1383      // {{{ isElementRequired()
1384  
1385      /**
1386       * Returns whether or not the form element is required
1387       *
1388       * @param     string   $element     Form element name
1389       * @since     1.0
1390       * @access    public
1391       * @return    boolean
1392       */
1393      function isElementRequired($element)
1394      {
1395          return in_array($element, $this->_required, true);
1396      } // end func isElementRequired
1397  
1398      // }}}
1399      // {{{ isElementFrozen()
1400  
1401      /**
1402       * Returns whether or not the form element is frozen
1403       *
1404       * @param     string   $element     Form element name
1405       * @since     1.0
1406       * @access    public
1407       * @return    boolean
1408       */
1409      function isElementFrozen($element)
1410      {
1411           if (isset($this->_elementIndex[$element])) {
1412               return $this->_elements[$this->_elementIndex[$element]]->isFrozen();
1413           }
1414           return false;
1415      } // end func isElementFrozen
1416  
1417      // }}}
1418      // {{{ setJsWarnings()
1419  
1420      /**
1421       * Sets JavaScript warning messages
1422       *
1423       * @param     string   $pref        Prefix warning
1424       * @param     string   $post        Postfix warning
1425       * @since     1.1
1426       * @access    public
1427       * @return    void
1428       */
1429      function setJsWarnings($pref, $post)
1430      {
1431          $this->_jsPrefix = $pref;
1432          $this->_jsPostfix = $post;
1433      } // end func setJsWarnings
1434  
1435      // }}}
1436      // {{{ setRequiredNote()
1437  
1438      /**
1439       * Sets required-note
1440       *
1441       * @param     string   $note        Message indicating some elements are required
1442       * @since     1.1
1443       * @access    public
1444       * @return    void
1445       */
1446      function setRequiredNote($note)
1447      {
1448          $this->_requiredNote = $note;
1449      } // end func setRequiredNote
1450  
1451      // }}}
1452      // {{{ getRequiredNote()
1453  
1454      /**
1455       * Returns the required note
1456       *
1457       * @since     2.0
1458       * @access    public
1459       * @return    string
1460       */
1461      function getRequiredNote()
1462      {
1463          return $this->_requiredNote;
1464      } // end func getRequiredNote
1465  
1466      // }}}
1467      // {{{ validate()
1468  
1469      /**
1470       * Performs the server side validation
1471       * @access    public
1472       * @since     1.0
1473       * @return    boolean   true if no error found
1474       */
1475      function validate()
1476      {
1477          if (count($this->_rules) == 0 && count($this->_formRules) == 0 &&
1478              $this->isSubmitted()) {
1479              return (0 == count($this->_errors));
1480          } elseif (!$this->isSubmitted()) {
1481              return false;
1482          }
1483  
1484          include_once('HTML/QuickForm/RuleRegistry.php');
1485          $registry =& HTML_QuickForm_RuleRegistry::singleton();
1486  
1487          foreach ($this->_rules as $target => $rules) {
1488              $submitValue = $this->getSubmitValue($target);
1489  
1490              foreach ($rules as $rule) {
1491                  if ((isset($rule['group']) && isset($this->_errors[$rule['group']])) ||
1492                       isset($this->_errors[$target])) {
1493                      continue 2;
1494                  }
1495                  // If element is not required and is empty, we shouldn't validate it
1496                  if (!$this->isElementRequired($target)) {
1497                      if (!isset($submitValue) || '' == $submitValue) {
1498                          continue 2;
1499                      // Fix for bug #3501: we shouldn't validate not uploaded files, either.
1500                      // Unfortunately, we can't just use $element->isUploadedFile() since
1501                      // the element in question can be buried in group. Thus this hack.
1502                      } elseif (is_array($submitValue)) {
1503                          if (false === ($pos = strpos($target, '['))) {
1504                              $isUpload = !empty($this->_submitFiles[$target]);
1505                          } else {
1506                              $base = substr($target, 0, $pos);
1507                              $idx  = "['" . str_replace(array(']', '['), array('', "']['"), substr($target, $pos + 1, -1)) . "']";
1508                              eval("\$isUpload = isset(\$this->_submitFiles['{$base}']['name']{$idx});");
1509                          }
1510                          if ($isUpload && (!isset($submitValue['error']) || 0 != $submitValue['error'])) {
1511                              continue 2;
1512                          }
1513                      }
1514                  }
1515                  if (isset($rule['dependent']) && is_array($rule['dependent'])) {
1516                      $values = array($submitValue);
1517                      foreach ($rule['dependent'] as $elName) {
1518                          $values[] = $this->getSubmitValue($elName);
1519                      }
1520                      $result = $registry->validate($rule['type'], $values, $rule['format'], true);
1521                  } elseif (is_array($submitValue) && !isset($rule['howmany'])) {
1522                      $result = $registry->validate($rule['type'], $submitValue, $rule['format'], true);
1523                  } else {
1524                      $result = $registry->validate($rule['type'], $submitValue, $rule['format'], false);
1525                  }
1526  
1527                  if (!$result || (!empty($rule['howmany']) && $rule['howmany'] > (int)$result)) {
1528                      if (isset($rule['group'])) {
1529                          $this->_errors[$rule['group']] = $rule['message'];
1530                      } else {
1531                          $this->_errors[$target] = $rule['message'];
1532                      }
1533                  }
1534              }
1535          }
1536  
1537          // process the global rules now
1538          foreach ($this->_formRules as $rule) {
1539              if (true !== ($res = call_user_func($rule, $this->_submitValues, $this->_submitFiles))) {
1540                  if (is_array($res)) {
1541                      $this->_errors += $res;
1542                  } else {
1543                      return self::raiseError(null, QUICKFORM_ERROR, null, E_USER_WARNING, 'Form rule callback returned invalid value in HTML_QuickForm::validate()', 'HTML_QuickForm_Error', true);
1544                  }
1545              }
1546          }
1547  
1548          return (0 == count($this->_errors));
1549      } // end func validate
1550  
1551      // }}}
1552      // {{{ freeze()
1553  
1554      /**
1555       * Displays elements without HTML input tags
1556       *
1557       * @param    mixed   $elementList       array or string of element(s) to be frozen
1558       * @since     1.0
1559       * @access   public
1560       * @throws   HTML_QuickForm_Error
1561       */
1562      function freeze($elementList=null)
1563      {
1564          if (!isset($elementList)) {
1565              $this->_freezeAll = true;
1566              $elementList = array();
1567          } else {
1568              if (!is_array($elementList)) {
1569                  $elementList = preg_split('/[ ]*,[ ]*/', $elementList);
1570              }
1571              $elementList = array_flip($elementList);
1572          }
1573  
1574          foreach (array_keys($this->_elements) as $key) {
1575              $name = $this->_elements[$key]->getName();
1576              if ($this->_freezeAll || isset($elementList[$name])) {
1577                  $this->_elements[$key]->freeze();
1578                  unset($elementList[$name]);
1579              }
1580          }
1581  
1582          if (!empty($elementList)) {
1583              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);
1584          }
1585          return true;
1586      } // end func freeze
1587  
1588      // }}}
1589      // {{{ isFrozen()
1590  
1591      /**
1592       * Returns whether or not the whole form is frozen
1593       *
1594       * @since     3.0
1595       * @access    public
1596       * @return    boolean
1597       */
1598      function isFrozen()
1599      {
1600           return $this->_freezeAll;
1601      } // end func isFrozen
1602  
1603      // }}}
1604      // {{{ process()
1605  
1606      /**
1607       * Performs the form data processing
1608       *
1609       * @param    mixed     $callback        Callback, either function name or array(&$object, 'method')
1610       * @param    bool      $mergeFiles      Whether uploaded files should be processed too
1611       * @since    1.0
1612       * @access   public
1613       * @throws   HTML_QuickForm_Error
1614       */
1615      function process($callback, $mergeFiles = true)
1616      {
1617          if (!is_callable($callback)) {
1618              return self::raiseError(null, QUICKFORM_INVALID_PROCESS, null, E_USER_WARNING, "Callback function does not exist in QuickForm::process()", 'HTML_QuickForm_Error', true);
1619          }
1620          $values = ($mergeFiles === true) ? HTML_QuickForm::arrayMerge($this->_submitValues, $this->_submitFiles) : $this->_submitValues;
1621          return call_user_func($callback, $values);
1622      } // end func process
1623  
1624      // }}}
1625      // {{{ accept()
1626  
1627     /**
1628      * Accepts a renderer
1629      *
1630      * @param object     An HTML_QuickForm_Renderer object
1631      * @since 3.0
1632      * @access public
1633      * @return void
1634      */
1635      function accept(&$renderer)
1636      {
1637          $renderer->startForm($this);
1638          foreach (array_keys($this->_elements) as $key) {
1639              $element =& $this->_elements[$key];
1640              $elementName = $element->getName();
1641              $required    = ($this->isElementRequired($elementName) && !$element->isFrozen());
1642              $error       = $this->getElementError($elementName);
1643              $element->accept($renderer, $required, $error);
1644          }
1645          $renderer->finishForm($this);
1646      } // end func accept
1647  
1648      // }}}
1649      // {{{ defaultRenderer()
1650  
1651     /**
1652      * Returns a reference to default renderer object
1653      *
1654      * @access public
1655      * @since 3.0
1656      * @return object a default renderer object
1657      */
1658      function &defaultRenderer()
1659      {
1660          if (!isset($GLOBALS['_HTML_QuickForm_default_renderer'])) {
1661              include_once('HTML/QuickForm/Renderer/Default.php');
1662              $GLOBALS['_HTML_QuickForm_default_renderer'] = new HTML_QuickForm_Renderer_Default(); //Moodle: PHP 5.3 compatibility
1663          }
1664          return $GLOBALS['_HTML_QuickForm_default_renderer'];
1665      } // end func defaultRenderer
1666  
1667      // }}}
1668      // {{{ toHtml ()
1669  
1670      /**
1671       * Returns an HTML version of the form
1672       *
1673       * @param string $in_data (optional) Any extra data to insert right
1674       *               before form is rendered.  Useful when using templates.
1675       *
1676       * @return   string     Html version of the form
1677       * @since     1.0
1678       * @access   public
1679       */
1680      function toHtml ($in_data = null)
1681      {
1682          if (!is_null($in_data)) {
1683              $this->addElement('html', $in_data);
1684          }
1685          $renderer =& $this->defaultRenderer();
1686          $this->accept($renderer);
1687          return $renderer->toHtml();
1688      } // end func toHtml
1689  
1690      // }}}
1691      // {{{ getValidationScript()
1692  
1693      /**
1694       * Returns the client side validation script
1695       *
1696       * @since     2.0
1697       * @access    public
1698       * @return    string    Javascript to perform validation, empty string if no 'client' rules were added
1699       */
1700      function getValidationScript()
1701      {
1702          if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) {
1703              return '';
1704          }
1705  
1706          include_once('HTML/QuickForm/RuleRegistry.php');
1707          $registry =& HTML_QuickForm_RuleRegistry::singleton();
1708          $test = array();
1709          $js_escape = array(
1710              "\r"    => '\r',
1711              "\n"    => '\n',
1712              "\t"    => '\t',
1713              "'"     => "\\'",
1714              '"'     => '\"',
1715              '\\'    => '\\\\'
1716          );
1717  
1718          foreach ($this->_rules as $elementName => $rules) {
1719              foreach ($rules as $rule) {
1720                  if ('client' == $rule['validation']) {
1721                      unset($element);
1722  
1723                      $dependent  = isset($rule['dependent']) && is_array($rule['dependent']);
1724                      $rule['message'] = strtr($rule['message'], $js_escape);
1725  
1726                      if (isset($rule['group'])) {
1727                          $group    =& $this->getElement($rule['group']);
1728                          // No JavaScript validation for frozen elements
1729                          if ($group->isFrozen()) {
1730                              continue 2;
1731                          }
1732                          $elements =& $group->getElements();
1733                          foreach (array_keys($elements) as $key) {
1734                              if ($elementName == $group->getElementName($key)) {
1735                                  $element =& $elements[$key];
1736                                  break;
1737                              }
1738                          }
1739                      } elseif ($dependent) {
1740                          $element   =  array();
1741                          $element[] =& $this->getElement($elementName);
1742                          foreach ($rule['dependent'] as $elName) {
1743                              $element[] =& $this->getElement($elName);
1744                          }
1745                      } else {
1746                          $element =& $this->getElement($elementName);
1747                      }
1748                      // No JavaScript validation for frozen elements
1749                      if (is_object($element) && $element->isFrozen()) {
1750                          continue 2;
1751                      } elseif (is_array($element)) {
1752                          foreach (array_keys($element) as $key) {
1753                              if ($element[$key]->isFrozen()) {
1754                                  continue 3;
1755                              }
1756                          }
1757                      }
1758  
1759                      $test[] = $registry->getValidationScript($element, $elementName, $rule);
1760                  }
1761              }
1762          }
1763          if (count($test) > 0) {
1764              return
1765                  "\n<script type=\"text/javascript\">\n" .
1766                  "//<![CDATA[\n" .
1767                  "function validate_" . $this->_attributes['id'] . "(frm) {\n" .
1768                  "  var value = '';\n" .
1769                  "  var errFlag = new Array();\n" .
1770                  "  var _qfGroups = {};\n" .
1771                  "  _qfMsg = '';\n\n" .
1772                  join("\n", $test) .
1773                  "\n  if (_qfMsg != '') {\n" .
1774                  "    _qfMsg = '" . strtr($this->_jsPrefix, $js_escape) . "' + _qfMsg;\n" .
1775                  "    _qfMsg = _qfMsg + '\\n" . strtr($this->_jsPostfix, $js_escape) . "';\n" .
1776                  "    alert(_qfMsg);\n" .
1777                  "    return false;\n" .
1778                  "  }\n" .
1779                  "  return true;\n" .
1780                  "}\n" .
1781                  "//]]>\n" .
1782                  "</script>";
1783          }
1784          return '';
1785      } // end func getValidationScript
1786  
1787      // }}}
1788      // {{{ getSubmitValues()
1789  
1790      /**
1791       * Returns the values submitted by the form
1792       *
1793       * @since     2.0
1794       * @access    public
1795       * @param     bool      Whether uploaded files should be returned too
1796       * @return    array
1797       */
1798      function getSubmitValues($mergeFiles = false)
1799      {
1800          return $mergeFiles? HTML_QuickForm::arrayMerge($this->_submitValues, $this->_submitFiles): $this->_submitValues;
1801      } // end func getSubmitValues
1802  
1803      // }}}
1804      // {{{ toArray()
1805  
1806      /**
1807       * Returns the form's contents in an array.
1808       *
1809       * The description of the array structure is in HTML_QuickForm_Renderer_Array docs
1810       *
1811       * @since     2.0
1812       * @access    public
1813       * @param     bool      Whether to collect hidden elements (passed to the Renderer's constructor)
1814       * @return    array of form contents
1815       */
1816      function toArray($collectHidden = false)
1817      {
1818          include_once 'HTML/QuickForm/Renderer/Array.php';
1819          $renderer = new HTML_QuickForm_Renderer_Array($collectHidden); //Moodle: PHP 5.3 compatibility
1820          $this->accept($renderer);
1821          return $renderer->toArray();
1822       } // end func toArray
1823  
1824      // }}}
1825      // {{{ exportValue()
1826  
1827      /**
1828       * Returns a 'safe' element's value
1829       *
1830       * This method first tries to find a cleaned-up submitted value,
1831       * it will return a value set by setValue()/setDefaults()/setConstants()
1832       * if submitted value does not exist for the given element.
1833       *
1834       * @param  string   Name of an element
1835       * @access public
1836       * @return mixed
1837       */
1838      function exportValue($element)
1839      {
1840          if (!isset($this->_elementIndex[$element])) {
1841              return self::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true);
1842          }
1843          $value = $this->_elements[$this->_elementIndex[$element]]->exportValue($this->_submitValues, false);
1844          if (isset($this->_duplicateIndex[$element])) {
1845              foreach ($this->_duplicateIndex[$element] as $index) {
1846                  if (null !== ($v = $this->_elements[$index]->exportValue($this->_submitValues, false))) {
1847                      if (is_array($value)) {
1848                          $value[] = $v;
1849                      } else {
1850                          $value = (null === $value)? $v: array($value, $v);
1851                      }
1852                  }
1853              }
1854          }
1855          return $value;
1856      }
1857  
1858      // }}}
1859      // {{{ exportValues()
1860  
1861      /**
1862       * Returns 'safe' elements' values
1863       *
1864       * Unlike getSubmitValues(), this will return only the values
1865       * corresponding to the elements present in the form.
1866       *
1867       * @param   mixed   Array/string of element names, whose values we want. If not set then return all elements.
1868       * @access  public
1869       * @return  array   An assoc array of elements' values
1870       * @throws  HTML_QuickForm_Error
1871       */
1872      function exportValues($elementList = null)
1873      {
1874          $values = array();
1875          if (null === $elementList) {
1876              // iterate over all elements, calling their exportValue() methods
1877              foreach (array_keys($this->_elements) as $key) {
1878                  $value = $this->_elements[$key]->exportValue($this->_submitValues, true);
1879                  if (is_array($value)) {
1880                      // This shit throws a bogus warning in PHP 4.3.x
1881                      $values = HTML_QuickForm::arrayMerge($values, $value);
1882                  }
1883              }
1884          } else {
1885              if (!is_array($elementList)) {
1886                  $elementList = array_map('trim', explode(',', $elementList));
1887              }
1888              foreach ($elementList as $elementName) {
1889                  $value = $this->exportValue($elementName);
1890                  $pear = new PEAR();
1891                  if ($pear->isError($value)) {
1892                      return $value;
1893                  }
1894                  $values[$elementName] = $value;
1895              }
1896          }
1897          return $values;
1898      }
1899  
1900      // }}}
1901      // {{{ isSubmitted()
1902  
1903     /**
1904      * Tells whether the form was already submitted
1905      *
1906      * This is useful since the _submitFiles and _submitValues arrays
1907      * may be completely empty after the trackSubmit value is removed.
1908      *
1909      * @access public
1910      * @return bool
1911      */
1912      function isSubmitted()
1913      {
1914          return $this->_flagSubmitted;
1915      }
1916  
1917  
1918      // }}}
1919      // {{{ isError()
1920  
1921      /**
1922       * Tell whether a result from a QuickForm method is an error (an instance of HTML_QuickForm_Error)
1923       *
1924       * @access public
1925       * @param mixed     result code
1926       * @return bool     whether $value is an error
1927       */
1928      static function isError($value)
1929      {
1930          return (is_object($value) && is_a($value, 'html_quickform_error'));
1931      } // end func isError
1932  
1933      // }}}
1934      // {{{ errorMessage()
1935  
1936      /**
1937       * Return a textual error message for an QuickForm error code
1938       *
1939       * @access  public
1940       * @param   int     error code
1941       * @return  string  error message
1942       */
1943      static function errorMessage($value)
1944      {
1945          // make the variable static so that it only has to do the defining on the first call
1946          static $errorMessages;
1947  
1948          // define the varies error messages
1949          if (!isset($errorMessages)) {
1950              $errorMessages = array(
1951                  QUICKFORM_OK                    => 'no error',
1952                  QUICKFORM_ERROR                 => 'unknown error',
1953                  QUICKFORM_INVALID_RULE          => 'the rule does not exist as a registered rule',
1954                  QUICKFORM_NONEXIST_ELEMENT      => 'nonexistent html element',
1955                  QUICKFORM_INVALID_FILTER        => 'invalid filter',
1956                  QUICKFORM_UNREGISTERED_ELEMENT  => 'unregistered element',
1957                  QUICKFORM_INVALID_ELEMENT_NAME  => 'element already exists',
1958                  QUICKFORM_INVALID_PROCESS       => 'process callback does not exist',
1959                  QUICKFORM_DEPRECATED            => 'method is deprecated',
1960                  QUICKFORM_INVALID_DATASOURCE    => 'datasource is not an object'
1961              );
1962          }
1963  
1964          // If this is an error object, then grab the corresponding error code
1965          if (HTML_QuickForm::isError($value)) {
1966              $value = $value->getCode();
1967          }
1968  
1969          // return the textual error message corresponding to the code
1970          return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[QUICKFORM_ERROR];
1971      } // end func errorMessage
1972  
1973      // }}}
1974  } // end class HTML_QuickForm
1975  
1976  class HTML_QuickForm_Error extends PEAR_Error {
1977  
1978      // {{{ properties
1979  
1980      /**
1981      * Prefix for all error messages
1982      * @var string
1983      */
1984      var $error_message_prefix = 'QuickForm Error: ';
1985  
1986      // }}}
1987      // {{{ constructor
1988  
1989      /**
1990      * Creates a quickform error object, extending the PEAR_Error class
1991      *
1992      * @param int   $code the error code
1993      * @param int   $mode the reaction to the error, either return, die or trigger/callback
1994      * @param int   $level intensity of the error (PHP error code)
1995      * @param mixed $debuginfo any information that can inform user as to nature of the error
1996      */
1997      public function __construct($code = QUICKFORM_ERROR, $mode = PEAR_ERROR_RETURN,
1998                           $level = E_USER_NOTICE, $debuginfo = null)
1999      {
2000          if (is_int($code)) {
2001              parent::__construct(HTML_QuickForm::errorMessage($code), $code, $mode, $level, $debuginfo);
2002          } else {
2003              parent::__construct("Invalid error code: $code", QUICKFORM_ERROR, $mode, $level, $debuginfo);
2004          }
2005      }
2006  
2007      /**
2008       * Old syntax of class constructor. Deprecated in PHP7.
2009       *
2010       * @deprecated since Moodle 3.1
2011       */
2012      public function HTML_QuickForm_Error($code = QUICKFORM_ERROR, $mode = PEAR_ERROR_RETURN,
2013                           $level = E_USER_NOTICE, $debuginfo = null) {
2014          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
2015          self::__construct($code, $mode, $level, $debuginfo);
2016      }
2017  
2018      // }}}
2019  } // end class HTML_QuickForm_Error
2020  ?>