xref: /dokuwiki/inc/Form/Form.php (revision 1f5d8b65a983fe0914971ee0bb4e5e58cbf8c8a7)
1<?php
2namespace dokuwiki\Form;
3
4/**
5 * Class Form
6 *
7 * Represents the whole Form. This is what you work on, and add Elements to
8 *
9 * @package dokuwiki\Form
10 */
11class Form extends Element {
12
13    /**
14     * @var array name value pairs for hidden values
15     */
16    protected $hidden = array();
17
18    /**
19     * @var Element[] the elements of the form
20     */
21    protected $elements = array();
22
23    /**
24     * Creates a new, empty form with some default attributes
25     *
26     * @param array $attributes
27     */
28    public function __construct($attributes = array()) {
29        global $ID;
30
31        parent::__construct('form', $attributes);
32
33        // use the current URL as default action
34        if(!$this->attr('action')) {
35            $get = $_GET;
36            if(isset($get['id'])) unset($get['id']);
37            $self = wl($ID, $get, false, '&'); //attributes are escaped later
38            $this->attr('action', $self);
39        }
40
41        // post is default
42        if(!$this->attr('method')) {
43            $this->attr('method', 'post');
44        }
45
46        // we like UTF-8
47        if(!$this->attr('accept-charset')) {
48            $this->attr('accept-charset', 'utf-8');
49        }
50
51        // add the security token by default
52        $this->setHiddenField('sectok', getSecurityToken());
53
54        // identify this as a new form based form in HTML
55        $this->addClass('doku_form');
56    }
57
58    /**
59     * Sets a hidden field
60     *
61     * @param $name
62     * @param $value
63     * @return $this
64     */
65    public function setHiddenField($name, $value) {
66        $this->hidden[$name] = $value;
67        return $this;
68    }
69
70    #region Element adding functions
71
72    /**
73     * Adds an element to the end of the form
74     *
75     * @param Element $element
76     * @param int $pos 0-based position in the form, -1 for at the end
77     * @return Element
78     */
79    public function addElement(Element $element, $pos = -1) {
80        if(is_a($element, 'Doku_Form2')) throw new \InvalidArgumentException('You can\'t add a form to a form');
81        if($pos < 0) {
82            $this->elements[] = $element;
83        } else {
84            array_splice($this->elements, $pos, 0, array($element));
85        }
86        return $element;
87    }
88
89    /**
90     * Adds a text input field
91     *
92     * @param $name
93     * @param $label
94     * @param int $pos
95     * @return InputElement
96     */
97    public function addTextInput($name, $label = '', $pos = -1) {
98        return $this->addElement(new InputElement('text', $name, $label), $pos);
99    }
100
101    /**
102     * Adds a password input field
103     *
104     * @param $name
105     * @param $label
106     * @param int $pos
107     * @return InputElement
108     */
109    public function addPasswordInput($name, $label = '', $pos = -1) {
110        return $this->addElement(new InputElement('password', $name, $label), $pos);
111    }
112
113    /**
114     * Adds a radio button field
115     *
116     * @param $name
117     * @param $label
118     * @param int $pos
119     * @return CheckableElement
120     */
121    public function addRadioButton($name, $label = '', $pos = -1) {
122        return $this->addElement(new CheckableElement('radio', $name, $label), $pos);
123    }
124
125    /**
126     * Adds a checkbox field
127     *
128     * @param $name
129     * @param $label
130     * @param int $pos
131     * @return CheckableElement
132     */
133    public function addCheckbox($name, $label = '', $pos = -1) {
134        return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
135    }
136
137    /**
138     * Adds a textarea field
139     *
140     * @param $name
141     * @param $label
142     * @param int $pos
143     * @return TextareaElement
144     */
145    public function addTextarea($name, $label = '', $pos = -1) {
146        return $this->addElement(new TextareaElement($name, $label), $pos);
147    }
148
149    /**
150     * Add fixed HTML to the form
151     *
152     * @param $html
153     * @param int $pos
154     * @return HTMLElement
155     */
156    public function addHTML($html, $pos = -1) {
157        return $this->addElement(new HTMLElement($html), $pos);
158    }
159
160    /**
161     * Add a closed HTML tag to the form
162     *
163     * @param $tag
164     * @param int $pos
165     * @return TagElement
166     */
167    public function addTag($tag, $pos = -1) {
168        return $this->addElement(new TagElement($tag), $pos);
169    }
170
171    /**
172     * Add an open HTML tag to the form
173     *
174     * Be sure to close it again!
175     *
176     * @param $tag
177     * @param int $pos
178     * @return TagOpenElement
179     */
180    public function addTagOpen($tag, $pos = -1) {
181        return $this->addElement(new TagOpenElement($tag), $pos);
182    }
183
184    /**
185     * Add a closing HTML tag to the form
186     *
187     * Be sure it had been opened before
188     *
189     * @param $tag
190     * @param int $pos
191     * @return TagCloseElement
192     */
193    public function addTagClose($tag, $pos = -1) {
194        return $this->addElement(new TagCloseElement($tag), $pos);
195    }
196
197    /**
198     * Open a Fieldset
199     *
200     * @param $legend
201     * @param int $pos
202     * @return FieldsetOpenElement
203     */
204    public function addFieldsetOpen($legend = '', $pos = -1) {
205        return $this->addElement(new FieldsetOpenElement($legend), $pos);
206    }
207
208    /**
209     * Close a fieldset
210     *
211     * @param int $pos
212     * @return TagCloseElement
213     */
214    public function addFieldsetClose($pos = -1) {
215        return $this->addElement(new FieldsetCloseElement(), $pos);
216    }
217
218    #endregion
219
220    /**
221     * Adjust the elements so that fieldset open and closes are matching
222     */
223    protected function balanceFieldsets() {
224        $lastclose = 0;
225        $isopen = false;
226        $len = count($this->elements);
227
228        for($pos = 0; $pos < $len; $pos++) {
229            $type = $this->elements[$pos]->getType();
230            if($type == 'fieldsetopen') {
231                if($isopen) {
232                    //close previous feldset
233                    $this->addFieldsetClose($pos);
234                    $lastclose = $pos + 1;
235                    $pos++;
236                    $len++;
237                }
238                $isopen = true;
239            } else if($type == 'fieldsetclose') {
240                if(!$isopen) {
241                    // make sure there was a fieldsetopen
242                    // either right after the last close or at the begining
243                    $this->addFieldsetOpen('', $lastclose);
244                    $len++;
245                    $pos++;
246                }
247                $lastclose = $pos;
248                $isopen = false;
249            }
250        }
251
252        // close open fieldset at the end
253        if($isopen) {
254            $this->addFieldsetClose();
255        }
256    }
257
258    /**
259     * The HTML representation of the whole form
260     *
261     * @return string
262     */
263    public function toHTML() {
264        $this->balanceFieldsets();
265
266        $html = '<form ' . buildAttributes($this->attrs()) . '>' . DOKU_LF;
267
268        foreach($this->hidden as $name => $value) {
269            $html .= '<input type="hidden" name="' . $name . '" value="' . formText($value) . '" />' . DOKU_LF;
270        }
271
272        foreach($this->elements as $element) {
273            $html .= $element->toHTML() . DOKU_LF;
274        }
275
276        $html .= '</form>' . DOKU_LF;
277
278        return $html;
279    }
280}
281