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