Completed
Push — betterCoreSearch ( c6b5b7...5facb9 )
by Michael
12:07 queued 07:58
created

Form::getElementPosition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
namespace 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
 */
11
class 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 string $name
62
     * @param string $value
63
     * @return $this
64
     */
65
    public function setHiddenField($name, $value) {
66
        $this->hidden[$name] = $value;
67
        return $this;
68
    }
69
70
    #region element query function
71
72
    /**
73
     * Returns the numbers of elements in the form
74
     *
75
     * @return int
76
     */
77
    public function elementCount() {
78
        return count($this->elements);
79
    }
80
81
    /**
82
     * Get the position of the element in the form or false if it is not in the form
83
     *
84
     * Warning: This function may return Boolean FALSE, but may also return a non-Boolean value which evaluates to FALSE. Please read the section on Booleans for more information. Use the === operator for testing the return value of this function.
85
     *
86
     * @param Element $element
87
     *
88
     * @return false|int
89
     */
90
    public function getElementPosition(Element $element)
91
    {
92
        return array_search($element, $this->elements, true);
93
    }
94
95
    /**
96
     * Returns a reference to the element at a position.
97
     * A position out-of-bounds will return either the
98
     * first (underflow) or last (overflow) element.
99
     *
100
     * @param int $pos
101
     * @return Element
102
     */
103
    public function getElementAt($pos) {
104
        if($pos < 0) $pos = count($this->elements) + $pos;
105
        if($pos < 0) $pos = 0;
106
        if($pos >= count($this->elements)) $pos = count($this->elements) - 1;
107
        return $this->elements[$pos];
108
    }
109
110
    /**
111
     * Gets the position of the first of a type of element
112
     *
113
     * @param string $type Element type to look for.
114
     * @param int $offset search from this position onward
115
     * @return false|int position of element if found, otherwise false
116
     */
117
    public function findPositionByType($type, $offset = 0) {
118
        $len = $this->elementCount();
119
        for($pos = $offset; $pos < $len; $pos++) {
120
            if($this->elements[$pos]->getType() == $type) {
121
                return $pos;
122
            }
123
        }
124
        return false;
125
    }
126
127
    /**
128
     * Gets the position of the first element matching the attribute
129
     *
130
     * @param string $name Name of the attribute
131
     * @param string $value Value the attribute should have
132
     * @param int $offset search from this position onward
133
     * @return false|int position of element if found, otherwise false
134
     */
135
    public function findPositionByAttribute($name, $value, $offset = 0) {
136
        $len = $this->elementCount();
137
        for($pos = $offset; $pos < $len; $pos++) {
138
            if($this->elements[$pos]->attr($name) == $value) {
139
                return $pos;
140
            }
141
        }
142
        return false;
143
    }
144
145
    #endregion
146
147
    #region Element positioning functions
148
149
    /**
150
     * Adds or inserts an element to the form
151
     *
152
     * @param Element $element
153
     * @param int $pos 0-based position in the form, -1 for at the end
154
     * @return Element
155
     */
156
    public function addElement(Element $element, $pos = -1) {
157
        if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException('You can\'t add a form to a form');
158
        if($pos < 0) {
159
            $this->elements[] = $element;
160
        } else {
161
            array_splice($this->elements, $pos, 0, array($element));
162
        }
163
        return $element;
164
    }
165
166
    /**
167
     * Replaces an existing element with a new one
168
     *
169
     * @param Element $element the new element
170
     * @param int $pos 0-based position of the element to replace
171
     */
172
    public function replaceElement(Element $element, $pos) {
173
        if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException('You can\'t add a form to a form');
174
        array_splice($this->elements, $pos, 1, array($element));
175
    }
176
177
    /**
178
     * Remove an element from the form completely
179
     *
180
     * @param int $pos 0-based position of the element to remove
181
     */
182
    public function removeElement($pos) {
183
        array_splice($this->elements, $pos, 1);
184
    }
185
186
    #endregion
187
188
    #region Element adding functions
189
190
    /**
191
     * Adds a text input field
192
     *
193
     * @param string $name
194
     * @param string $label
195
     * @param int $pos
196
     * @return InputElement
197
     */
198
    public function addTextInput($name, $label = '', $pos = -1) {
199
        return $this->addElement(new InputElement('text', $name, $label), $pos);
200
    }
201
202
    /**
203
     * Adds a password input field
204
     *
205
     * @param string $name
206
     * @param string $label
207
     * @param int $pos
208
     * @return InputElement
209
     */
210
    public function addPasswordInput($name, $label = '', $pos = -1) {
211
        return $this->addElement(new InputElement('password', $name, $label), $pos);
212
    }
213
214
    /**
215
     * Adds a radio button field
216
     *
217
     * @param string $name
218
     * @param string $label
219
     * @param int $pos
220
     * @return CheckableElement
221
     */
222
    public function addRadioButton($name, $label = '', $pos = -1) {
223
        return $this->addElement(new CheckableElement('radio', $name, $label), $pos);
224
    }
225
226
    /**
227
     * Adds a checkbox field
228
     *
229
     * @param string $name
230
     * @param string $label
231
     * @param int $pos
232
     * @return CheckableElement
233
     */
234
    public function addCheckbox($name, $label = '', $pos = -1) {
235
        return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
236
    }
237
238
    /**
239
     * Adds a dropdown field
240
     *
241
     * @param string $name
242
     * @param array $options
243
     * @param string $label
244
     * @param int $pos
245
     * @return DropdownElement
246
     */
247
    public function addDropdown($name, $options, $label = '', $pos = -1) {
248
        return $this->addElement(new DropdownElement($name, $options, $label), $pos);
249
    }
250
251
    /**
252
     * Adds a textarea field
253
     *
254
     * @param string $name
255
     * @param string $label
256
     * @param int $pos
257
     * @return TextareaElement
258
     */
259
    public function addTextarea($name, $label = '', $pos = -1) {
260
        return $this->addElement(new TextareaElement($name, $label), $pos);
261
    }
262
263
    /**
264
     * Adds a simple button, escapes the content for you
265
     *
266
     * @param string $name
267
     * @param string $content
268
     * @param int $pos
269
     * @return Element
270
     */
271
    public function addButton($name, $content, $pos = -1) {
272
        return $this->addElement(new ButtonElement($name, hsc($content)), $pos);
273
    }
274
275
    /**
276
     * Adds a simple button, allows HTML for content
277
     *
278
     * @param string $name
279
     * @param string $html
280
     * @param int $pos
281
     * @return Element
282
     */
283
    public function addButtonHTML($name, $html, $pos = -1) {
284
        return $this->addElement(new ButtonElement($name, $html), $pos);
285
    }
286
287
    /**
288
     * Adds a label referencing another input element, escapes the label for you
289
     *
290
     * @param string $label
291
     * @param string $for
292
     * @param int $pos
293
     * @return Element
294
     */
295
    public function addLabel($label, $for='', $pos = -1) {
296
        return $this->addLabelHTML(hsc($label), $for, $pos);
297
    }
298
299
    /**
300
     * Adds a label referencing another input element, allows HTML for content
301
     *
302
     * @param string $content
303
     * @param string|Element $for
304
     * @param int $pos
305
     * @return Element
306
     */
307
    public function addLabelHTML($content, $for='', $pos = -1) {
308
        $element = new LabelElement(hsc($content));
309
310
        if(is_a($for, '\dokuwiki\Form\Element')) {
311
            /** @var Element $for */
312
            $for = $for->id();
313
        }
314
        $for = (string) $for;
315
        if($for !== '') {
316
            $element->attr('for', $for);
317
        }
318
319
        return $this->addElement($element, $pos);
320
    }
321
322
    /**
323
     * Add fixed HTML to the form
324
     *
325
     * @param string $html
326
     * @param int $pos
327
     * @return HTMLElement
328
     */
329
    public function addHTML($html, $pos = -1) {
330
        return $this->addElement(new HTMLElement($html), $pos);
331
    }
332
333
    /**
334
     * Add a closed HTML tag to the form
335
     *
336
     * @param string $tag
337
     * @param int $pos
338
     * @return TagElement
339
     */
340
    public function addTag($tag, $pos = -1) {
341
        return $this->addElement(new TagElement($tag), $pos);
342
    }
343
344
    /**
345
     * Add an open HTML tag to the form
346
     *
347
     * Be sure to close it again!
348
     *
349
     * @param string $tag
350
     * @param int $pos
351
     * @return TagOpenElement
352
     */
353
    public function addTagOpen($tag, $pos = -1) {
354
        return $this->addElement(new TagOpenElement($tag), $pos);
355
    }
356
357
    /**
358
     * Add a closing HTML tag to the form
359
     *
360
     * Be sure it had been opened before
361
     *
362
     * @param string $tag
363
     * @param int $pos
364
     * @return TagCloseElement
365
     */
366
    public function addTagClose($tag, $pos = -1) {
367
        return $this->addElement(new TagCloseElement($tag), $pos);
368
    }
369
370
    /**
371
     * Open a Fieldset
372
     *
373
     * @param string $legend
374
     * @param int $pos
375
     * @return FieldsetOpenElement
376
     */
377
    public function addFieldsetOpen($legend = '', $pos = -1) {
378
        return $this->addElement(new FieldsetOpenElement($legend), $pos);
379
    }
380
381
    /**
382
     * Close a fieldset
383
     *
384
     * @param int $pos
385
     * @return TagCloseElement
386
     */
387
    public function addFieldsetClose($pos = -1) {
388
        return $this->addElement(new FieldsetCloseElement(), $pos);
389
    }
390
391
    #endregion
392
393
    /**
394
     * Adjust the elements so that fieldset open and closes are matching
395
     */
396
    protected function balanceFieldsets() {
397
        $lastclose = 0;
398
        $isopen = false;
399
        $len = count($this->elements);
400
401
        for($pos = 0; $pos < $len; $pos++) {
402
            $type = $this->elements[$pos]->getType();
403
            if($type == 'fieldsetopen') {
404
                if($isopen) {
405
                    //close previous fieldset
406
                    $this->addFieldsetClose($pos);
407
                    $lastclose = $pos + 1;
408
                    $pos++;
409
                    $len++;
410
                }
411
                $isopen = true;
412
            } else if($type == 'fieldsetclose') {
413
                if(!$isopen) {
414
                    // make sure there was a fieldsetopen
415
                    // either right after the last close or at the begining
416
                    $this->addFieldsetOpen('', $lastclose);
417
                    $len++;
418
                    $pos++;
419
                }
420
                $lastclose = $pos;
421
                $isopen = false;
422
            }
423
        }
424
425
        // close open fieldset at the end
426
        if($isopen) {
427
            $this->addFieldsetClose();
428
        }
429
    }
430
431
    /**
432
     * The HTML representation of the whole form
433
     *
434
     * @return string
435
     */
436
    public function toHTML() {
437
        $this->balanceFieldsets();
438
439
        $html = '<form ' . buildAttributes($this->attrs()) . '>';
0 ignored issues
show
Bug introduced by
It seems like $this->attrs() targeting dokuwiki\Form\Element::attrs() can also be of type this<dokuwiki\Form\Form>; however, buildAttributes() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
440
441
        foreach($this->hidden as $name => $value) {
442
            $html .= '<input type="hidden" name="' . $name . '" value="' . formText($value) . '" />';
443
        }
444
445
        foreach($this->elements as $element) {
446
            $html .= $element->toHTML();
447
        }
448
449
        $html .= '</form>';
450
451
        return $html;
452
    }
453
}
454