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