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