1<?php
2/**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category   Zend
16 * @package    Zend_Form
17 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
18 * @license    http://framework.zend.com/license/new-bsd     New BSD License
19 */
20
21/** @see Zend_Validate_Interface */
22
23/**
24 * Zend_Form
25 *
26 * @category   Zend
27 * @package    Zend_Form
28 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
29 * @license    http://framework.zend.com/license/new-bsd     New BSD License
30 * @version    $Id$
31 */
32class Zend_Form implements Iterator, Countable, Zend_Validate_Interface
33{
34    /**#@+
35     * Plugin loader type constants
36     */
37    const DECORATOR = 'DECORATOR';
38    const ELEMENT = 'ELEMENT';
39    /**#@-*/
40
41    /**#@+
42     * Method type constants
43     */
44    const METHOD_DELETE = 'delete';
45    const METHOD_GET    = 'get';
46    const METHOD_POST   = 'post';
47    const METHOD_PUT    = 'put';
48    /**#@-*/
49
50    /**#@+
51     * Encoding type constants
52     */
53    const ENCTYPE_URLENCODED = 'application/x-www-form-urlencoded';
54    const ENCTYPE_MULTIPART  = 'multipart/form-data';
55    /**#@-*/
56
57    /**
58     * Form metadata and attributes
59     * @var array
60     */
61    protected $_attribs = array();
62
63    /**
64     * Decorators for rendering
65     * @var array
66     */
67    protected $_decorators = array();
68
69    /**
70     * Default display group class
71     * @var string
72     */
73    protected $_defaultDisplayGroupClass = 'Zend_Form_DisplayGroup';
74
75    /**
76     * Form description
77     * @var string
78     */
79    protected $_description;
80
81    /**
82     * Should we disable loading the default decorators?
83     * @var bool
84     */
85    protected $_disableLoadDefaultDecorators = false;
86
87    /**
88     * Display group prefix paths
89     * @var array
90     */
91    protected $_displayGroupPrefixPaths = array();
92
93    /**
94     * Groups of elements grouped for display purposes
95     * @var array
96     */
97    protected $_displayGroups = array();
98
99    /**
100     * Global decorators to apply to all elements
101     * @var null|array
102     */
103    protected $_elementDecorators;
104
105    /**
106     * Prefix paths to use when creating elements
107     * @var array
108     */
109    protected $_elementPrefixPaths = array();
110
111    /**
112     * Form elements
113     * @var array
114     */
115    protected $_elements = array();
116
117    /**
118     * Array to which elements belong (if any)
119     * @var string
120     */
121    protected $_elementsBelongTo;
122
123    /**
124     * Custom form-level error messages
125     * @var array
126     */
127    protected $_errorMessages = array();
128
129    /**
130     * Are there errors in the form?
131     * @var bool
132     */
133    protected $_errorsExist = false;
134
135    /**
136     * Has the form been manually flagged as an error?
137     * @var bool
138     */
139    protected $_errorsForced = false;
140
141    /**
142     * Form order
143     * @var int|null
144     */
145    protected $_formOrder;
146
147    /**
148     * Whether or not form elements are members of an array
149     * @var bool
150     */
151    protected $_isArray = false;
152
153    /**
154     * Form legend
155     * @var string
156     */
157    protected $_legend;
158
159    /**
160     * Plugin loaders
161     * @var array
162     */
163    protected $_loaders = array();
164
165    /**
166     * Allowed form methods
167     * @var array
168     */
169    protected $_methods = array('delete', 'get', 'post', 'put');
170
171    /**
172     * Order in which to display and iterate elements
173     * @var array
174     */
175    protected $_order = array();
176
177    /**
178     * Whether internal order has been updated or not
179     * @var bool
180     */
181    protected $_orderUpdated = false;
182
183    /**
184     * Sub form prefix paths
185     * @var array
186     */
187    protected $_subFormPrefixPaths = array();
188
189    /**
190     * Sub forms
191     * @var array
192     */
193    protected $_subForms = array();
194
195    /**
196     * @var Zend_Translate
197     */
198    protected $_translator;
199
200    /**
201     * Global default translation adapter
202     * @var Zend_Translate
203     */
204    protected static $_translatorDefault;
205
206    /**
207     * is the translator disabled?
208     * @var bool
209     */
210    protected $_translatorDisabled = false;
211
212    /**
213     * @var Zend_View_Interface
214     */
215    protected $_view;
216
217    /**
218     * @var bool
219     */
220    protected $_isRendered = false;
221
222    /**
223     * Constructor
224     *
225     * Registers form view helper as decorator
226     *
227     * @param mixed $options
228     */
229    public function __construct($options = null)
230    {
231        if (is_array($options)) {
232            $this->setOptions($options);
233        } elseif ($options instanceof Zend_Config) {
234            $this->setConfig($options);
235        }
236
237        // Extensions...
238        $this->init();
239
240        $this->loadDefaultDecorators();
241    }
242
243    /**
244     * Clone form object and all children
245     *
246     * @return void
247     */
248    public function __clone()
249    {
250        $elements = array();
251        foreach ($this->getElements() as $name => $element) {
252            $elements[] = clone $element;
253        }
254        $this->setElements($elements);
255
256        $subForms = array();
257        foreach ($this->getSubForms() as $name => $subForm) {
258            $subForms[$name] = clone $subForm;
259        }
260        $this->setSubForms($subForms);
261
262        $displayGroups = array();
263        foreach ($this->_displayGroups as $group)  {
264            /** @var Zend_Form_DisplayGroup $clone */
265            $clone    = clone $group;
266            $elements = array();
267            foreach ($clone->getElements() as $name => $e) {
268                $elements[] = $this->getElement($name);
269            }
270            $clone->setElements($elements);
271            $displayGroups[] = $clone;
272        }
273        $this->setDisplayGroups($displayGroups);
274    }
275
276    /**
277     * Reset values of form
278     *
279     * @return Zend_Form
280     */
281    public function reset()
282    {
283        /** @var Zend_Form_Element $element */
284        foreach ($this->getElements() as $element) {
285            $element->setValue(null);
286        }
287        /** @var Zend_Form_SubForm $subForm */
288        foreach ($this->getSubForms() as $subForm) {
289            $subForm->reset();
290        }
291
292        return $this;
293    }
294
295    /**
296     * Initialize form (used by extending classes)
297     *
298     * @return void
299     */
300    public function init()
301    {
302    }
303
304    /**
305     * Set form state from options array
306     *
307     * @param  array $options
308     * @return Zend_Form
309     */
310    public function setOptions(array $options)
311    {
312        if (isset($options['prefixPath'])) {
313            $this->addPrefixPaths($options['prefixPath']);
314            unset($options['prefixPath']);
315        }
316
317        if (isset($options['elementPrefixPath'])) {
318            $this->addElementPrefixPaths($options['elementPrefixPath']);
319            unset($options['elementPrefixPath']);
320        }
321
322        if (isset($options['displayGroupPrefixPath'])) {
323            $this->addDisplayGroupPrefixPaths($options['displayGroupPrefixPath']);
324            unset($options['displayGroupPrefixPath']);
325        }
326
327        if (isset($options['elementDecorators'])) {
328            $this->_elementDecorators = $options['elementDecorators'];
329            unset($options['elementDecorators']);
330        }
331
332        if (isset($options['elements'])) {
333            $this->setElements($options['elements']);
334            unset($options['elements']);
335        }
336
337        if (isset($options['defaultDisplayGroupClass'])) {
338            $this->setDefaultDisplayGroupClass($options['defaultDisplayGroupClass']);
339            unset($options['defaultDisplayGroupClass']);
340        }
341
342        if (isset($options['displayGroupDecorators'])) {
343            $displayGroupDecorators = $options['displayGroupDecorators'];
344            unset($options['displayGroupDecorators']);
345        }
346
347        if (isset($options['elementsBelongTo'])) {
348            $elementsBelongTo = $options['elementsBelongTo'];
349            unset($options['elementsBelongTo']);
350        }
351
352        if (isset($options['attribs'])) {
353            $this->addAttribs($options['attribs']);
354            unset($options['attribs']);
355        }
356
357        if (isset($options['subForms'])) {
358            $this->addSubForms($options['subForms']);
359            unset($options['subForms']);
360        }
361
362        $forbidden = array(
363            'Options', 'Config', 'PluginLoader', 'SubForms', 'Translator',
364            'Attrib', 'Default',
365        );
366
367        foreach ($options as $key => $value) {
368            $normalized = ucfirst($key);
369            if (in_array($normalized, $forbidden)) {
370                continue;
371            }
372
373            $method = 'set' . $normalized;
374            if (method_exists($this, $method)) {
375                if($normalized == 'View' && !($value instanceof Zend_View_Interface)) {
376                    continue;
377                }
378                $this->$method($value);
379            } else {
380                $this->setAttrib($key, $value);
381            }
382        }
383
384        if (isset($displayGroupDecorators)) {
385            $this->setDisplayGroupDecorators($displayGroupDecorators);
386        }
387
388        if (isset($elementsBelongTo)) {
389            $this->setElementsBelongTo($elementsBelongTo);
390        }
391
392        return $this;
393    }
394
395    /**
396     * Set form state from config object
397     *
398     * @param  Zend_Config $config
399     * @return Zend_Form
400     */
401    public function setConfig(Zend_Config $config)
402    {
403        return $this->setOptions($config->toArray());
404    }
405
406
407    // Loaders
408
409    /**
410     * Set plugin loaders for use with decorators and elements
411     *
412     * @param  Zend_Loader_PluginLoader_Interface $loader
413     * @param  string $type 'decorator' or 'element'
414     * @return Zend_Form
415     * @throws Zend_Form_Exception on invalid type
416     */
417    public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type = null)
418    {
419        $type = strtoupper($type);
420        switch ($type) {
421            case self::DECORATOR:
422            case self::ELEMENT:
423                $this->_loaders[$type] = $loader;
424                return $this;
425            default:
426                throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type));
427        }
428    }
429
430    /**
431     * Retrieve plugin loader for given type
432     *
433     * $type may be one of:
434     * - decorator
435     * - element
436     *
437     * If a plugin loader does not exist for the given type, defaults are
438     * created.
439     *
440     * @param  string $type
441     * @return Zend_Loader_PluginLoader_Interface
442     * @throws Zend_Form_Exception
443     */
444    public function getPluginLoader($type = null)
445    {
446        $type = strtoupper($type);
447        if (!isset($this->_loaders[$type])) {
448            switch ($type) {
449                case self::DECORATOR:
450                    $prefixSegment = 'Form_Decorator';
451                    $pathSegment   = 'Form/Decorator';
452                    break;
453                case self::ELEMENT:
454                    $prefixSegment = 'Form_Element';
455                    $pathSegment   = 'Form/Element';
456                    break;
457                default:
458                    throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
459            }
460
461            $this->_loaders[$type] = new Zend_Loader_PluginLoader(
462                array('Zend_' . $prefixSegment . '_' => 'Zend/' . $pathSegment . '/')
463            );
464        }
465
466        return $this->_loaders[$type];
467    }
468
469    /**
470     * Add prefix path for plugin loader
471     *
472     * If no $type specified, assumes it is a base path for both filters and
473     * validators, and sets each according to the following rules:
474     * - decorators: $prefix = $prefix . '_Decorator'
475     * - elements: $prefix = $prefix . '_Element'
476     *
477     * Otherwise, the path prefix is set on the appropriate plugin loader.
478     *
479     * If $type is 'decorator', sets the path in the decorator plugin loader
480     * for all elements. Additionally, if no $type is provided,
481     * the prefix and path is added to both decorator and element
482     * plugin loader with following settings:
483     * $prefix . '_Decorator', $path . '/Decorator/'
484     * $prefix . '_Element', $path . '/Element/'
485     *
486     * @param  string $prefix
487     * @param  string $path
488     * @param  string $type
489     * @return Zend_Form
490     * @throws Zend_Form_Exception for invalid type
491     */
492    public function addPrefixPath($prefix, $path, $type = null)
493    {
494        $type = strtoupper($type);
495        switch ($type) {
496            case self::DECORATOR:
497            case self::ELEMENT:
498                $loader = $this->getPluginLoader($type);
499                $loader->addPrefixPath($prefix, $path);
500                return $this;
501            case null:
502                $nsSeparator = (false !== strpos($prefix, '\\'))?'\\':'_';
503                $prefix = rtrim($prefix, $nsSeparator);
504                $path   = rtrim($path, DIRECTORY_SEPARATOR);
505                foreach (array(self::DECORATOR, self::ELEMENT) as $type) {
506                    $cType        = ucfirst(strtolower($type));
507                    $pluginPath   = $path . DIRECTORY_SEPARATOR . $cType . DIRECTORY_SEPARATOR;
508                    $pluginPrefix = $prefix . $nsSeparator . $cType;
509                    $loader       = $this->getPluginLoader($type);
510                    $loader->addPrefixPath($pluginPrefix, $pluginPath);
511                }
512                return $this;
513            default:
514                throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
515        }
516    }
517
518    /**
519     * Add many prefix paths at once
520     *
521     * @param  array $spec
522     * @return Zend_Form
523     */
524    public function addPrefixPaths(array $spec)
525    {
526        if (isset($spec['prefix']) && isset($spec['path'])) {
527            return $this->addPrefixPath($spec['prefix'], $spec['path']);
528        }
529        foreach ($spec as $type => $paths) {
530            if (is_numeric($type) && is_array($paths)) {
531                $type = null;
532                if (isset($paths['prefix']) && isset($paths['path'])) {
533                    if (isset($paths['type'])) {
534                        $type = $paths['type'];
535                    }
536                    $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
537                }
538            } elseif (!is_numeric($type)) {
539                if (!isset($paths['prefix']) || !isset($paths['path'])) {
540                    continue;
541                }
542                $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
543            }
544        }
545        return $this;
546    }
547
548    /**
549     * Add prefix path for all elements
550     *
551     * @param  string $prefix
552     * @param  string $path
553     * @param  string $type
554     * @return Zend_Form
555     */
556    public function addElementPrefixPath($prefix, $path, $type = null)
557    {
558        $this->_elementPrefixPaths[] = array(
559            'prefix' => $prefix,
560            'path'   => $path,
561            'type'   => $type,
562        );
563
564        /** @var Zend_Form_Element $element */
565        foreach ($this->getElements() as $element) {
566            $element->addPrefixPath($prefix, $path, $type);
567        }
568
569        /** @var Zend_Form_SubForm $subForm */
570        foreach ($this->getSubForms() as $subForm) {
571            $subForm->addElementPrefixPath($prefix, $path, $type);
572        }
573
574        return $this;
575    }
576
577    /**
578     * Add prefix paths for all elements
579     *
580     * @param  array $spec
581     * @return Zend_Form
582     */
583    public function addElementPrefixPaths(array $spec)
584    {
585        $this->_elementPrefixPaths = $this->_elementPrefixPaths + $spec;
586
587        /** @var Zend_Form_Element $element */
588        foreach ($this->getElements() as $element) {
589            $element->addPrefixPaths($spec);
590        }
591
592        return $this;
593    }
594
595    /**
596     * Add prefix path for all display groups
597     *
598     * @param  string $prefix
599     * @param  string $path
600     * @return Zend_Form
601     */
602    public function addDisplayGroupPrefixPath($prefix, $path)
603    {
604        $this->_displayGroupPrefixPaths[] = array(
605            'prefix' => $prefix,
606            'path'   => $path,
607        );
608
609        /** @var Zend_Form_DisplayGroup $group */
610        foreach ($this->getDisplayGroups() as $group) {
611            $group->addPrefixPath($prefix, $path);
612        }
613
614        return $this;
615    }
616
617    /**
618     * Add multiple display group prefix paths at once
619     *
620     * @param  array $spec
621     * @return Zend_Form
622     */
623    public function addDisplayGroupPrefixPaths(array $spec)
624    {
625        foreach ($spec as $key => $value) {
626            if (is_string($value) && !is_numeric($key)) {
627                $this->addDisplayGroupPrefixPath($key, $value);
628                continue;
629            }
630
631            if (is_string($value) && is_numeric($key)) {
632                continue;
633            }
634
635            if (is_array($value)) {
636                $count = count($value);
637                if (array_keys($value) === range(0, $count - 1)) {
638                    if ($count < 2) {
639                        continue;
640                    }
641                    $prefix = array_shift($value);
642                    $path   = array_shift($value);
643                    $this->addDisplayGroupPrefixPath($prefix, $path);
644                    continue;
645                }
646                if (array_key_exists('prefix', $value) && array_key_exists('path', $value)) {
647                    $this->addDisplayGroupPrefixPath($value['prefix'], $value['path']);
648                }
649            }
650        }
651        return $this;
652    }
653
654    // Form metadata:
655
656    /**
657     * Set form attribute
658     *
659     * @param  string $key
660     * @param  mixed $value
661     * @return Zend_Form
662     */
663    public function setAttrib($key, $value)
664    {
665        $key = (string) $key;
666        $this->_attribs[$key] = $value;
667        return $this;
668    }
669
670    /**
671     * Add multiple form attributes at once
672     *
673     * @param  array $attribs
674     * @return Zend_Form
675     */
676    public function addAttribs(array $attribs)
677    {
678        foreach ($attribs as $key => $value) {
679            $this->setAttrib($key, $value);
680        }
681        return $this;
682    }
683
684    /**
685     * Set multiple form attributes at once
686     *
687     * Overwrites any previously set attributes.
688     *
689     * @param  array $attribs
690     * @return Zend_Form
691     */
692    public function setAttribs(array $attribs)
693    {
694        $this->clearAttribs();
695        return $this->addAttribs($attribs);
696    }
697
698    /**
699     * Retrieve a single form attribute
700     *
701     * @param  string $key
702     * @return mixed
703     */
704    public function getAttrib($key)
705    {
706        $key = (string) $key;
707        if (!isset($this->_attribs[$key])) {
708            return null;
709        }
710
711        return $this->_attribs[$key];
712    }
713
714    /**
715     * Retrieve all form attributes/metadata
716     *
717     * @return array
718     */
719    public function getAttribs()
720    {
721        return $this->_attribs;
722    }
723
724    /**
725     * Remove attribute
726     *
727     * @param  string $key
728     * @return bool
729     */
730    public function removeAttrib($key)
731    {
732        if (isset($this->_attribs[$key])) {
733            unset($this->_attribs[$key]);
734            return true;
735        }
736
737        return false;
738    }
739
740    /**
741     * Clear all form attributes
742     *
743     * @return Zend_Form
744     */
745    public function clearAttribs()
746    {
747        $this->_attribs = array();
748        return $this;
749    }
750
751    /**
752     * Set form action
753     *
754     * @param  string $action
755     * @return Zend_Form
756     */
757    public function setAction($action)
758    {
759        return $this->setAttrib('action', (string) $action);
760    }
761
762    /**
763     * Get form action
764     *
765     * Sets default to '' if not set.
766     *
767     * @return string
768     */
769    public function getAction()
770    {
771        $action = $this->getAttrib('action');
772        if (null === $action) {
773            $action = '';
774            $this->setAction($action);
775        }
776        return $action;
777    }
778
779    /**
780     * Set form method
781     *
782     * Only values in {@link $_methods()} allowed
783     *
784     * @param  string $method
785     * @return Zend_Form
786     * @throws Zend_Form_Exception
787     */
788    public function setMethod($method)
789    {
790        $method = strtolower($method);
791        if (!in_array($method, $this->_methods)) {
792            throw new Zend_Form_Exception(sprintf('"%s" is an invalid form method', $method));
793        }
794        $this->setAttrib('method', $method);
795        return $this;
796    }
797
798    /**
799     * Retrieve form method
800     *
801     * @return string
802     */
803    public function getMethod()
804    {
805        if (null === ($method = $this->getAttrib('method'))) {
806            $method = self::METHOD_POST;
807            $this->setAttrib('method', $method);
808        }
809        return strtolower($method);
810    }
811
812    /**
813     * Set encoding type
814     *
815     * @param  string $value
816     * @return Zend_Form
817     */
818    public function setEnctype($value)
819    {
820        $this->setAttrib('enctype', $value);
821        return $this;
822    }
823
824    /**
825     * Get encoding type
826     *
827     * @return string
828     */
829    public function getEnctype()
830    {
831        if (null === ($enctype = $this->getAttrib('enctype'))) {
832            $enctype = self::ENCTYPE_URLENCODED;
833            $this->setAttrib('enctype', $enctype);
834        }
835        return $this->getAttrib('enctype');
836    }
837
838    /**
839     * Filter a name to only allow valid variable characters
840     *
841     * @param  string $value
842     * @param  bool $allowBrackets
843     * @return string
844     */
845    public function filterName($value, $allowBrackets = false)
846    {
847        $charset = '^a-zA-Z0-9_\x7f-\xff';
848        if ($allowBrackets) {
849            $charset .= '\[\]';
850        }
851        return preg_replace('/[' . $charset . ']/', '', (string) $value);
852    }
853
854    /**
855     * Set form name
856     *
857     * @param  string $name
858     * @return Zend_Form
859     * @throws Zend_Form_Exception
860     */
861    public function setName($name)
862    {
863        $name = $this->filterName($name);
864        if ('' === (string)$name) {
865            throw new Zend_Form_Exception('Invalid name provided; must contain only valid variable characters and be non-empty');
866        }
867
868        return $this->setAttrib('name', $name);
869    }
870
871    /**
872     * Get name attribute
873     *
874     * @return null|string
875     */
876    public function getName()
877    {
878        return $this->getAttrib('name');
879    }
880
881    /**
882     * Get fully qualified name
883     *
884     * Places name as subitem of array and/or appends brackets.
885     *
886     * @return string
887     */
888    public function getFullyQualifiedName()
889    {
890        return $this->getName();
891    }
892
893    /**
894     * Get element id
895     *
896     * @return string
897     */
898    public function getId()
899    {
900        if (null !== ($id = $this->getAttrib('id'))) {
901            return $id;
902        }
903
904        $id = $this->getFullyQualifiedName();
905
906        // Bail early if no array notation detected
907        if (!strstr($id, '[')) {
908            return $id;
909        }
910
911        // Strip array notation
912        if ('[]' == substr($id, -2)) {
913            $id = substr($id, 0, strlen($id) - 2);
914        }
915        $id = str_replace('][', '-', $id);
916        $id = str_replace(array(']', '['), '-', $id);
917        $id = trim($id, '-');
918
919        return $id;
920    }
921
922    /**
923     * Set form legend
924     *
925     * @param  string $value
926     * @return Zend_Form
927     */
928    public function setLegend($value)
929    {
930        $this->_legend = (string) $value;
931        return $this;
932    }
933
934    /**
935     * Get form legend
936     *
937     * @return string
938     */
939    public function getLegend()
940    {
941        return $this->_legend;
942    }
943
944    /**
945     * Set form description
946     *
947     * @param  string $value
948     * @return Zend_Form
949     */
950    public function setDescription($value)
951    {
952        $this->_description = (string) $value;
953        return $this;
954    }
955
956    /**
957     * Retrieve form description
958     *
959     * @return string
960     */
961    public function getDescription()
962    {
963        return $this->_description;
964    }
965
966    /**
967     * Set form order
968     *
969     * @param  int $index
970     * @return Zend_Form
971     */
972    public function setOrder($index)
973    {
974        $this->_formOrder = (int) $index;
975        return $this;
976    }
977
978    /**
979     * Get form order
980     *
981     * @return int|null
982     */
983    public function getOrder()
984    {
985        return $this->_formOrder;
986    }
987
988    /**
989     * When calling renderFormElements or render this method
990     * is used to set $_isRendered member to prevent repeatedly
991     * merging belongsTo setting
992     */
993    protected function _setIsRendered()
994    {
995        $this->_isRendered = true;
996        return $this;
997    }
998
999    /**
1000     * Get the value of $_isRendered member
1001     */
1002    protected function _getIsRendered()
1003    {
1004        return (bool)$this->_isRendered;
1005    }
1006
1007    // Element interaction:
1008
1009    /**
1010     * Add a new element
1011     *
1012     * $element may be either a string element type, or an object of type
1013     * Zend_Form_Element. If a string element type is provided, $name must be
1014     * provided, and $options may be optionally provided for configuring the
1015     * element.
1016     *
1017     * If a Zend_Form_Element is provided, $name may be optionally provided,
1018     * and any provided $options will be ignored.
1019     *
1020     * @param  string|Zend_Form_Element $element
1021     * @param  string $name
1022     * @param  array|Zend_Config $options
1023     * @throws Zend_Form_Exception on invalid element
1024     * @return Zend_Form
1025     */
1026    public function addElement($element, $name = null, $options = null)
1027    {
1028        if (is_string($element)) {
1029            if (null === $name) {
1030                throw new Zend_Form_Exception(
1031                    'Elements specified by string must have an accompanying name'
1032                );
1033            }
1034
1035            $this->_elements[$name] = $this->createElement($element, $name, $options);
1036        } elseif ($element instanceof Zend_Form_Element) {
1037            $prefixPaths              = array();
1038            $prefixPaths['decorator'] = $this->getPluginLoader('decorator')->getPaths();
1039            if (!empty($this->_elementPrefixPaths)) {
1040                $prefixPaths = array_merge($prefixPaths, $this->_elementPrefixPaths);
1041            }
1042
1043            if (is_array($this->_elementDecorators)
1044                && 0 == count($element->getDecorators())
1045            ) {
1046                $element->setDecorators($this->_elementDecorators);
1047            }
1048
1049            if (null === $name) {
1050                $name = $element->getName();
1051            }
1052
1053            $this->_elements[$name] = $element;
1054            $this->_elements[$name]->addPrefixPaths($prefixPaths);
1055        } else {
1056            throw new Zend_Form_Exception(
1057                'Element must be specified by string or Zend_Form_Element instance'
1058            );
1059        }
1060
1061        $this->_order[$name] = $this->_elements[$name]->getOrder();
1062        $this->_orderUpdated = true;
1063        $this->_setElementsBelongTo($name);
1064
1065        return $this;
1066    }
1067
1068    /**
1069     * Create an element
1070     *
1071     * Acts as a factory for creating elements. Elements created with this
1072     * method will not be attached to the form, but will contain element
1073     * settings as specified in the form object (including plugin loader
1074     * prefix paths, default decorators, etc.).
1075     *
1076     * @param  string            $type
1077     * @param  string            $name
1078     * @param  array|Zend_Config $options
1079     * @throws Zend_Form_Exception
1080     * @return Zend_Form_Element
1081     */
1082    public function createElement($type, $name, $options = null)
1083    {
1084        if (!is_string($type)) {
1085            throw new Zend_Form_Exception('Element type must be a string indicating type');
1086        }
1087
1088        if (!is_string($name)) {
1089            throw new Zend_Form_Exception('Element name must be a string');
1090        }
1091
1092        $prefixPaths              = array();
1093        $prefixPaths['decorator'] = $this->getPluginLoader('decorator')->getPaths();
1094        if (!empty($this->_elementPrefixPaths)) {
1095            $prefixPaths = array_merge($prefixPaths, $this->_elementPrefixPaths);
1096        }
1097
1098        if ($options instanceof Zend_Config) {
1099            $options = $options->toArray();
1100        }
1101
1102        if ((null === $options) || !is_array($options)) {
1103            $options = array('prefixPath' => $prefixPaths);
1104
1105            if (is_array($this->_elementDecorators)) {
1106                $options['decorators'] = $this->_elementDecorators;
1107            }
1108        } elseif (is_array($options)) {
1109            if (array_key_exists('prefixPath', $options)) {
1110                $options['prefixPath'] = array_merge($prefixPaths, $options['prefixPath']);
1111            } else {
1112                $options['prefixPath'] = $prefixPaths;
1113            }
1114
1115            if (is_array($this->_elementDecorators)
1116                && !array_key_exists('decorators', $options)
1117            ) {
1118                $options['decorators'] = $this->_elementDecorators;
1119            }
1120        }
1121
1122        $class = $this->getPluginLoader(self::ELEMENT)->load($type);
1123        $element = new $class($name, $options);
1124
1125        return $element;
1126    }
1127
1128    /**
1129     * Add multiple elements at once
1130     *
1131     * @param  array $elements
1132     * @return Zend_Form
1133     */
1134    public function addElements(array $elements)
1135    {
1136        foreach ($elements as $key => $spec) {
1137            $name = null;
1138            if (!is_numeric($key)) {
1139                $name = $key;
1140            }
1141
1142            if (is_string($spec) || ($spec instanceof Zend_Form_Element)) {
1143                $this->addElement($spec, $name);
1144                continue;
1145            }
1146
1147            if (is_array($spec)) {
1148                $argc = count($spec);
1149                $options = array();
1150                if (isset($spec['type'])) {
1151                    $type = $spec['type'];
1152                    if (isset($spec['name'])) {
1153                        $name = $spec['name'];
1154                    }
1155                    if (isset($spec['options'])) {
1156                        $options = $spec['options'];
1157                    }
1158                    $this->addElement($type, $name, $options);
1159                } else {
1160                    switch ($argc) {
1161                        case 0:
1162                            continue 2;
1163                        case (1 <= $argc):
1164                            $type = array_shift($spec);
1165                        case (2 <= $argc):
1166                            if (null === $name) {
1167                                $name = array_shift($spec);
1168                            } else {
1169                                $options = array_shift($spec);
1170                            }
1171                        case (3 <= $argc):
1172                            if (empty($options)) {
1173                                $options = array_shift($spec);
1174                            }
1175                        default:
1176                            $this->addElement($type, $name, $options);
1177                    }
1178                }
1179            }
1180        }
1181        return $this;
1182    }
1183
1184    /**
1185     * Set form elements (overwrites existing elements)
1186     *
1187     * @param  array $elements
1188     * @return Zend_Form
1189     */
1190    public function setElements(array $elements)
1191    {
1192        $this->clearElements();
1193        return $this->addElements($elements);
1194    }
1195
1196    /**
1197     * Retrieve a single element
1198     *
1199     * @param  string $name
1200     * @return Zend_Form_Element|null
1201     */
1202    public function getElement($name)
1203    {
1204        if (array_key_exists($name, $this->_elements)) {
1205            return $this->_elements[$name];
1206        }
1207        return null;
1208    }
1209
1210    /**
1211     * Retrieve all elements
1212     *
1213     * @return array
1214     */
1215    public function getElements()
1216    {
1217        return $this->_elements;
1218    }
1219
1220    /**
1221     * Remove element
1222     *
1223     * @param  string $name
1224     * @return boolean
1225     */
1226    public function removeElement($name)
1227    {
1228        $name = (string) $name;
1229        if (isset($this->_elements[$name])) {
1230            unset($this->_elements[$name]);
1231            if (array_key_exists($name, $this->_order)) {
1232                unset($this->_order[$name]);
1233                $this->_orderUpdated = true;
1234            } else {
1235                /** @var Zend_Form_DisplayGroup $group */
1236                foreach ($this->_displayGroups as $group) {
1237                    if (null !== $group->getElement($name)) {
1238                        $group->removeElement($name);
1239                    }
1240                }
1241            }
1242            return true;
1243        }
1244
1245        return false;
1246    }
1247
1248    /**
1249     * Remove all form elements
1250     *
1251     * @return Zend_Form
1252     */
1253    public function clearElements()
1254    {
1255        foreach (array_keys($this->_elements) as $key) {
1256            if (array_key_exists($key, $this->_order)) {
1257                unset($this->_order[$key]);
1258            }
1259        }
1260        $this->_elements     = array();
1261        $this->_orderUpdated = true;
1262        return $this;
1263    }
1264
1265    /**
1266     * Set default values for elements
1267     *
1268     * Sets values for all elements specified in the array of $defaults.
1269     *
1270     * @param  array $defaults
1271     * @return Zend_Form
1272     */
1273    public function setDefaults(array $defaults)
1274    {
1275        $eBelongTo = null;
1276
1277        if ($this->isArray()) {
1278            $eBelongTo = $this->getElementsBelongTo();
1279            $defaults = $this->_dissolveArrayValue($defaults, $eBelongTo);
1280        }
1281        /** @var Zend_Form_Element $element */
1282        foreach ($this->getElements() as $name => $element) {
1283            $check = $defaults;
1284            if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
1285                $check = $this->_dissolveArrayValue($defaults, $belongsTo);
1286            }
1287            if (array_key_exists($name, (array)$check)) {
1288                $this->setDefault($name, $check[$name]);
1289                $defaults = $this->_dissolveArrayUnsetKey($defaults, $belongsTo, $name);
1290            }
1291        }
1292        /** @var Zend_Form_SubForm $form */
1293        foreach ($this->getSubForms() as $name => $form) {
1294            if (!$form->isArray() && array_key_exists($name, $defaults)) {
1295                $form->setDefaults($defaults[$name]);
1296            } else {
1297                $form->setDefaults($defaults);
1298            }
1299        }
1300        return $this;
1301    }
1302
1303    /**
1304     * Set default value for an element
1305     *
1306     * @param  string $name
1307     * @param  mixed $value
1308     * @return Zend_Form
1309     */
1310    public function setDefault($name, $value)
1311    {
1312        $name = (string) $name;
1313        if ($element = $this->getElement($name)) {
1314            $element->setValue($value);
1315        } else {
1316            if (is_scalar($value)) {
1317                /** @var Zend_Form_SubForm $subForm */
1318                foreach ($this->getSubForms() as $subForm) {
1319                    $subForm->setDefault($name, $value);
1320                }
1321            } elseif (is_array($value) && ($subForm = $this->getSubForm($name))) {
1322                $subForm->setDefaults($value);
1323            }
1324        }
1325        return $this;
1326    }
1327
1328    /**
1329     * Retrieve value for single element
1330     *
1331     * @param  string $name
1332     * @return mixed
1333     */
1334    public function getValue($name)
1335    {
1336        if ($element = $this->getElement($name)) {
1337            return $element->getValue();
1338        }
1339
1340        if ($subForm = $this->getSubForm($name)) {
1341            return $subForm->getValues(true);
1342        }
1343
1344        /** @var Zend_Form_SubForm $subForm */
1345        foreach ($this->getSubForms() as $subForm) {
1346            if ($name == $subForm->getElementsBelongTo()) {
1347                return $subForm->getValues(true);
1348            }
1349        }
1350        return null;
1351    }
1352
1353    /**
1354     * Retrieve all form element values
1355     *
1356     * @param  bool $suppressArrayNotation
1357     * @return array
1358     */
1359    public function getValues($suppressArrayNotation = false)
1360    {
1361        $values = array();
1362        $eBelongTo = null;
1363
1364        if ($this->isArray()) {
1365            $eBelongTo = $this->getElementsBelongTo();
1366        }
1367        /** @var Zend_Form_Element $element */
1368        foreach ($this->getElements() as $key => $element) {
1369            if (!$element->getIgnore()) {
1370                $merge = array();
1371                if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
1372                    if ('' !== (string)$belongsTo) {
1373                        $key = $belongsTo . '[' . $key . ']';
1374                    }
1375                }
1376                $merge = $this->_attachToArray($element->getValue(), $key);
1377                $values = $this->_array_replace_recursive($values, $merge);
1378            }
1379        }
1380        /** @var Zend_Form_SubForm $subForm */
1381        foreach ($this->getSubForms() as $key => $subForm) {
1382            $merge = array();
1383            if (!$subForm->isArray()) {
1384                $merge[$key] = $subForm->getValues();
1385            } else {
1386                $merge = $this->_attachToArray($subForm->getValues(true),
1387                                               $subForm->getElementsBelongTo());
1388            }
1389            $values = $this->_array_replace_recursive($values, $merge);
1390        }
1391
1392        if (!$suppressArrayNotation &&
1393            $this->isArray() &&
1394            !$this->_getIsRendered()) {
1395            $values = $this->_attachToArray($values, $this->getElementsBelongTo());
1396        }
1397
1398        return $values;
1399    }
1400
1401    /**
1402     * Returns only the valid values from the given form input.
1403     *
1404     * For models that can be saved in a partially valid state, for example when following the builder,
1405     * prototype or state patterns it is particularly interessting to retrieve all the current valid
1406     * values to persist them.
1407     *
1408     * @param  array $data
1409     * @param  bool $suppressArrayNotation
1410     * @return array
1411     */
1412    public function getValidValues($data, $suppressArrayNotation = false)
1413    {
1414        $values = array();
1415        $eBelongTo = null;
1416
1417        if ($this->isArray()) {
1418            $eBelongTo = $this->getElementsBelongTo();
1419            $data = $this->_dissolveArrayValue($data, $eBelongTo);
1420        }
1421        $context = $data;
1422        /** @var Zend_Form_Element $element */
1423        foreach ($this->getElements() as $key => $element) {
1424            if (!$element->getIgnore()) {
1425                $check = $data;
1426                if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
1427                    $check = $this->_dissolveArrayValue($data, $belongsTo);
1428                }
1429                if (isset($check[$key])) {
1430                    if($element->isValid($check[$key], $context)) {
1431                        $merge = array();
1432                        if ($belongsTo !== $eBelongTo && '' !== (string)$belongsTo) {
1433                            $key = $belongsTo . '[' . $key . ']';
1434                        }
1435                        $merge = $this->_attachToArray($element->getValue(), $key);
1436                        $values = $this->_array_replace_recursive($values, $merge);
1437                    }
1438                    $data = $this->_dissolveArrayUnsetKey($data, $belongsTo, $key);
1439                }
1440            }
1441        }
1442        /** @var Zend_Form_SubForm $form */
1443        foreach ($this->getSubForms() as $key => $form) {
1444            $merge = array();
1445            if (isset($data[$key]) && !$form->isArray()) {
1446                $tmp = $form->getValidValues($data[$key]);
1447                if (!empty($tmp)) {
1448                    $merge[$key] = $tmp;
1449                }
1450            } else {
1451                $tmp = $form->getValidValues($data, true);
1452                if (!empty($tmp)) {
1453                    $merge = $this->_attachToArray($tmp, $form->getElementsBelongTo());
1454                }
1455            }
1456            $values = $this->_array_replace_recursive($values, $merge);
1457        }
1458        if (!$suppressArrayNotation &&
1459            $this->isArray() &&
1460            !empty($values) &&
1461            !$this->_getIsRendered()) {
1462            $values = $this->_attachToArray($values, $this->getElementsBelongTo());
1463        }
1464
1465        return $values;
1466    }
1467
1468    /**
1469     * Get unfiltered element value
1470     *
1471     * @param  string $name
1472     * @return mixed
1473     */
1474    public function getUnfilteredValue($name)
1475    {
1476        if ($element = $this->getElement($name)) {
1477            return $element->getUnfilteredValue();
1478        }
1479        return null;
1480    }
1481
1482    /**
1483     * Retrieve all unfiltered element values
1484     *
1485     * @return array
1486     */
1487    public function getUnfilteredValues()
1488    {
1489        $values = array();
1490        /** @var Zend_Form_Element $element */
1491        foreach ($this->getElements() as $key => $element) {
1492            $values[$key] = $element->getUnfilteredValue();
1493        }
1494
1495        return $values;
1496    }
1497
1498    /**
1499     * Set all elements' filters
1500     *
1501     * @param  array $filters
1502     * @return Zend_Form
1503     */
1504    public function setElementFilters(array $filters)
1505    {
1506        /** @var Zend_Form_Element $element */
1507        foreach ($this->getElements() as $element) {
1508            $element->setFilters($filters);
1509        }
1510        return $this;
1511    }
1512
1513    /**
1514     * Set name of array elements belong to
1515     *
1516     * @param  string $array
1517     * @return Zend_Form
1518     */
1519    public function setElementsBelongTo($array)
1520    {
1521        $origName = $this->getElementsBelongTo();
1522        $name = $this->filterName($array, true);
1523        if ('' === $name) {
1524            $name = null;
1525        }
1526        $this->_elementsBelongTo = $name;
1527
1528        if (null === $name) {
1529            $this->setIsArray(false);
1530            if (null !== $origName) {
1531                $this->_setElementsBelongTo();
1532            }
1533        } else {
1534            $this->setIsArray(true);
1535            $this->_setElementsBelongTo();
1536        }
1537
1538        return $this;
1539    }
1540
1541    /**
1542     * Set array to which elements belong
1543     *
1544     * @param  string $name Element name
1545     * @return void
1546     */
1547    protected function _setElementsBelongTo($name = null)
1548    {
1549        $array = $this->getElementsBelongTo();
1550
1551        if (null === $array) {
1552            return;
1553        }
1554
1555        if (null === $name) {
1556            /** @var Zend_Form_Element $element */
1557            foreach ($this->getElements() as $element) {
1558                $element->setBelongsTo($array);
1559            }
1560        } else {
1561            if (null !== ($element = $this->getElement($name))) {
1562                $element->setBelongsTo($array);
1563            }
1564        }
1565    }
1566
1567    /**
1568     * Get name of array elements belong to
1569     *
1570     * @return string|null
1571     */
1572    public function getElementsBelongTo()
1573    {
1574        if ((null === $this->_elementsBelongTo) && $this->isArray()) {
1575            $name = $this->getName();
1576            if ('' !== (string)$name) {
1577                return $name;
1578            }
1579        }
1580        return $this->_elementsBelongTo;
1581    }
1582
1583    /**
1584     * Set flag indicating elements belong to array
1585     *
1586     * @param  bool $flag Value of flag
1587     * @return Zend_Form
1588     */
1589    public function setIsArray($flag)
1590    {
1591        $this->_isArray = (bool) $flag;
1592        return $this;
1593    }
1594
1595    /**
1596     * Get flag indicating if elements belong to an array
1597     *
1598     * @return bool
1599     */
1600    public function isArray()
1601    {
1602        return $this->_isArray;
1603    }
1604
1605    // Element groups:
1606
1607    /**
1608     * Add a form group/subform
1609     *
1610     * @param  Zend_Form $form
1611     * @param  string $name
1612     * @param  int $order
1613     * @return Zend_Form
1614     */
1615    public function addSubForm(Zend_Form $form, $name, $order = null)
1616    {
1617        $name = (string) $name;
1618        /** @var Zend_Loader_PluginLoader $loader */
1619        foreach ($this->_loaders as $type => $loader) {
1620            $loaderPaths = $loader->getPaths();
1621            foreach ($loaderPaths as $prefix => $paths) {
1622                foreach ($paths as $path) {
1623                    $form->addPrefixPath($prefix, $path, $type);
1624                }
1625            }
1626        }
1627
1628        if (!empty($this->_elementPrefixPaths)) {
1629            foreach ($this->_elementPrefixPaths as $spec) {
1630                list($prefix, $path, $type) = array_values($spec);
1631                $form->addElementPrefixPath($prefix, $path, $type);
1632            }
1633        }
1634
1635        if (!empty($this->_displayGroupPrefixPaths)) {
1636            foreach ($this->_displayGroupPrefixPaths as $spec) {
1637                list($prefix, $path) = array_values($spec);
1638                $form->addDisplayGroupPrefixPath($prefix, $path);
1639            }
1640        }
1641
1642        if (null !== $order) {
1643            $form->setOrder($order);
1644        }
1645
1646        if (($oldName = $form->getName()) &&
1647            $oldName !== $name &&
1648            $oldName === $form->getElementsBelongTo()) {
1649            $form->setElementsBelongTo($name);
1650        }
1651
1652        $form->setName($name);
1653        $this->_subForms[$name] = $form;
1654        $this->_order[$name]    = $order;
1655        $this->_orderUpdated    = true;
1656        return $this;
1657    }
1658
1659    /**
1660     * Add multiple form subForms/subforms at once
1661     *
1662     * @param  array $subForms
1663     * @return Zend_Form
1664     */
1665    public function addSubForms(array $subForms)
1666    {
1667        foreach ($subForms as $key => $spec) {
1668            $name = (string) $key;
1669            if ($spec instanceof Zend_Form) {
1670                $this->addSubForm($spec, $name);
1671                continue;
1672            }
1673
1674            if (is_array($spec)) {
1675                $argc  = count($spec);
1676                $order = null;
1677                switch ($argc) {
1678                    case 0:
1679                        continue 2;
1680                    case (1 <= $argc):
1681                        $subForm = array_shift($spec);
1682
1683                        if (!$subForm instanceof Zend_Form) {
1684                            $subForm = new Zend_Form_SubForm($subForm);
1685                        }
1686                    case (2 <= $argc):
1687                        $name  = array_shift($spec);
1688                    case (3 <= $argc):
1689                        $order = array_shift($spec);
1690                    default:
1691                        $this->addSubForm($subForm, $name, $order);
1692                }
1693            }
1694        }
1695        return $this;
1696    }
1697
1698    /**
1699     * Set multiple form subForms/subforms (overwrites)
1700     *
1701     * @param  array $subForms
1702     * @return Zend_Form
1703     */
1704    public function setSubForms(array $subForms)
1705    {
1706        $this->clearSubForms();
1707        return $this->addSubForms($subForms);
1708    }
1709
1710    /**
1711     * Retrieve a form subForm/subform
1712     *
1713     * @param  string $name
1714     * @return Zend_Form|null
1715     */
1716    public function getSubForm($name)
1717    {
1718        $name = (string) $name;
1719        if (isset($this->_subForms[$name])) {
1720            return $this->_subForms[$name];
1721        }
1722        return null;
1723    }
1724
1725    /**
1726     * Retrieve all form subForms/subforms
1727     *
1728     * @return array
1729     */
1730    public function getSubForms()
1731    {
1732        return $this->_subForms;
1733    }
1734
1735    /**
1736     * Remove form subForm/subform
1737     *
1738     * @param  string $name
1739     * @return boolean
1740     */
1741    public function removeSubForm($name)
1742    {
1743        $name = (string) $name;
1744        if (array_key_exists($name, $this->_subForms)) {
1745            unset($this->_subForms[$name]);
1746            if (array_key_exists($name, $this->_order)) {
1747                unset($this->_order[$name]);
1748                $this->_orderUpdated = true;
1749            }
1750            return true;
1751        }
1752
1753        return false;
1754    }
1755
1756    /**
1757     * Remove all form subForms/subforms
1758     *
1759     * @return Zend_Form
1760     */
1761    public function clearSubForms()
1762    {
1763        foreach (array_keys($this->_subForms) as $key) {
1764            if (array_key_exists($key, $this->_order)) {
1765                unset($this->_order[$key]);
1766            }
1767        }
1768        $this->_subForms     = array();
1769        $this->_orderUpdated = true;
1770        return $this;
1771    }
1772
1773
1774    // Display groups:
1775
1776    /**
1777     * Set default display group class
1778     *
1779     * @param  string $class
1780     * @return Zend_Form
1781     */
1782    public function setDefaultDisplayGroupClass($class)
1783    {
1784        $this->_defaultDisplayGroupClass = (string) $class;
1785        return $this;
1786    }
1787
1788    /**
1789     * Retrieve default display group class
1790     *
1791     * @return string
1792     */
1793    public function getDefaultDisplayGroupClass()
1794    {
1795        return $this->_defaultDisplayGroupClass;
1796    }
1797
1798    /**
1799     * Add a display group
1800     *
1801     * Groups named elements for display purposes.
1802     *
1803     * If a referenced element does not yet exist in the form, it is omitted.
1804     *
1805     * @param  array $elements
1806     * @param  string $name
1807     * @param  array|Zend_Config $options
1808     * @return Zend_Form
1809     * @throws Zend_Form_Exception if no valid elements provided
1810     */
1811    public function addDisplayGroup(array $elements, $name, $options = null)
1812    {
1813        $group = array();
1814        foreach ($elements as $element) {
1815            if($element instanceof Zend_Form_Element) {
1816                $elementName = $element->getName();
1817                if (!isset($this->_elements[$elementName])) {
1818                    $this->addElement($element);
1819                }
1820                $element = $elementName;
1821            }
1822
1823            if (isset($this->_elements[$element])) {
1824                $add = $this->getElement($element);
1825                if (null !== $add) {
1826                    $group[] = $add;
1827                }
1828            }
1829        }
1830        if (empty($group)) {
1831            throw new Zend_Form_Exception('No valid elements specified for display group');
1832        }
1833
1834        $name = (string) $name;
1835
1836        if (is_array($options)) {
1837            $options['form']     = $this;
1838            $options['elements'] = $group;
1839        } elseif ($options instanceof Zend_Config) {
1840            $options = $options->toArray();
1841            $options['form']     = $this;
1842            $options['elements'] = $group;
1843        } else {
1844            $options = array(
1845                'form'     => $this,
1846                'elements' => $group,
1847            );
1848        }
1849
1850        if (isset($options['displayGroupClass'])) {
1851            $class = $options['displayGroupClass'];
1852            unset($options['displayGroupClass']);
1853        } else {
1854            $class = $this->getDefaultDisplayGroupClass();
1855        }
1856
1857        if (!class_exists($class)) {
1858            Zend_Loader::loadClass($class);
1859        }
1860        $this->_displayGroups[$name] = new $class(
1861            $name,
1862            $this->getPluginLoader(self::DECORATOR),
1863            $options
1864        );
1865
1866        if (!empty($this->_displayGroupPrefixPaths)) {
1867            $this->_displayGroups[$name]->addPrefixPaths($this->_displayGroupPrefixPaths);
1868        }
1869
1870        $this->_order[$name] = $this->_displayGroups[$name]->getOrder();
1871        $this->_orderUpdated = true;
1872        return $this;
1873    }
1874
1875    /**
1876     * Add a display group object (used with cloning)
1877     *
1878     * @param  Zend_Form_DisplayGroup $group
1879     * @param  string|null            $name
1880     * @throws Zend_Form_Exception
1881     * @return Zend_Form
1882     */
1883    protected function _addDisplayGroupObject(Zend_Form_DisplayGroup $group, $name = null)
1884    {
1885        if (null === $name) {
1886            $name = $group->getName();
1887            if ('' === (string)$name) {
1888                throw new Zend_Form_Exception('Invalid display group added; requires name');
1889            }
1890        }
1891
1892        $this->_displayGroups[$name] = $group;
1893        $group->setForm($this);
1894
1895        if (!empty($this->_displayGroupPrefixPaths)) {
1896            $this->_displayGroups[$name]->addPrefixPaths($this->_displayGroupPrefixPaths);
1897        }
1898
1899        $this->_order[$name] = $this->_displayGroups[$name]->getOrder();
1900        $this->_orderUpdated = true;
1901        return $this;
1902    }
1903
1904    /**
1905     * Add multiple display groups at once
1906     *
1907     * @param  array $groups
1908     * @return Zend_Form
1909     */
1910    public function addDisplayGroups(array $groups)
1911    {
1912        foreach ($groups as $key => $spec) {
1913            $name = null;
1914            if (!is_numeric($key)) {
1915                $name = $key;
1916            }
1917
1918            if ($spec instanceof Zend_Form_DisplayGroup) {
1919                $this->_addDisplayGroupObject($spec);
1920            }
1921
1922            if (!is_array($spec) || empty($spec)) {
1923                continue;
1924            }
1925
1926            $argc    = count($spec);
1927            $options = array();
1928
1929            if (isset($spec['elements'])) {
1930                $elements = $spec['elements'];
1931                if (isset($spec['name'])) {
1932                    $name = $spec['name'];
1933                }
1934                if (isset($spec['options'])) {
1935                    $options = $spec['options'];
1936                }
1937                $this->addDisplayGroup($elements, $name, $options);
1938            } else {
1939                switch ($argc) {
1940                    case (1 <= $argc):
1941                        $elements = array_shift($spec);
1942                        if (!is_array($elements) && (null !== $name)) {
1943                            $elements = array_merge((array) $elements, $spec);
1944                            $this->addDisplayGroup($elements, $name);
1945                            break;
1946                        }
1947                    case (2 <= $argc):
1948                        if (null !== $name) {
1949                            $options = array_shift($spec);
1950                            $this->addDisplayGroup($elements, $name, $options);
1951                            break;
1952                        }
1953                        $name = array_shift($spec);
1954                    case (3 <= $argc):
1955                        $options = array_shift($spec);
1956                    default:
1957                        $this->addDisplayGroup($elements, $name, $options);
1958                }
1959            }
1960        }
1961        return $this;
1962    }
1963
1964    /**
1965     * Add multiple display groups (overwrites)
1966     *
1967     * @param  array $groups
1968     * @return Zend_Form
1969     */
1970    public function setDisplayGroups(array $groups)
1971    {
1972        return $this->clearDisplayGroups()
1973                    ->addDisplayGroups($groups);
1974    }
1975
1976    /**
1977     * Return a display group
1978     *
1979     * @param  string $name
1980     * @return Zend_Form_DisplayGroup|null
1981     */
1982    public function getDisplayGroup($name)
1983    {
1984        $name = (string) $name;
1985        if (isset($this->_displayGroups[$name])) {
1986            return $this->_displayGroups[$name];
1987        }
1988
1989        return null;
1990    }
1991
1992    /**
1993     * Return all display groups
1994     *
1995     * @return array
1996     */
1997    public function getDisplayGroups()
1998    {
1999        return $this->_displayGroups;
2000    }
2001
2002    /**
2003     * Remove a display group by name
2004     *
2005     * @param  string $name
2006     * @return boolean
2007     */
2008    public function removeDisplayGroup($name)
2009    {
2010        $name = (string) $name;
2011        if (array_key_exists($name, $this->_displayGroups)) {
2012            /** @var Zend_Form_Element $element */
2013            foreach ($this->_displayGroups[$name] as $key => $element) {
2014                if (array_key_exists($key, $this->_elements)) {
2015                    $this->_order[$key]  = $element->getOrder();
2016                    $this->_orderUpdated = true;
2017                }
2018            }
2019            unset($this->_displayGroups[$name]);
2020
2021            if (array_key_exists($name, $this->_order)) {
2022                unset($this->_order[$name]);
2023                $this->_orderUpdated = true;
2024            }
2025            return true;
2026        }
2027
2028        return false;
2029    }
2030
2031    /**
2032     * Remove all display groups
2033     *
2034     * @return Zend_Form
2035     */
2036    public function clearDisplayGroups()
2037    {
2038        foreach ($this->_displayGroups as $key => $group) {
2039            if (array_key_exists($key, $this->_order)) {
2040                unset($this->_order[$key]);
2041            }
2042            /** @var Zend_Form_Element $element */
2043            foreach ($group as $name => $element) {
2044                if (isset($this->_elements[$name])) {
2045                    $this->_order[$name] = $element->getOrder();
2046                }
2047                $this->_order[$name] = $element->getOrder();
2048            }
2049        }
2050        $this->_displayGroups = array();
2051        $this->_orderUpdated  = true;
2052        return $this;
2053    }
2054
2055
2056    // Processing
2057
2058    /**
2059     * Populate form
2060     *
2061     * Proxies to {@link setDefaults()}
2062     *
2063     * @param  array $values
2064     * @return Zend_Form
2065     */
2066    public function populate(array $values)
2067    {
2068        return $this->setDefaults($values);
2069    }
2070
2071    /**
2072     * Determine array key name from given value
2073     *
2074     * Given a value such as foo[bar][baz], returns the last element (in this case, 'baz').
2075     *
2076     * @param  string $value
2077     * @return string
2078     */
2079    protected function _getArrayName($value)
2080    {
2081        if (!is_string($value) || '' === $value) {
2082            return $value;
2083        }
2084
2085        if (!strstr($value, '[')) {
2086            return $value;
2087        }
2088
2089        $endPos = strlen($value) - 1;
2090        if (']' != $value[$endPos]) {
2091            return $value;
2092        }
2093
2094        $start = strrpos($value, '[') + 1;
2095        $name = substr($value, $start, $endPos - $start);
2096        return $name;
2097    }
2098
2099    /**
2100     * Extract the value by walking the array using given array path.
2101     *
2102     * Given an array path such as foo[bar][baz], returns the value of the last
2103     * element (in this case, 'baz').
2104     *
2105     * @param  array $value Array to walk
2106     * @param  string $arrayPath Array notation path of the part to extract
2107     * @return string
2108     */
2109    protected function _dissolveArrayValue($value, $arrayPath)
2110    {
2111        // As long as we have more levels
2112        while ($arrayPos = strpos($arrayPath, '[')) {
2113            // Get the next key in the path
2114            $arrayKey = trim(substr($arrayPath, 0, $arrayPos), ']');
2115
2116            // Set the potentially final value or the next search point in the array
2117            if (isset($value[$arrayKey])) {
2118                $value = $value[$arrayKey];
2119            }
2120
2121            // Set the next search point in the path
2122            $arrayPath = trim(substr($arrayPath, $arrayPos + 1), ']');
2123        }
2124
2125        if (isset($value[$arrayPath])) {
2126            $value = $value[$arrayPath];
2127        }
2128
2129        return $value;
2130    }
2131
2132    /**
2133     * Given an array, an optional arrayPath and a key this method
2134     * dissolves the arrayPath and unsets the key within the array
2135     * if it exists.
2136     *
2137     * @param array $array
2138     * @param string|null $arrayPath
2139     * @param string $key
2140     * @return array
2141     */
2142    protected function _dissolveArrayUnsetKey($array, $arrayPath, $key)
2143    {
2144        $unset =& $array;
2145        $path  = trim(strtr((string)$arrayPath, array('[' => '/', ']' => '')), '/');
2146        $segs  = ('' !== $path) ? explode('/', $path) : array();
2147
2148        foreach ($segs as $seg) {
2149            if (!array_key_exists($seg, (array)$unset)) {
2150                return $array;
2151            }
2152            $unset =& $unset[$seg];
2153        }
2154        if (array_key_exists($key, (array)$unset)) {
2155            unset($unset[$key]);
2156        }
2157        return $array;
2158    }
2159
2160    /**
2161     * Converts given arrayPath to an array and attaches given value at the end of it.
2162     *
2163     * @param  mixed $value The value to attach
2164     * @param  string $arrayPath Given array path to convert and attach to.
2165     * @return array
2166     */
2167    protected function _attachToArray($value, $arrayPath)
2168    {
2169        // As long as we have more levels
2170        while ($arrayPos = strrpos($arrayPath, '[')) {
2171            // Get the next key in the path
2172            $arrayKey = trim(substr($arrayPath, $arrayPos + 1), ']');
2173
2174            // Attach
2175            $value = array($arrayKey => $value);
2176
2177            // Set the next search point in the path
2178            $arrayPath = trim(substr($arrayPath, 0, $arrayPos), ']');
2179        }
2180
2181        $value = array($arrayPath => $value);
2182
2183        return $value;
2184    }
2185
2186    /**
2187     * Returns a one dimensional numerical indexed array with the
2188     * Elements, SubForms and Elements from DisplayGroups as Values.
2189     *
2190     * Subitems are inserted based on their order Setting if set,
2191     * otherwise they are appended, the resulting numerical index
2192     * may differ from the order value.
2193     *
2194     * @access protected
2195     * @return array
2196     */
2197    public function getElementsAndSubFormsOrdered()
2198    {
2199        $ordered = array();
2200        foreach ($this->_order as $name => $order) {
2201            $order = isset($order) ? $order : count($ordered);
2202            if ($this->$name instanceof Zend_Form_Element ||
2203                $this->$name instanceof Zend_Form) {
2204                array_splice($ordered, $order, 0, array($this->$name));
2205            } else if ($this->$name instanceof Zend_Form_DisplayGroup) {
2206                $subordered = array();
2207                /** @var Zend_Form_Element $element */
2208                foreach ($this->$name->getElements() as $element) {
2209                    $suborder = $element->getOrder();
2210                    $suborder = (null !== $suborder) ? $suborder : count($subordered);
2211                    array_splice($subordered, $suborder, 0, array($element));
2212                }
2213                if (!empty($subordered)) {
2214                    array_splice($ordered, $order, 0, $subordered);
2215                }
2216            }
2217        }
2218        return $ordered;
2219    }
2220
2221    /**
2222     * This is a helper function until php 5.3 is widespreaded
2223     *
2224     * @param array $into
2225     * @return array
2226     */
2227    protected function _array_replace_recursive(array $into)
2228    {
2229        $fromArrays = array_slice(func_get_args(),1);
2230
2231        foreach ($fromArrays as $from) {
2232            foreach ($from as $key => $value) {
2233                if (is_array($value)) {
2234                    if (!isset($into[$key])) {
2235                        $into[$key] = array();
2236                    }
2237                    $into[$key] = $this->_array_replace_recursive($into[$key], $from[$key]);
2238                } else {
2239                    $into[$key] = $value;
2240                }
2241            }
2242        }
2243        return $into;
2244    }
2245
2246    /**
2247     * Validate the form
2248     *
2249     * @param  array $data
2250     * @throws Zend_Form_Exception
2251     * @return bool
2252     */
2253    public function isValid($data)
2254    {
2255        if (!is_array($data)) {
2256            throw new Zend_Form_Exception(__METHOD__ . ' expects an array');
2257        }
2258        $translator = $this->getTranslator();
2259        $valid      = true;
2260        $eBelongTo  = null;
2261
2262        if ($this->isArray()) {
2263            $eBelongTo = $this->getElementsBelongTo();
2264            $data = $this->_dissolveArrayValue($data, $eBelongTo);
2265        }
2266        $context = $data;
2267        /** @var Zend_Form_Element $element */
2268        foreach ($this->getElements() as $key => $element) {
2269            if (null !== $translator && $this->hasTranslator()
2270                    && !$element->hasTranslator()) {
2271                $element->setTranslator($translator);
2272            }
2273            $check = $data;
2274            if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
2275                $check = $this->_dissolveArrayValue($data, $belongsTo);
2276            }
2277            if (!isset($check[$key])) {
2278                $valid = $element->isValid(null, $context) && $valid;
2279            } else {
2280                $valid = $element->isValid($check[$key], $context) && $valid;
2281                $data = $this->_dissolveArrayUnsetKey($data, $belongsTo, $key);
2282            }
2283        }
2284        /** @var Zend_Form_SubForm $form */
2285        foreach ($this->getSubForms() as $key => $form) {
2286            if (null !== $translator && $this->hasTranslator()
2287                    && !$form->hasTranslator()) {
2288                $form->setTranslator($translator);
2289            }
2290            if (isset($data[$key]) && !$form->isArray()) {
2291                $valid = $form->isValid($data[$key]) && $valid;
2292            } else {
2293                $valid = $form->isValid($data) && $valid;
2294            }
2295        }
2296
2297        $this->_errorsExist = !$valid;
2298
2299        // If manually flagged as an error, return invalid status
2300        if ($this->_errorsForced) {
2301            return false;
2302        }
2303
2304        return $valid;
2305    }
2306
2307    /**
2308     * Validate a partial form
2309     *
2310     * Does not check for required flags.
2311     *
2312     * @param  array $data
2313     * @return boolean
2314     */
2315    public function isValidPartial(array $data)
2316    {
2317        $eBelongTo  = null;
2318
2319        if ($this->isArray()) {
2320            $eBelongTo = $this->getElementsBelongTo();
2321            $data = $this->_dissolveArrayValue($data, $eBelongTo);
2322        }
2323
2324        $translator = $this->getTranslator();
2325        $valid      = true;
2326        $context    = $data;
2327
2328        /** @var Zend_Form_Element $element */
2329        foreach ($this->getElements() as $key => $element) {
2330            $check = $data;
2331            if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
2332                $check = $this->_dissolveArrayValue($data, $belongsTo);
2333            }
2334            if (isset($check[$key])) {
2335                if (null !== $translator && !$element->hasTranslator()) {
2336                    $element->setTranslator($translator);
2337                }
2338                $valid = $element->isValid($check[$key], $context) && $valid;
2339                $data = $this->_dissolveArrayUnsetKey($data, $belongsTo, $key);
2340            }
2341        }
2342        /** @var Zend_Form_SubForm $form */
2343        foreach ($this->getSubForms() as $key => $form) {
2344            if (null !== $translator && !$form->hasTranslator()) {
2345                $form->setTranslator($translator);
2346            }
2347            if (isset($data[$key]) && !$form->isArray()) {
2348                $valid = $form->isValidPartial($data[$key]) && $valid;
2349            } else {
2350                $valid = $form->isValidPartial($data) && $valid;
2351            }
2352        }
2353
2354        $this->_errorsExist = !$valid;
2355        return $valid;
2356    }
2357
2358    /**
2359     * Process submitted AJAX data
2360     *
2361     * Checks if provided $data is valid, via {@link isValidPartial()}. If so,
2362     * it returns JSON-encoded boolean true. If not, it returns JSON-encoded
2363     * error messages (as returned by {@link getMessages()}).
2364     *
2365     * @param  array $data
2366     * @return string JSON-encoded boolean true or error messages
2367     */
2368    public function processAjax(array $data)
2369    {
2370        if ($this->isValidPartial($data)) {
2371            return Zend_Json::encode(true);
2372        }
2373        $messages = $this->getMessages();
2374        return Zend_Json::encode($messages);
2375    }
2376
2377    /**
2378     * Add a custom error message to return in the event of failed validation
2379     *
2380     * @param  string $message
2381     * @return Zend_Form
2382     */
2383    public function addErrorMessage($message)
2384    {
2385        $this->_errorMessages[] = (string) $message;
2386        return $this;
2387    }
2388
2389    /**
2390     * Add multiple custom error messages to return in the event of failed validation
2391     *
2392     * @param  array $messages
2393     * @return Zend_Form
2394     */
2395    public function addErrorMessages(array $messages)
2396    {
2397        foreach ($messages as $message) {
2398            $this->addErrorMessage($message);
2399        }
2400        return $this;
2401    }
2402
2403    /**
2404     * Same as addErrorMessages(), but clears custom error message stack first
2405     *
2406     * @param  array $messages
2407     * @return Zend_Form
2408     */
2409    public function setErrorMessages(array $messages)
2410    {
2411        $this->clearErrorMessages();
2412        return $this->addErrorMessages($messages);
2413    }
2414
2415    /**
2416     * Retrieve custom error messages
2417     *
2418     * @return array
2419     */
2420    public function getErrorMessages()
2421    {
2422        return $this->_errorMessages;
2423    }
2424
2425    /**
2426     * Clear custom error messages stack
2427     *
2428     * @return Zend_Form
2429     */
2430    public function clearErrorMessages()
2431    {
2432        $this->_errorMessages = array();
2433        return $this;
2434    }
2435
2436    /**
2437     * Mark the element as being in a failed validation state
2438     *
2439     * @return Zend_Form
2440     */
2441    public function markAsError()
2442    {
2443        $this->_errorsExist  = true;
2444        $this->_errorsForced = true;
2445        return $this;
2446    }
2447
2448    /**
2449     * Add an error message and mark element as failed validation
2450     *
2451     * @param  string $message
2452     * @return Zend_Form
2453     */
2454    public function addError($message)
2455    {
2456        $this->addErrorMessage($message);
2457        $this->markAsError();
2458        return $this;
2459    }
2460
2461    /**
2462     * Add multiple error messages and flag element as failed validation
2463     *
2464     * @param  array $messages
2465     * @return Zend_Form
2466     */
2467    public function addErrors(array $messages)
2468    {
2469        foreach ($messages as $message) {
2470            $this->addError($message);
2471        }
2472        return $this;
2473    }
2474
2475    /**
2476     * Overwrite any previously set error messages and flag as failed validation
2477     *
2478     * @param  array $messages
2479     * @return Zend_Form
2480     */
2481    public function setErrors(array $messages)
2482    {
2483        $this->clearErrorMessages();
2484        return $this->addErrors($messages);
2485    }
2486
2487
2488    public function persistData()
2489    {
2490    }
2491
2492    /**
2493     * Are there errors in the form?
2494     *
2495     * @deprecated since 1.11.1 - use hasErrors() instead
2496     * @return bool
2497     */
2498    public function isErrors()
2499    {
2500        return $this->hasErrors();
2501    }
2502
2503    /**
2504     * Are there errors in the form?
2505     *
2506     * @return bool
2507     */
2508    public function hasErrors()
2509    {
2510        $errors = $this->_errorsExist;
2511
2512        if (!$errors) {
2513            /** @var Zend_Form_Element $element */
2514            foreach ($this->getElements() as $element) {
2515                if ($element->hasErrors()) {
2516                    $errors = true;
2517                    break;
2518                }
2519            }
2520
2521            /** @var Zend_Form_SubForm $subForm */
2522            foreach ($this->getSubForms() as $subForm) {
2523                if ($subForm->hasErrors()) {
2524                    $errors = true;
2525                    break;
2526                }
2527            }
2528        }
2529
2530        return $errors;
2531    }
2532
2533    /**
2534     * Get error codes for all elements failing validation
2535     *
2536     * @param  string $name
2537     * @param  bool   $suppressArrayNotation
2538     * @return array
2539     */
2540    public function getErrors($name = null, $suppressArrayNotation = false)
2541    {
2542        $errors = array();
2543        if (null !== $name) {
2544            if (isset($this->_elements[$name])) {
2545                return $this->getElement($name)->getErrors();
2546            } else if (isset($this->_subForms[$name])) {
2547                return $this->getSubForm($name)->getErrors(null, true);
2548            }
2549        }
2550
2551        /** @var Zend_Form_Element $element */
2552        foreach ($this->_elements as $key => $element) {
2553            $errors[$key] = $element->getErrors();
2554        }
2555        /** @var Zend_Form_SubForm $subForm */
2556        foreach ($this->getSubForms() as $key => $subForm) {
2557            $merge = array();
2558            if (!$subForm->isArray()) {
2559                $merge[$key] = $subForm->getErrors();
2560            } else {
2561                $merge = $this->_attachToArray($subForm->getErrors(null, true),
2562                                               $subForm->getElementsBelongTo());
2563            }
2564            $errors = $this->_array_replace_recursive($errors, $merge);
2565        }
2566
2567        if (!$suppressArrayNotation &&
2568            $this->isArray() &&
2569            !$this->_getIsRendered()) {
2570            $errors = $this->_attachToArray($errors, $this->getElementsBelongTo());
2571        }
2572
2573        return $errors;
2574    }
2575
2576    /**
2577     * Retrieve error messages from elements failing validations
2578     *
2579     * @param  string $name
2580     * @param  bool $suppressArrayNotation
2581     * @return array
2582     */
2583    public function getMessages($name = null, $suppressArrayNotation = false)
2584    {
2585        if (null !== $name) {
2586            if (isset($this->_elements[$name])) {
2587                return $this->getElement($name)->getMessages();
2588            } else if (isset($this->_subForms[$name])) {
2589                return $this->getSubForm($name)->getMessages(null, true);
2590            }
2591            /** @var Zend_Form_SubForm $subForm */
2592            foreach ($this->getSubForms() as $key => $subForm) {
2593                if ($subForm->isArray()) {
2594                    $belongTo = $subForm->getElementsBelongTo();
2595                    if ($name == $this->_getArrayName($belongTo)) {
2596                        return $subForm->getMessages(null, true);
2597                    }
2598                }
2599            }
2600        }
2601
2602        $customMessages = $this->_getErrorMessages();
2603        if ($this->isErrors() && !empty($customMessages)) {
2604            return $customMessages;
2605        }
2606
2607        $messages = array();
2608
2609        /** @var Zend_Form_Element $element */
2610        foreach ($this->getElements() as $name => $element) {
2611            $eMessages = $element->getMessages();
2612            if (!empty($eMessages)) {
2613                $messages[$name] = $eMessages;
2614            }
2615        }
2616
2617        /** @var Zend_Form_SubForm $subForm */
2618        foreach ($this->getSubForms() as $key => $subForm) {
2619            $merge = $subForm->getMessages(null, true);
2620            if (!empty($merge)) {
2621                if (!$subForm->isArray()) {
2622                    $merge = array($key => $merge);
2623                } else {
2624                    $merge = $this->_attachToArray($merge,
2625                                                   $subForm->getElementsBelongTo());
2626                }
2627                $messages = $this->_array_replace_recursive($messages, $merge);
2628            }
2629        }
2630
2631        if (!$suppressArrayNotation &&
2632            $this->isArray() &&
2633            !$this->_getIsRendered()) {
2634            $messages = $this->_attachToArray($messages, $this->getElementsBelongTo());
2635        }
2636
2637        return $messages;
2638    }
2639
2640    /**
2641     * Retrieve translated custom error messages
2642     * Proxies to {@link _getErrorMessages()}.
2643     *
2644     * @return array
2645     */
2646    public function getCustomMessages()
2647    {
2648        return $this->_getErrorMessages();
2649    }
2650
2651
2652    // Rendering
2653
2654    /**
2655     * Set view object
2656     *
2657     * @param  Zend_View_Interface $view
2658     * @return Zend_Form
2659     */
2660    public function setView(Zend_View_Interface $view = null)
2661    {
2662        $this->_view = $view;
2663        return $this;
2664    }
2665
2666    /**
2667     * Retrieve view object
2668     *
2669     * If none registered, attempts to pull from ViewRenderer.
2670     *
2671     * @return Zend_View_Interface|null
2672     */
2673    public function getView()
2674    {
2675        if (null === $this->_view) {
2676            $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
2677            $this->setView($viewRenderer->view);
2678        }
2679
2680        return $this->_view;
2681    }
2682
2683    /**
2684     * Instantiate a decorator based on class name or class name fragment
2685     *
2686     * @param  string $name
2687     * @param  null|array $options
2688     * @return Zend_Form_Decorator_Interface
2689     */
2690    protected function _getDecorator($name, $options)
2691    {
2692        $class = $this->getPluginLoader(self::DECORATOR)->load($name);
2693        if (null === $options) {
2694            $decorator = new $class;
2695        } else {
2696            $decorator = new $class($options);
2697        }
2698
2699        return $decorator;
2700    }
2701
2702    /**
2703     * Add a decorator for rendering the element
2704     *
2705     * @param  string|Zend_Form_Decorator_Interface $decorator
2706     * @param  array|Zend_Config                    $options Options with which to initialize decorator
2707     * @throws Zend_Form_Exception
2708     * @return Zend_Form
2709     */
2710    public function addDecorator($decorator, $options = null)
2711    {
2712        if ($decorator instanceof Zend_Form_Decorator_Interface) {
2713            $name = get_class($decorator);
2714        } elseif (is_string($decorator)) {
2715            $name      = $decorator;
2716            $decorator = array(
2717                'decorator' => $name,
2718                'options'   => $options,
2719            );
2720        } elseif (is_array($decorator)) {
2721            foreach ($decorator as $name => $spec) {
2722                break;
2723            }
2724            if (is_numeric($name)) {
2725                throw new Zend_Form_Exception('Invalid alias provided to addDecorator; must be alphanumeric string');
2726            }
2727            if (is_string($spec)) {
2728                $decorator = array(
2729                    'decorator' => $spec,
2730                    'options'   => $options,
2731                );
2732            } elseif ($spec instanceof Zend_Form_Decorator_Interface) {
2733                $decorator = $spec;
2734            }
2735        } else {
2736            throw new Zend_Form_Exception('Invalid decorator provided to addDecorator; must be string or Zend_Form_Decorator_Interface');
2737        }
2738
2739        $this->_decorators[$name] = $decorator;
2740
2741        return $this;
2742    }
2743
2744    /**
2745     * Add many decorators at once
2746     *
2747     * @param  array $decorators
2748     * @throws Zend_Form_Exception
2749     * @return Zend_Form
2750     */
2751    public function addDecorators(array $decorators)
2752    {
2753        foreach ($decorators as $decoratorName => $decoratorInfo) {
2754            if (is_string($decoratorInfo) ||
2755                $decoratorInfo instanceof Zend_Form_Decorator_Interface) {
2756                if (!is_numeric($decoratorName)) {
2757                    $this->addDecorator(array($decoratorName => $decoratorInfo));
2758                } else {
2759                    $this->addDecorator($decoratorInfo);
2760                }
2761            } elseif (is_array($decoratorInfo)) {
2762                $argc    = count($decoratorInfo);
2763                $options = array();
2764                if (isset($decoratorInfo['decorator'])) {
2765                    $decorator = $decoratorInfo['decorator'];
2766                    if (isset($decoratorInfo['options'])) {
2767                        $options = $decoratorInfo['options'];
2768                    }
2769                    $this->addDecorator($decorator, $options);
2770                } else {
2771                    switch (true) {
2772                        case (0 == $argc):
2773                            break;
2774                        case (1 <= $argc):
2775                            $decorator  = array_shift($decoratorInfo);
2776                        case (2 <= $argc):
2777                            $options = array_shift($decoratorInfo);
2778                        default:
2779                            $this->addDecorator($decorator, $options);
2780                            break;
2781                    }
2782                }
2783            } else {
2784                throw new Zend_Form_Exception('Invalid decorator passed to addDecorators()');
2785            }
2786        }
2787
2788        return $this;
2789    }
2790
2791    /**
2792     * Overwrite all decorators
2793     *
2794     * @param  array $decorators
2795     * @return Zend_Form
2796     */
2797    public function setDecorators(array $decorators)
2798    {
2799        $this->clearDecorators();
2800        return $this->addDecorators($decorators);
2801    }
2802
2803    /**
2804     * Retrieve a registered decorator
2805     *
2806     * @param  string $name
2807     * @return false|Zend_Form_Decorator_Abstract
2808     */
2809    public function getDecorator($name)
2810    {
2811        if (!isset($this->_decorators[$name])) {
2812            $len = strlen($name);
2813            foreach ($this->_decorators as $localName => $decorator) {
2814                if ($len > strlen($localName)) {
2815                    continue;
2816                }
2817
2818                if (0 === substr_compare($localName, $name, -$len, $len, true)) {
2819                    if (is_array($decorator)) {
2820                        return $this->_loadDecorator($decorator, $localName);
2821                    }
2822                    return $decorator;
2823                }
2824            }
2825            return false;
2826        }
2827
2828        if (is_array($this->_decorators[$name])) {
2829            return $this->_loadDecorator($this->_decorators[$name], $name);
2830        }
2831
2832        return $this->_decorators[$name];
2833    }
2834
2835    /**
2836     * Retrieve all decorators
2837     *
2838     * @return array
2839     */
2840    public function getDecorators()
2841    {
2842        foreach ($this->_decorators as $key => $value) {
2843            if (is_array($value)) {
2844                $this->_loadDecorator($value, $key);
2845            }
2846        }
2847        return $this->_decorators;
2848    }
2849
2850    /**
2851     * Remove a single decorator
2852     *
2853     * @param  string $name
2854     * @return bool
2855     */
2856    public function removeDecorator($name)
2857    {
2858        $decorator = $this->getDecorator($name);
2859        if ($decorator) {
2860            if (array_key_exists($name, $this->_decorators)) {
2861                unset($this->_decorators[$name]);
2862            } else {
2863                $class = get_class($decorator);
2864                if (!array_key_exists($class, $this->_decorators)) {
2865                    return false;
2866                }
2867                unset($this->_decorators[$class]);
2868            }
2869            return true;
2870        }
2871
2872        return false;
2873    }
2874
2875    /**
2876     * Clear all decorators
2877     *
2878     * @return Zend_Form
2879     */
2880    public function clearDecorators()
2881    {
2882        $this->_decorators = array();
2883        return $this;
2884    }
2885
2886    /**
2887     * Set all element decorators as specified
2888     *
2889     * @param  array $decorators
2890     * @param  array|null $elements Specific elements to decorate or exclude from decoration
2891     * @param  bool $include Whether $elements is an inclusion or exclusion list
2892     * @return Zend_Form
2893     */
2894    public function setElementDecorators(array $decorators, array $elements = null, $include = true)
2895    {
2896        if (is_array($elements)) {
2897            if ($include) {
2898                $elementObjs = array();
2899                foreach ($elements as $name) {
2900                    if (null !== ($element = $this->getElement($name))) {
2901                        $elementObjs[] = $element;
2902                    }
2903                }
2904            } else {
2905                $elementObjs = $this->getElements();
2906                foreach ($elements as $name) {
2907                    if (array_key_exists($name, $elementObjs)) {
2908                        unset($elementObjs[$name]);
2909                    }
2910                }
2911            }
2912        } else {
2913            $elementObjs = $this->getElements();
2914        }
2915
2916        /** @var Zend_Form_Element $element */
2917        foreach ($elementObjs as $element) {
2918            $element->setDecorators($decorators);
2919        }
2920
2921        $this->_elementDecorators = $decorators;
2922
2923        return $this;
2924    }
2925
2926    /**
2927     * Set all display group decorators as specified
2928     *
2929     * @param  array $decorators
2930     * @return Zend_Form
2931     */
2932    public function setDisplayGroupDecorators(array $decorators)
2933    {
2934        /** @var Zend_Form_DisplayGroup $group */
2935        foreach ($this->getDisplayGroups() as $group) {
2936            $group->setDecorators($decorators);
2937        }
2938
2939        return $this;
2940    }
2941
2942    /**
2943     * Set all subform decorators as specified
2944     *
2945     * @param  array $decorators
2946     * @return Zend_Form
2947     */
2948    public function setSubFormDecorators(array $decorators)
2949    {
2950        /** @var Zend_Form_SubForm $form */
2951        foreach ($this->getSubForms() as $form) {
2952            $form->setDecorators($decorators);
2953        }
2954
2955        return $this;
2956    }
2957
2958    /**
2959     * Render form
2960     *
2961     * @param  Zend_View_Interface $view
2962     * @return string
2963     */
2964    public function render(Zend_View_Interface $view = null)
2965    {
2966        if (null !== $view) {
2967            $this->setView($view);
2968        }
2969
2970        $content = '';
2971        /** @var Zend_Form_Decorator_Abstract $decorator */
2972        foreach ($this->getDecorators() as $decorator) {
2973            $decorator->setElement($this);
2974            $content = $decorator->render($content);
2975        }
2976        $this->_setIsRendered();
2977        return $content;
2978    }
2979
2980    /**
2981     * Serialize as string
2982     *
2983     * Proxies to {@link render()}.
2984     *
2985     * @return string
2986     */
2987    public function __toString()
2988    {
2989        try {
2990            $return = $this->render();
2991            return $return;
2992        } catch (Exception $e) {
2993            $message = "Exception caught by form: " . $e->getMessage()
2994                     . "\nStack Trace:\n" . $e->getTraceAsString();
2995            trigger_error($message, E_USER_WARNING);
2996            return '';
2997        }
2998    }
2999
3000
3001    // Localization:
3002
3003    /**
3004     * Set translator object
3005     *
3006     * @param  Zend_Translate|Zend_Translate_Adapter|null $translator
3007     * @throws Zend_Form_Exception
3008     * @return Zend_Form
3009     */
3010    public function setTranslator($translator = null)
3011    {
3012        if (null === $translator) {
3013            $this->_translator = null;
3014        } elseif ($translator instanceof Zend_Translate_Adapter) {
3015            $this->_translator = $translator;
3016        } elseif ($translator instanceof Zend_Translate) {
3017            $this->_translator = $translator->getAdapter();
3018        } else {
3019            throw new Zend_Form_Exception('Invalid translator specified');
3020        }
3021
3022        return $this;
3023    }
3024
3025    /**
3026     * Set global default translator object
3027     *
3028     * @param  Zend_Translate|Zend_Translate_Adapter|null $translator
3029     * @throws Zend_Form_Exception
3030     * @return void
3031     */
3032    public static function setDefaultTranslator($translator = null)
3033    {
3034        if (null === $translator) {
3035            self::$_translatorDefault = null;
3036        } elseif ($translator instanceof Zend_Translate_Adapter) {
3037            self::$_translatorDefault = $translator;
3038        } elseif ($translator instanceof Zend_Translate) {
3039            self::$_translatorDefault = $translator->getAdapter();
3040        } else {
3041            throw new Zend_Form_Exception('Invalid translator specified');
3042        }
3043    }
3044
3045    /**
3046     * Retrieve translator object
3047     *
3048     * @return Zend_Translate|null
3049     */
3050    public function getTranslator()
3051    {
3052        if ($this->translatorIsDisabled()) {
3053            return null;
3054        }
3055
3056        if (null === $this->_translator) {
3057            return self::getDefaultTranslator();
3058        }
3059
3060        return $this->_translator;
3061    }
3062
3063    /**
3064     * Does this form have its own specific translator?
3065     *
3066     * @return bool
3067     */
3068    public function hasTranslator()
3069    {
3070        return (bool)$this->_translator;
3071    }
3072
3073    /**
3074     * Get global default translator object
3075     *
3076     * @return null|Zend_Translate
3077     */
3078    public static function getDefaultTranslator()
3079    {
3080        if (null === self::$_translatorDefault) {
3081            if (Zend_Registry::isRegistered('Zend_Translate')) {
3082                $translator = Zend_Registry::get('Zend_Translate');
3083                if ($translator instanceof Zend_Translate_Adapter) {
3084                    return $translator;
3085                } elseif ($translator instanceof Zend_Translate) {
3086                    return $translator->getAdapter();
3087                }
3088            }
3089        }
3090        return self::$_translatorDefault;
3091    }
3092
3093    /**
3094     * Is there a default translation object set?
3095     *
3096     * @return boolean
3097     */
3098    public static function hasDefaultTranslator()
3099    {
3100        return (bool)self::$_translatorDefault;
3101    }
3102
3103    /**
3104     * Indicate whether or not translation should be disabled
3105     *
3106     * @param  bool $flag
3107     * @return Zend_Form
3108     */
3109    public function setDisableTranslator($flag)
3110    {
3111        $this->_translatorDisabled = (bool) $flag;
3112        return $this;
3113    }
3114
3115    /**
3116     * Is translation disabled?
3117     *
3118     * @return bool
3119     */
3120    public function translatorIsDisabled()
3121    {
3122        return $this->_translatorDisabled;
3123    }
3124
3125    /**
3126     * Overloading: access to elements, form groups, and display groups
3127     *
3128     * @param  string $name
3129     * @return Zend_Form_Element|Zend_Form|null
3130     */
3131    public function __get($name)
3132    {
3133        if (isset($this->_elements[$name])) {
3134            return $this->_elements[$name];
3135        } elseif (isset($this->_subForms[$name])) {
3136            return $this->_subForms[$name];
3137        } elseif (isset($this->_displayGroups[$name])) {
3138            return $this->_displayGroups[$name];
3139        }
3140
3141        return null;
3142    }
3143
3144    /**
3145     * Overloading: access to elements, form groups, and display groups
3146     *
3147     * @param  string $name
3148     * @param  Zend_Form_Element|Zend_Form $value
3149     * @return void
3150     * @throws Zend_Form_Exception for invalid $value
3151     */
3152    public function __set($name, $value)
3153    {
3154        if ($value instanceof Zend_Form_Element) {
3155            $this->addElement($value, $name);
3156            return;
3157        } elseif ($value instanceof Zend_Form) {
3158            $this->addSubForm($value, $name);
3159            return;
3160        } elseif (is_array($value)) {
3161            $this->addDisplayGroup($value, $name);
3162            return;
3163        }
3164
3165        if (is_object($value)) {
3166            $type = get_class($value);
3167        } else {
3168            $type = gettype($value);
3169        }
3170        throw new Zend_Form_Exception('Only form elements and groups may be overloaded; variable of type "' . $type . '" provided');
3171    }
3172
3173    /**
3174     * Overloading: access to elements, form groups, and display groups
3175     *
3176     * @param  string $name
3177     * @return boolean
3178     */
3179    public function __isset($name)
3180    {
3181        if (isset($this->_elements[$name])
3182            || isset($this->_subForms[$name])
3183            || isset($this->_displayGroups[$name]))
3184        {
3185            return true;
3186        }
3187
3188        return false;
3189    }
3190
3191    /**
3192     * Overloading: access to elements, form groups, and display groups
3193     *
3194     * @param  string $name
3195     * @return void
3196     */
3197    public function __unset($name)
3198    {
3199        if (isset($this->_elements[$name])) {
3200            unset($this->_elements[$name]);
3201        } elseif (isset($this->_subForms[$name])) {
3202            unset($this->_subForms[$name]);
3203        } elseif (isset($this->_displayGroups[$name])) {
3204            unset($this->_displayGroups[$name]);
3205        }
3206    }
3207
3208    /**
3209     * Overloading: allow rendering specific decorators
3210     *
3211     * Call renderDecoratorName() to render a specific decorator.
3212     *
3213     * @param  string $method
3214     * @param  array $args
3215     * @return string
3216     * @throws Zend_Form_Exception for invalid decorator or invalid method call
3217     */
3218    public function __call($method, $args)
3219    {
3220        if ('render' == substr($method, 0, 6)) {
3221            $decoratorName = substr($method, 6);
3222            if (false !== ($decorator = $this->getDecorator($decoratorName))) {
3223                $decorator->setElement($this);
3224                $seed = '';
3225                if (0 < count($args)) {
3226                    $seed = array_shift($args);
3227                }
3228                if ($decoratorName === 'FormElements' ||
3229                    $decoratorName === 'PrepareElements') {
3230                        $this->_setIsRendered();
3231                }
3232                return $decorator->render($seed);
3233            }
3234
3235            throw new Zend_Form_Exception(sprintf('Decorator by name %s does not exist', $decoratorName));
3236        }
3237
3238        throw new Zend_Form_Exception(sprintf('Method %s does not exist', $method));
3239    }
3240
3241    // Interfaces: Iterator, Countable
3242
3243    /**
3244     * Current element/subform/display group
3245     *
3246     * @throws Zend_Form_Exception
3247     * @return Zend_Form_Element|Zend_Form_DisplayGroup|Zend_Form
3248     */
3249    public function current()
3250    {
3251        $this->_sort();
3252        current($this->_order);
3253        $key = key($this->_order);
3254
3255        if (isset($this->_elements[$key])) {
3256            return $this->getElement($key);
3257        } elseif (isset($this->_subForms[$key])) {
3258            return $this->getSubForm($key);
3259        } elseif (isset($this->_displayGroups[$key])) {
3260            return $this->getDisplayGroup($key);
3261        } else {
3262            throw new Zend_Form_Exception(sprintf('Corruption detected in form; invalid key ("%s") found in internal iterator', (string) $key));
3263        }
3264    }
3265
3266    /**
3267     * Current element/subform/display group name
3268     *
3269     * @return string
3270     */
3271    public function key()
3272    {
3273        $this->_sort();
3274        return key($this->_order);
3275    }
3276
3277    /**
3278     * Move pointer to next element/subform/display group
3279     *
3280     * @return void
3281     */
3282    public function next()
3283    {
3284        $this->_sort();
3285        next($this->_order);
3286    }
3287
3288    /**
3289     * Move pointer to beginning of element/subform/display group loop
3290     *
3291     * @return void
3292     */
3293    public function rewind()
3294    {
3295        $this->_sort();
3296        reset($this->_order);
3297    }
3298
3299    /**
3300     * Determine if current element/subform/display group is valid
3301     *
3302     * @return bool
3303     */
3304    public function valid()
3305    {
3306        $this->_sort();
3307        return (current($this->_order) !== false);
3308    }
3309
3310    /**
3311     * Count of elements/subforms that are iterable
3312     *
3313     * @return int
3314     */
3315    public function count()
3316    {
3317        return count($this->_order);
3318    }
3319
3320    /**
3321     * Set flag to disable loading default decorators
3322     *
3323     * @param  bool $flag
3324     * @return Zend_Form
3325     */
3326    public function setDisableLoadDefaultDecorators($flag)
3327    {
3328        $this->_disableLoadDefaultDecorators = (bool) $flag;
3329        return $this;
3330    }
3331
3332    /**
3333     * Should we load the default decorators?
3334     *
3335     * @return bool
3336     */
3337    public function loadDefaultDecoratorsIsDisabled()
3338    {
3339        return $this->_disableLoadDefaultDecorators;
3340    }
3341
3342    /**
3343     * Load the default decorators
3344     *
3345     * @return Zend_Form
3346     */
3347    public function loadDefaultDecorators()
3348    {
3349        if ($this->loadDefaultDecoratorsIsDisabled()) {
3350            return $this;
3351        }
3352
3353        $decorators = $this->getDecorators();
3354        if (empty($decorators)) {
3355            $this->addDecorator('FormElements')
3356                 ->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form'))
3357                 ->addDecorator('Form');
3358        }
3359        return $this;
3360    }
3361
3362    /**
3363     * Remove an element from iteration
3364     *
3365     * @param  string $name Element/group/form name
3366     * @return void
3367     */
3368    public function removeFromIteration($name)
3369    {
3370        if (array_key_exists($name, $this->_order)) {
3371            unset($this->_order[$name]);
3372            $this->_orderUpdated = true;
3373        }
3374    }
3375
3376    /**
3377     * Sort items according to their order
3378     *
3379     * @throws Zend_Form_Exception
3380     * @return void
3381     */
3382    protected function _sort()
3383    {
3384        if ($this->_orderUpdated) {
3385            $items = array();
3386            $index = 0;
3387            foreach ($this->_order as $key => $order) {
3388                if (null === $order) {
3389                    if (null === ($order = $this->{$key}->getOrder())) {
3390                        while (array_search($index, $this->_order, true)) {
3391                            ++$index;
3392                        }
3393                        $items[$index] = $key;
3394                        ++$index;
3395                    } else {
3396                        $items[$order] = $key;
3397                    }
3398                } elseif (isset($items[$order]) && $items[$order] !== $key) {
3399                    throw new Zend_Form_Exception('Form elements ' .
3400                        $items[$order] . ' and ' . $key .
3401                        ' have the same order (' .
3402                        $order . ') - ' .
3403                        'this would result in only the last added element to be rendered'
3404                    );
3405                } else {
3406                    $items[$order] = $key;
3407                }
3408            }
3409
3410            $items = array_flip($items);
3411            asort($items);
3412            $this->_order = $items;
3413            $this->_orderUpdated = false;
3414        }
3415    }
3416
3417    /**
3418     * Lazy-load a decorator
3419     *
3420     * @param  array $decorator Decorator type and options
3421     * @param  mixed $name Decorator name or alias
3422     * @return Zend_Form_Decorator_Interface
3423     */
3424    protected function _loadDecorator(array $decorator, $name)
3425    {
3426        $sameName = false;
3427        if ($name == $decorator['decorator']) {
3428            $sameName = true;
3429        }
3430
3431        $instance = $this->_getDecorator($decorator['decorator'], $decorator['options']);
3432        if ($sameName) {
3433            $newName            = get_class($instance);
3434            $decoratorNames     = array_keys($this->_decorators);
3435            $order              = array_flip($decoratorNames);
3436            $order[$newName]    = $order[$name];
3437            $decoratorsExchange = array();
3438            unset($order[$name]);
3439            asort($order);
3440            foreach ($order as $key => $index) {
3441                if ($key == $newName) {
3442                    $decoratorsExchange[$key] = $instance;
3443                    continue;
3444                }
3445                $decoratorsExchange[$key] = $this->_decorators[$key];
3446            }
3447            $this->_decorators = $decoratorsExchange;
3448        } else {
3449            $this->_decorators[$name] = $instance;
3450        }
3451
3452        return $instance;
3453    }
3454
3455    /**
3456     * Retrieve optionally translated custom error messages
3457     *
3458     * @return array
3459     */
3460    protected function _getErrorMessages()
3461    {
3462        $messages   = $this->getErrorMessages();
3463        $translator = $this->getTranslator();
3464        if (null !== $translator) {
3465            foreach ($messages as $key => $message) {
3466                $messages[$key] = $translator->translate($message);
3467            }
3468        }
3469        return $messages;
3470    }
3471}
3472