Form::toHTML() can trigger event
[dokuwiki.git] / inc / Form / Form.php
blob44571875196d2d602e1cc7af2eb40e06bce00cd7
1 <?php
3 namespace dokuwiki\Form;
5 /**
6 * Class Form
8 * Represents the whole Form. This is what you work on, and add Elements to
10 * @package dokuwiki\Form
12 class Form extends Element
14 /**
15 * @var array name value pairs for hidden values
17 protected $hidden = array();
19 /**
20 * @var Element[] the elements of the form
22 protected $elements = array();
24 /**
25 * Creates a new, empty form with some default attributes
27 * @param array $attributes
28 * @param bool $unsafe if true, then the security token is ommited
30 public function __construct($attributes = array(), $unsafe = false)
32 global $ID;
34 parent::__construct('form', $attributes);
36 // use the current URL as default action
37 if (!$this->attr('action')) {
38 $get = $_GET;
39 if (isset($get['id'])) unset($get['id']);
40 $self = wl($ID, $get, false, '&'); //attributes are escaped later
41 $this->attr('action', $self);
44 // post is default
45 if (!$this->attr('method')) {
46 $this->attr('method', 'post');
49 // we like UTF-8
50 if (!$this->attr('accept-charset')) {
51 $this->attr('accept-charset', 'utf-8');
54 // add the security token by default
55 if (!$unsafe) {
56 $this->setHiddenField('sectok', getSecurityToken());
59 // identify this as a new form based form in HTML
60 $this->addClass('doku_form');
63 /**
64 * Sets a hidden field
66 * @param string $name
67 * @param string $value
68 * @return $this
70 public function setHiddenField($name, $value)
72 $this->hidden[$name] = $value;
73 return $this;
76 #region element query function
78 /**
79 * Returns the numbers of elements in the form
81 * @return int
83 public function elementCount()
85 return count($this->elements);
88 /**
89 * Get the position of the element in the form or false if it is not in the form
91 * Warning: This function may return Boolean FALSE, but may also return a non-Boolean value which evaluates
92 * to FALSE. Please read the section on Booleans for more information. Use the === operator for testing the
93 * return value of this function.
95 * @param Element $element
97 * @return false|int
99 public function getElementPosition(Element $element)
101 return array_search($element, $this->elements, true);
105 * Returns a reference to the element at a position.
106 * A position out-of-bounds will return either the
107 * first (underflow) or last (overflow) element.
109 * @param int $pos
110 * @return Element
112 public function getElementAt($pos)
114 if ($pos < 0) $pos = count($this->elements) + $pos;
115 if ($pos < 0) $pos = 0;
116 if ($pos >= count($this->elements)) $pos = count($this->elements) - 1;
117 return $this->elements[$pos];
121 * Gets the position of the first of a type of element
123 * @param string $type Element type to look for.
124 * @param int $offset search from this position onward
125 * @return false|int position of element if found, otherwise false
127 public function findPositionByType($type, $offset = 0)
129 $len = $this->elementCount();
130 for ($pos = $offset; $pos < $len; $pos++) {
131 if ($this->elements[$pos]->getType() == $type) {
132 return $pos;
135 return false;
139 * Gets the position of the first element matching the attribute
141 * @param string $name Name of the attribute
142 * @param string $value Value the attribute should have
143 * @param int $offset search from this position onward
144 * @return false|int position of element if found, otherwise false
146 public function findPositionByAttribute($name, $value, $offset = 0)
148 $len = $this->elementCount();
149 for ($pos = $offset; $pos < $len; $pos++) {
150 if ($this->elements[$pos]->attr($name) == $value) {
151 return $pos;
154 return false;
157 #endregion
159 #region Element positioning functions
162 * Adds or inserts an element to the form
164 * @param Element $element
165 * @param int $pos 0-based position in the form, -1 for at the end
166 * @return Element
168 public function addElement(Element $element, $pos = -1)
170 if (is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
171 'You can\'t add a form to a form'
173 if ($pos < 0) {
174 $this->elements[] = $element;
175 } else {
176 array_splice($this->elements, $pos, 0, array($element));
178 return $element;
182 * Replaces an existing element with a new one
184 * @param Element $element the new element
185 * @param int $pos 0-based position of the element to replace
187 public function replaceElement(Element $element, $pos)
189 if (is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
190 'You can\'t add a form to a form'
192 array_splice($this->elements, $pos, 1, array($element));
196 * Remove an element from the form completely
198 * @param int $pos 0-based position of the element to remove
200 public function removeElement($pos)
202 array_splice($this->elements, $pos, 1);
205 #endregion
207 #region Element adding functions
210 * Adds a text input field
212 * @param string $name
213 * @param string $label
214 * @param int $pos
215 * @return InputElement
217 public function addTextInput($name, $label = '', $pos = -1)
219 return $this->addElement(new InputElement('text', $name, $label), $pos);
223 * Adds a password input field
225 * @param string $name
226 * @param string $label
227 * @param int $pos
228 * @return InputElement
230 public function addPasswordInput($name, $label = '', $pos = -1)
232 return $this->addElement(new InputElement('password', $name, $label), $pos);
236 * Adds a radio button field
238 * @param string $name
239 * @param string $label
240 * @param int $pos
241 * @return CheckableElement
243 public function addRadioButton($name, $label = '', $pos = -1)
245 return $this->addElement(new CheckableElement('radio', $name, $label), $pos);
249 * Adds a checkbox field
251 * @param string $name
252 * @param string $label
253 * @param int $pos
254 * @return CheckableElement
256 public function addCheckbox($name, $label = '', $pos = -1)
258 return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
262 * Adds a dropdown field
264 * @param string $name
265 * @param array $options
266 * @param string $label
267 * @param int $pos
268 * @return DropdownElement
270 public function addDropdown($name, $options, $label = '', $pos = -1)
272 return $this->addElement(new DropdownElement($name, $options, $label), $pos);
276 * Adds a textarea field
278 * @param string $name
279 * @param string $label
280 * @param int $pos
281 * @return TextareaElement
283 public function addTextarea($name, $label = '', $pos = -1)
285 return $this->addElement(new TextareaElement($name, $label), $pos);
289 * Adds a simple button, escapes the content for you
291 * @param string $name
292 * @param string $content
293 * @param int $pos
294 * @return Element
296 public function addButton($name, $content, $pos = -1)
298 return $this->addElement(new ButtonElement($name, hsc($content)), $pos);
302 * Adds a simple button, allows HTML for content
304 * @param string $name
305 * @param string $html
306 * @param int $pos
307 * @return Element
309 public function addButtonHTML($name, $html, $pos = -1)
311 return $this->addElement(new ButtonElement($name, $html), $pos);
315 * Adds a label referencing another input element, escapes the label for you
317 * @param string $label
318 * @param string $for
319 * @param int $pos
320 * @return Element
322 public function addLabel($label, $for='', $pos = -1)
324 return $this->addLabelHTML(hsc($label), $for, $pos);
328 * Adds a label referencing another input element, allows HTML for content
330 * @param string $content
331 * @param string|Element $for
332 * @param int $pos
333 * @return Element
335 public function addLabelHTML($content, $for='', $pos = -1)
337 $element = new LabelElement(hsc($content));
339 if (is_a($for, '\dokuwiki\Form\Element')) {
340 /** @var Element $for */
341 $for = $for->id();
343 $for = (string) $for;
344 if ($for !== '') {
345 $element->attr('for', $for);
348 return $this->addElement($element, $pos);
352 * Add fixed HTML to the form
354 * @param string $html
355 * @param int $pos
356 * @return HTMLElement
358 public function addHTML($html, $pos = -1)
360 return $this->addElement(new HTMLElement($html), $pos);
364 * Add a closed HTML tag to the form
366 * @param string $tag
367 * @param int $pos
368 * @return TagElement
370 public function addTag($tag, $pos = -1)
372 return $this->addElement(new TagElement($tag), $pos);
376 * Add an open HTML tag to the form
378 * Be sure to close it again!
380 * @param string $tag
381 * @param int $pos
382 * @return TagOpenElement
384 public function addTagOpen($tag, $pos = -1)
386 return $this->addElement(new TagOpenElement($tag), $pos);
390 * Add a closing HTML tag to the form
392 * Be sure it had been opened before
394 * @param string $tag
395 * @param int $pos
396 * @return TagCloseElement
398 public function addTagClose($tag, $pos = -1)
400 return $this->addElement(new TagCloseElement($tag), $pos);
404 * Open a Fieldset
406 * @param string $legend
407 * @param int $pos
408 * @return FieldsetOpenElement
410 public function addFieldsetOpen($legend = '', $pos = -1)
412 return $this->addElement(new FieldsetOpenElement($legend), $pos);
416 * Close a fieldset
418 * @param int $pos
419 * @return TagCloseElement
421 public function addFieldsetClose($pos = -1)
423 return $this->addElement(new FieldsetCloseElement(), $pos);
426 #endregion
429 * Adjust the elements so that fieldset open and closes are matching
431 protected function balanceFieldsets()
433 $lastclose = 0;
434 $isopen = false;
435 $len = count($this->elements);
437 for ($pos = 0; $pos < $len; $pos++) {
438 $type = $this->elements[$pos]->getType();
439 if ($type == 'fieldsetopen') {
440 if ($isopen) {
441 //close previous fieldset
442 $this->addFieldsetClose($pos);
443 $lastclose = $pos + 1;
444 $pos++;
445 $len++;
447 $isopen = true;
448 } elseif ($type == 'fieldsetclose') {
449 if (!$isopen) {
450 // make sure there was a fieldsetopen
451 // either right after the last close or at the begining
452 $this->addFieldsetOpen('', $lastclose);
453 $len++;
454 $pos++;
456 $lastclose = $pos;
457 $isopen = false;
461 // close open fieldset at the end
462 if ($isopen) {
463 $this->addFieldsetClose();
468 * The HTML representation of the whole form
470 * @param string $eventName (optional) name of the event: HTMLFORM_{$name}_OUTPUT
471 * @return string
473 public function toHTML($eventName = null)
475 $this->balanceFieldsets();
477 // trigger event to provide an opportunity to modify this form
478 if (isset($eventName)) {
479 if (!preg_match('/^HTMLFORM_[A-Z]+?_OUTPUT$/', $eventName)) {
480 $eventName = 'HTMLFORM_'.strtoupper($eventName).'_OUTPUT';
482 Event::createAndTrigger($eventName, $this, null, false);
485 $html = '<form '. buildAttributes($this->attrs()) .'>';
487 foreach ($this->hidden as $name => $value) {
488 $html .= '<input type="hidden" name="'. $name .'" value="'. formText($value) .'" />';
491 foreach ($this->elements as $element) {
492 $html .= $element->toHTML();
495 $html .= '</form>';
497 return $html;