xref: /dokuwiki/inc/Form/Form.php (revision 64159a61e94d0ce680071c8890e144982c3a8cbe)
112a4e4d1SAndreas Gohr<?php
212a4e4d1SAndreas Gohrnamespace dokuwiki\Form;
312a4e4d1SAndreas Gohr
412a4e4d1SAndreas Gohr/**
512a4e4d1SAndreas Gohr * Class Form
612a4e4d1SAndreas Gohr *
712a4e4d1SAndreas Gohr * Represents the whole Form. This is what you work on, and add Elements to
812a4e4d1SAndreas Gohr *
912a4e4d1SAndreas Gohr * @package dokuwiki\Form
1012a4e4d1SAndreas Gohr */
1112a4e4d1SAndreas Gohrclass Form extends Element {
1212a4e4d1SAndreas Gohr
1312a4e4d1SAndreas Gohr    /**
1412a4e4d1SAndreas Gohr     * @var array name value pairs for hidden values
1512a4e4d1SAndreas Gohr     */
1612a4e4d1SAndreas Gohr    protected $hidden = array();
1712a4e4d1SAndreas Gohr
1812a4e4d1SAndreas Gohr    /**
1912a4e4d1SAndreas Gohr     * @var Element[] the elements of the form
2012a4e4d1SAndreas Gohr     */
2112a4e4d1SAndreas Gohr    protected $elements = array();
2212a4e4d1SAndreas Gohr
2312a4e4d1SAndreas Gohr    /**
2412a4e4d1SAndreas Gohr     * Creates a new, empty form with some default attributes
2512a4e4d1SAndreas Gohr     *
2612a4e4d1SAndreas Gohr     * @param array $attributes
277fa270bcSMichael Große     * @param bool  $unsafe     if true, then the security token is ommited
2812a4e4d1SAndreas Gohr     */
297fa270bcSMichael Große    public function __construct($attributes = array(), $unsafe = false) {
3012a4e4d1SAndreas Gohr        global $ID;
3112a4e4d1SAndreas Gohr
3212a4e4d1SAndreas Gohr        parent::__construct('form', $attributes);
3312a4e4d1SAndreas Gohr
3412a4e4d1SAndreas Gohr        // use the current URL as default action
3512a4e4d1SAndreas Gohr        if(!$this->attr('action')) {
3612a4e4d1SAndreas Gohr            $get = $_GET;
3712a4e4d1SAndreas Gohr            if(isset($get['id'])) unset($get['id']);
386d0ceaf9SAndreas Gohr            $self = wl($ID, $get, false, '&'); //attributes are escaped later
3912a4e4d1SAndreas Gohr            $this->attr('action', $self);
4012a4e4d1SAndreas Gohr        }
4112a4e4d1SAndreas Gohr
4212a4e4d1SAndreas Gohr        // post is default
4312a4e4d1SAndreas Gohr        if(!$this->attr('method')) {
4412a4e4d1SAndreas Gohr            $this->attr('method', 'post');
4512a4e4d1SAndreas Gohr        }
4612a4e4d1SAndreas Gohr
4712a4e4d1SAndreas Gohr        // we like UTF-8
4812a4e4d1SAndreas Gohr        if(!$this->attr('accept-charset')) {
4912a4e4d1SAndreas Gohr            $this->attr('accept-charset', 'utf-8');
5012a4e4d1SAndreas Gohr        }
5112a4e4d1SAndreas Gohr
5212a4e4d1SAndreas Gohr        // add the security token by default
537fa270bcSMichael Große        if (!$unsafe) {
5412a4e4d1SAndreas Gohr            $this->setHiddenField('sectok', getSecurityToken());
557fa270bcSMichael Große        }
5612a4e4d1SAndreas Gohr
576d0ceaf9SAndreas Gohr        // identify this as a new form based form in HTML
586d0ceaf9SAndreas Gohr        $this->addClass('doku_form');
5912a4e4d1SAndreas Gohr    }
6012a4e4d1SAndreas Gohr
6112a4e4d1SAndreas Gohr    /**
6212a4e4d1SAndreas Gohr     * Sets a hidden field
6312a4e4d1SAndreas Gohr     *
647ec97767SGerrit Uitslag     * @param string $name
657ec97767SGerrit Uitslag     * @param string $value
6612a4e4d1SAndreas Gohr     * @return $this
6712a4e4d1SAndreas Gohr     */
6812a4e4d1SAndreas Gohr    public function setHiddenField($name, $value) {
6912a4e4d1SAndreas Gohr        $this->hidden[$name] = $value;
7012a4e4d1SAndreas Gohr        return $this;
7112a4e4d1SAndreas Gohr    }
7212a4e4d1SAndreas Gohr
73ef0c211bSAndreas Gohr    #region element query function
7464744a10SAndreas Gohr
7512a4e4d1SAndreas Gohr    /**
76ef0c211bSAndreas Gohr     * Returns the numbers of elements in the form
77ef0c211bSAndreas Gohr     *
78ef0c211bSAndreas Gohr     * @return int
79ef0c211bSAndreas Gohr     */
80ef0c211bSAndreas Gohr    public function elementCount() {
81ef0c211bSAndreas Gohr        return count($this->elements);
82ef0c211bSAndreas Gohr    }
83ef0c211bSAndreas Gohr
84ef0c211bSAndreas Gohr    /**
855facb9bcSMichael Große     * Get the position of the element in the form or false if it is not in the form
865facb9bcSMichael Große     *
87*64159a61SAndreas Gohr     * Warning: This function may return Boolean FALSE, but may also return a non-Boolean value which evaluates
88*64159a61SAndreas Gohr     * to FALSE. Please read the section on Booleans for more information. Use the === operator for testing the
89*64159a61SAndreas Gohr     * return value of this function.
905facb9bcSMichael Große     *
915facb9bcSMichael Große     * @param Element $element
925facb9bcSMichael Große     *
935facb9bcSMichael Große     * @return false|int
945facb9bcSMichael Große     */
955facb9bcSMichael Große    public function getElementPosition(Element $element)
965facb9bcSMichael Große    {
975facb9bcSMichael Große        return array_search($element, $this->elements, true);
985facb9bcSMichael Große    }
995facb9bcSMichael Große
1005facb9bcSMichael Große    /**
101ef0c211bSAndreas Gohr     * Returns a reference to the element at a position.
102ef0c211bSAndreas Gohr     * A position out-of-bounds will return either the
103ef0c211bSAndreas Gohr     * first (underflow) or last (overflow) element.
104ef0c211bSAndreas Gohr     *
1057ec97767SGerrit Uitslag     * @param int $pos
106ef0c211bSAndreas Gohr     * @return Element
107ef0c211bSAndreas Gohr     */
108ef0c211bSAndreas Gohr    public function getElementAt($pos) {
109ef0c211bSAndreas Gohr        if($pos < 0) $pos = count($this->elements) + $pos;
110ef0c211bSAndreas Gohr        if($pos < 0) $pos = 0;
111ef0c211bSAndreas Gohr        if($pos >= count($this->elements)) $pos = count($this->elements) - 1;
112ef0c211bSAndreas Gohr        return $this->elements[$pos];
113ef0c211bSAndreas Gohr    }
114ef0c211bSAndreas Gohr
115ef0c211bSAndreas Gohr    /**
116ef0c211bSAndreas Gohr     * Gets the position of the first of a type of element
117ef0c211bSAndreas Gohr     *
118ef0c211bSAndreas Gohr     * @param string $type Element type to look for.
119ef0c211bSAndreas Gohr     * @param int $offset search from this position onward
120ef0c211bSAndreas Gohr     * @return false|int position of element if found, otherwise false
121ef0c211bSAndreas Gohr     */
122ef0c211bSAndreas Gohr    public function findPositionByType($type, $offset = 0) {
123ef0c211bSAndreas Gohr        $len = $this->elementCount();
124ef0c211bSAndreas Gohr        for($pos = $offset; $pos < $len; $pos++) {
125ef0c211bSAndreas Gohr            if($this->elements[$pos]->getType() == $type) {
126ef0c211bSAndreas Gohr                return $pos;
127ef0c211bSAndreas Gohr            }
128ef0c211bSAndreas Gohr        }
129ef0c211bSAndreas Gohr        return false;
130ef0c211bSAndreas Gohr    }
131ef0c211bSAndreas Gohr
132ef0c211bSAndreas Gohr    /**
133ef0c211bSAndreas Gohr     * Gets the position of the first element matching the attribute
134ef0c211bSAndreas Gohr     *
135ef0c211bSAndreas Gohr     * @param string $name Name of the attribute
136ef0c211bSAndreas Gohr     * @param string $value Value the attribute should have
137ef0c211bSAndreas Gohr     * @param int $offset search from this position onward
138ef0c211bSAndreas Gohr     * @return false|int position of element if found, otherwise false
139ef0c211bSAndreas Gohr     */
140ef0c211bSAndreas Gohr    public function findPositionByAttribute($name, $value, $offset = 0) {
141ef0c211bSAndreas Gohr        $len = $this->elementCount();
142ef0c211bSAndreas Gohr        for($pos = $offset; $pos < $len; $pos++) {
143ef0c211bSAndreas Gohr            if($this->elements[$pos]->attr($name) == $value) {
144ef0c211bSAndreas Gohr                return $pos;
145ef0c211bSAndreas Gohr            }
146ef0c211bSAndreas Gohr        }
147ef0c211bSAndreas Gohr        return false;
148ef0c211bSAndreas Gohr    }
149ef0c211bSAndreas Gohr
150ef0c211bSAndreas Gohr    #endregion
151ef0c211bSAndreas Gohr
152ef0c211bSAndreas Gohr    #region Element positioning functions
153ef0c211bSAndreas Gohr
154ef0c211bSAndreas Gohr    /**
155ef0c211bSAndreas Gohr     * Adds or inserts an element to the form
15612a4e4d1SAndreas Gohr     *
15712a4e4d1SAndreas Gohr     * @param Element $element
15812a4e4d1SAndreas Gohr     * @param int $pos 0-based position in the form, -1 for at the end
15912a4e4d1SAndreas Gohr     * @return Element
16012a4e4d1SAndreas Gohr     */
16112a4e4d1SAndreas Gohr    public function addElement(Element $element, $pos = -1) {
162*64159a61SAndreas Gohr        if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
163*64159a61SAndreas Gohr            'You can\'t add a form to a form'
164*64159a61SAndreas Gohr        );
16512a4e4d1SAndreas Gohr        if($pos < 0) {
16612a4e4d1SAndreas Gohr            $this->elements[] = $element;
16712a4e4d1SAndreas Gohr        } else {
16812a4e4d1SAndreas Gohr            array_splice($this->elements, $pos, 0, array($element));
16912a4e4d1SAndreas Gohr        }
17012a4e4d1SAndreas Gohr        return $element;
17112a4e4d1SAndreas Gohr    }
17212a4e4d1SAndreas Gohr
17312a4e4d1SAndreas Gohr    /**
174ef0c211bSAndreas Gohr     * Replaces an existing element with a new one
175ef0c211bSAndreas Gohr     *
176ef0c211bSAndreas Gohr     * @param Element $element the new element
1777ec97767SGerrit Uitslag     * @param int $pos 0-based position of the element to replace
178ef0c211bSAndreas Gohr     */
179ef0c211bSAndreas Gohr    public function replaceElement(Element $element, $pos) {
180*64159a61SAndreas Gohr        if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
181*64159a61SAndreas Gohr            'You can\'t add a form to a form'
182*64159a61SAndreas Gohr        );
183ef0c211bSAndreas Gohr        array_splice($this->elements, $pos, 1, array($element));
184ef0c211bSAndreas Gohr    }
185ef0c211bSAndreas Gohr
186ef0c211bSAndreas Gohr    /**
187ef0c211bSAndreas Gohr     * Remove an element from the form completely
188ef0c211bSAndreas Gohr     *
1897ec97767SGerrit Uitslag     * @param int $pos 0-based position of the element to remove
190ef0c211bSAndreas Gohr     */
191ef0c211bSAndreas Gohr    public function removeElement($pos) {
192ef0c211bSAndreas Gohr        array_splice($this->elements, $pos, 1);
193ef0c211bSAndreas Gohr    }
194ef0c211bSAndreas Gohr
195ef0c211bSAndreas Gohr    #endregion
196ef0c211bSAndreas Gohr
197ef0c211bSAndreas Gohr    #region Element adding functions
198ef0c211bSAndreas Gohr
199ef0c211bSAndreas Gohr    /**
20012a4e4d1SAndreas Gohr     * Adds a text input field
20112a4e4d1SAndreas Gohr     *
2027ec97767SGerrit Uitslag     * @param string $name
2037ec97767SGerrit Uitslag     * @param string $label
20412a4e4d1SAndreas Gohr     * @param int $pos
20512a4e4d1SAndreas Gohr     * @return InputElement
20612a4e4d1SAndreas Gohr     */
207de19515fSAndreas Gohr    public function addTextInput($name, $label = '', $pos = -1) {
20812a4e4d1SAndreas Gohr        return $this->addElement(new InputElement('text', $name, $label), $pos);
20912a4e4d1SAndreas Gohr    }
21012a4e4d1SAndreas Gohr
21112a4e4d1SAndreas Gohr    /**
21212a4e4d1SAndreas Gohr     * Adds a password input field
21312a4e4d1SAndreas Gohr     *
2147ec97767SGerrit Uitslag     * @param string $name
2157ec97767SGerrit Uitslag     * @param string $label
21612a4e4d1SAndreas Gohr     * @param int $pos
21712a4e4d1SAndreas Gohr     * @return InputElement
21812a4e4d1SAndreas Gohr     */
219de19515fSAndreas Gohr    public function addPasswordInput($name, $label = '', $pos = -1) {
22012a4e4d1SAndreas Gohr        return $this->addElement(new InputElement('password', $name, $label), $pos);
22112a4e4d1SAndreas Gohr    }
22212a4e4d1SAndreas Gohr
22312a4e4d1SAndreas Gohr    /**
22412a4e4d1SAndreas Gohr     * Adds a radio button field
22512a4e4d1SAndreas Gohr     *
2267ec97767SGerrit Uitslag     * @param string $name
2277ec97767SGerrit Uitslag     * @param string $label
22812a4e4d1SAndreas Gohr     * @param int $pos
22912a4e4d1SAndreas Gohr     * @return CheckableElement
23012a4e4d1SAndreas Gohr     */
231de19515fSAndreas Gohr    public function addRadioButton($name, $label = '', $pos = -1) {
23212a4e4d1SAndreas Gohr        return $this->addElement(new CheckableElement('radio', $name, $label), $pos);
23312a4e4d1SAndreas Gohr    }
23412a4e4d1SAndreas Gohr
23512a4e4d1SAndreas Gohr    /**
23612a4e4d1SAndreas Gohr     * Adds a checkbox field
23712a4e4d1SAndreas Gohr     *
2387ec97767SGerrit Uitslag     * @param string $name
2397ec97767SGerrit Uitslag     * @param string $label
24012a4e4d1SAndreas Gohr     * @param int $pos
24112a4e4d1SAndreas Gohr     * @return CheckableElement
24212a4e4d1SAndreas Gohr     */
243de19515fSAndreas Gohr    public function addCheckbox($name, $label = '', $pos = -1) {
24412a4e4d1SAndreas Gohr        return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
24512a4e4d1SAndreas Gohr    }
24612a4e4d1SAndreas Gohr
24712a4e4d1SAndreas Gohr    /**
2488638ead5SAndreas Gohr     * Adds a dropdown field
2498638ead5SAndreas Gohr     *
2507ec97767SGerrit Uitslag     * @param string $name
2518638ead5SAndreas Gohr     * @param array $options
2528638ead5SAndreas Gohr     * @param string $label
2538638ead5SAndreas Gohr     * @param int $pos
2548638ead5SAndreas Gohr     * @return DropdownElement
2558638ead5SAndreas Gohr     */
2568638ead5SAndreas Gohr    public function addDropdown($name, $options, $label = '', $pos = -1) {
2578638ead5SAndreas Gohr        return $this->addElement(new DropdownElement($name, $options, $label), $pos);
2588638ead5SAndreas Gohr    }
2598638ead5SAndreas Gohr
2608638ead5SAndreas Gohr    /**
26112a4e4d1SAndreas Gohr     * Adds a textarea field
26212a4e4d1SAndreas Gohr     *
2637ec97767SGerrit Uitslag     * @param string $name
2647ec97767SGerrit Uitslag     * @param string $label
26512a4e4d1SAndreas Gohr     * @param int $pos
26612a4e4d1SAndreas Gohr     * @return TextareaElement
26712a4e4d1SAndreas Gohr     */
268de19515fSAndreas Gohr    public function addTextarea($name, $label = '', $pos = -1) {
26912a4e4d1SAndreas Gohr        return $this->addElement(new TextareaElement($name, $label), $pos);
27012a4e4d1SAndreas Gohr    }
27112a4e4d1SAndreas Gohr
27212a4e4d1SAndreas Gohr    /**
2738f0df229SAndreas Gohr     * Adds a simple button, escapes the content for you
2748f0df229SAndreas Gohr     *
2758f0df229SAndreas Gohr     * @param string $name
2768f0df229SAndreas Gohr     * @param string $content
2778f0df229SAndreas Gohr     * @param int $pos
2788f0df229SAndreas Gohr     * @return Element
2798f0df229SAndreas Gohr     */
2808f0df229SAndreas Gohr    public function addButton($name, $content, $pos = -1) {
2818f0df229SAndreas Gohr        return $this->addElement(new ButtonElement($name, hsc($content)), $pos);
2828f0df229SAndreas Gohr    }
2838f0df229SAndreas Gohr
2848f0df229SAndreas Gohr    /**
2858f0df229SAndreas Gohr     * Adds a simple button, allows HTML for content
2868f0df229SAndreas Gohr     *
2878f0df229SAndreas Gohr     * @param string $name
2888f0df229SAndreas Gohr     * @param string $html
2898f0df229SAndreas Gohr     * @param int $pos
2908f0df229SAndreas Gohr     * @return Element
2918f0df229SAndreas Gohr     */
2928f0df229SAndreas Gohr    public function addButtonHTML($name, $html, $pos = -1) {
2938f0df229SAndreas Gohr        return $this->addElement(new ButtonElement($name, $html), $pos);
2948f0df229SAndreas Gohr    }
2958f0df229SAndreas Gohr
2968f0df229SAndreas Gohr    /**
297a453c16bSAndreas Gohr     * Adds a label referencing another input element, escapes the label for you
298a453c16bSAndreas Gohr     *
2997ec97767SGerrit Uitslag     * @param string $label
300a453c16bSAndreas Gohr     * @param string $for
301a453c16bSAndreas Gohr     * @param int $pos
302a453c16bSAndreas Gohr     * @return Element
303a453c16bSAndreas Gohr     */
304a453c16bSAndreas Gohr    public function addLabel($label, $for='', $pos = -1) {
305a453c16bSAndreas Gohr        return $this->addLabelHTML(hsc($label), $for, $pos);
306a453c16bSAndreas Gohr    }
307a453c16bSAndreas Gohr
308a453c16bSAndreas Gohr    /**
309a453c16bSAndreas Gohr     * Adds a label referencing another input element, allows HTML for content
310a453c16bSAndreas Gohr     *
311a453c16bSAndreas Gohr     * @param string $content
312a453c16bSAndreas Gohr     * @param string|Element $for
313a453c16bSAndreas Gohr     * @param int $pos
314a453c16bSAndreas Gohr     * @return Element
315a453c16bSAndreas Gohr     */
316a453c16bSAndreas Gohr    public function addLabelHTML($content, $for='', $pos = -1) {
317a453c16bSAndreas Gohr        $element = new LabelElement(hsc($content));
318a453c16bSAndreas Gohr
319a453c16bSAndreas Gohr        if(is_a($for, '\dokuwiki\Form\Element')) {
320a453c16bSAndreas Gohr            /** @var Element $for */
321a453c16bSAndreas Gohr            $for = $for->id();
322a453c16bSAndreas Gohr        }
323a453c16bSAndreas Gohr        $for = (string) $for;
324a453c16bSAndreas Gohr        if($for !== '') {
325a453c16bSAndreas Gohr            $element->attr('for', $for);
326a453c16bSAndreas Gohr        }
327a453c16bSAndreas Gohr
328a453c16bSAndreas Gohr        return $this->addElement($element, $pos);
329a453c16bSAndreas Gohr    }
330a453c16bSAndreas Gohr
331a453c16bSAndreas Gohr    /**
332de19515fSAndreas Gohr     * Add fixed HTML to the form
333de19515fSAndreas Gohr     *
3347ec97767SGerrit Uitslag     * @param string $html
335de19515fSAndreas Gohr     * @param int $pos
33664744a10SAndreas Gohr     * @return HTMLElement
337de19515fSAndreas Gohr     */
338de19515fSAndreas Gohr    public function addHTML($html, $pos = -1) {
339de19515fSAndreas Gohr        return $this->addElement(new HTMLElement($html), $pos);
340de19515fSAndreas Gohr    }
341de19515fSAndreas Gohr
34264744a10SAndreas Gohr    /**
34364744a10SAndreas Gohr     * Add a closed HTML tag to the form
34464744a10SAndreas Gohr     *
3457ec97767SGerrit Uitslag     * @param string $tag
34664744a10SAndreas Gohr     * @param int $pos
34764744a10SAndreas Gohr     * @return TagElement
34864744a10SAndreas Gohr     */
34964744a10SAndreas Gohr    public function addTag($tag, $pos = -1) {
35064744a10SAndreas Gohr        return $this->addElement(new TagElement($tag), $pos);
35164744a10SAndreas Gohr    }
35264744a10SAndreas Gohr
35364744a10SAndreas Gohr    /**
35464744a10SAndreas Gohr     * Add an open HTML tag to the form
35564744a10SAndreas Gohr     *
35664744a10SAndreas Gohr     * Be sure to close it again!
35764744a10SAndreas Gohr     *
3587ec97767SGerrit Uitslag     * @param string $tag
35964744a10SAndreas Gohr     * @param int $pos
36064744a10SAndreas Gohr     * @return TagOpenElement
36164744a10SAndreas Gohr     */
36264744a10SAndreas Gohr    public function addTagOpen($tag, $pos = -1) {
36364744a10SAndreas Gohr        return $this->addElement(new TagOpenElement($tag), $pos);
36464744a10SAndreas Gohr    }
36564744a10SAndreas Gohr
36664744a10SAndreas Gohr    /**
36764744a10SAndreas Gohr     * Add a closing HTML tag to the form
36864744a10SAndreas Gohr     *
36964744a10SAndreas Gohr     * Be sure it had been opened before
37064744a10SAndreas Gohr     *
3717ec97767SGerrit Uitslag     * @param string $tag
37264744a10SAndreas Gohr     * @param int $pos
37364744a10SAndreas Gohr     * @return TagCloseElement
37464744a10SAndreas Gohr     */
37564744a10SAndreas Gohr    public function addTagClose($tag, $pos = -1) {
37664744a10SAndreas Gohr        return $this->addElement(new TagCloseElement($tag), $pos);
37764744a10SAndreas Gohr    }
37864744a10SAndreas Gohr
37964744a10SAndreas Gohr    /**
38064744a10SAndreas Gohr     * Open a Fieldset
38164744a10SAndreas Gohr     *
3827ec97767SGerrit Uitslag     * @param string $legend
38364744a10SAndreas Gohr     * @param int $pos
38464744a10SAndreas Gohr     * @return FieldsetOpenElement
38564744a10SAndreas Gohr     */
38664744a10SAndreas Gohr    public function addFieldsetOpen($legend = '', $pos = -1) {
38764744a10SAndreas Gohr        return $this->addElement(new FieldsetOpenElement($legend), $pos);
38864744a10SAndreas Gohr    }
38964744a10SAndreas Gohr
39064744a10SAndreas Gohr    /**
39164744a10SAndreas Gohr     * Close a fieldset
39264744a10SAndreas Gohr     *
39364744a10SAndreas Gohr     * @param int $pos
39464744a10SAndreas Gohr     * @return TagCloseElement
39564744a10SAndreas Gohr     */
39664744a10SAndreas Gohr    public function addFieldsetClose($pos = -1) {
39764744a10SAndreas Gohr        return $this->addElement(new FieldsetCloseElement(), $pos);
39864744a10SAndreas Gohr    }
39964744a10SAndreas Gohr
40064744a10SAndreas Gohr    #endregion
40164744a10SAndreas Gohr
4021f5d8b65SAndreas Gohr    /**
4031f5d8b65SAndreas Gohr     * Adjust the elements so that fieldset open and closes are matching
4041f5d8b65SAndreas Gohr     */
405de19515fSAndreas Gohr    protected function balanceFieldsets() {
4061f5d8b65SAndreas Gohr        $lastclose = 0;
4071f5d8b65SAndreas Gohr        $isopen = false;
4081f5d8b65SAndreas Gohr        $len = count($this->elements);
4091f5d8b65SAndreas Gohr
4101f5d8b65SAndreas Gohr        for($pos = 0; $pos < $len; $pos++) {
4111f5d8b65SAndreas Gohr            $type = $this->elements[$pos]->getType();
4121f5d8b65SAndreas Gohr            if($type == 'fieldsetopen') {
4131f5d8b65SAndreas Gohr                if($isopen) {
41480b13baaSGerrit Uitslag                    //close previous fieldset
4151f5d8b65SAndreas Gohr                    $this->addFieldsetClose($pos);
4161f5d8b65SAndreas Gohr                    $lastclose = $pos + 1;
4171f5d8b65SAndreas Gohr                    $pos++;
4181f5d8b65SAndreas Gohr                    $len++;
4191f5d8b65SAndreas Gohr                }
4201f5d8b65SAndreas Gohr                $isopen = true;
4211f5d8b65SAndreas Gohr            } else if($type == 'fieldsetclose') {
4221f5d8b65SAndreas Gohr                if(!$isopen) {
4231f5d8b65SAndreas Gohr                    // make sure there was a fieldsetopen
4241f5d8b65SAndreas Gohr                    // either right after the last close or at the begining
4251f5d8b65SAndreas Gohr                    $this->addFieldsetOpen('', $lastclose);
4261f5d8b65SAndreas Gohr                    $len++;
4271f5d8b65SAndreas Gohr                    $pos++;
4281f5d8b65SAndreas Gohr                }
4291f5d8b65SAndreas Gohr                $lastclose = $pos;
4301f5d8b65SAndreas Gohr                $isopen = false;
4311f5d8b65SAndreas Gohr            }
4321f5d8b65SAndreas Gohr        }
4331f5d8b65SAndreas Gohr
4341f5d8b65SAndreas Gohr        // close open fieldset at the end
4351f5d8b65SAndreas Gohr        if($isopen) {
4361f5d8b65SAndreas Gohr            $this->addFieldsetClose();
4371f5d8b65SAndreas Gohr        }
438de19515fSAndreas Gohr    }
439de19515fSAndreas Gohr
440de19515fSAndreas Gohr    /**
44112a4e4d1SAndreas Gohr     * The HTML representation of the whole form
44212a4e4d1SAndreas Gohr     *
44312a4e4d1SAndreas Gohr     * @return string
44412a4e4d1SAndreas Gohr     */
44512a4e4d1SAndreas Gohr    public function toHTML() {
446de19515fSAndreas Gohr        $this->balanceFieldsets();
447de19515fSAndreas Gohr
44801e3f2b3SMichael Große        $html = '<form ' . buildAttributes($this->attrs()) . '>';
44912a4e4d1SAndreas Gohr
45012a4e4d1SAndreas Gohr        foreach($this->hidden as $name => $value) {
45101e3f2b3SMichael Große            $html .= '<input type="hidden" name="' . $name . '" value="' . formText($value) . '" />';
45212a4e4d1SAndreas Gohr        }
45312a4e4d1SAndreas Gohr
45412a4e4d1SAndreas Gohr        foreach($this->elements as $element) {
45501e3f2b3SMichael Große            $html .= $element->toHTML();
45612a4e4d1SAndreas Gohr        }
45712a4e4d1SAndreas Gohr
45801e3f2b3SMichael Große        $html .= '</form>';
45912a4e4d1SAndreas Gohr
46012a4e4d1SAndreas Gohr        return $html;
46112a4e4d1SAndreas Gohr    }
46212a4e4d1SAndreas Gohr}
463