Completed
Push — master ( dca322...47e653 )
by Julito
08:54
created

HTML_QuickForm   F

Complexity

Total Complexity 298

Size/Duplication

Total Lines 1828
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 673
dl 0
loc 1828
rs 1.927
c 0
b 0
f 0
wmc 298

How to fix   Complexity   

Complex Class

Complex classes like HTML_QuickForm often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HTML_QuickForm, and based on these observations, apply Extract Interface, too.

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