[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/Form/ -> Form.php (source)

   1  <?php
   2  
   3  namespace dokuwiki\Form;
   4  
   5  use dokuwiki\Extension\Event;
   6  
   7  /**
   8   * Class Form
   9   *
  10   * Represents the whole Form. This is what you work on, and add Elements to
  11   *
  12   * @package dokuwiki\Form
  13   */
  14  class Form extends Element
  15  {
  16      /**
  17       * @var array name value pairs for hidden values
  18       */
  19      protected $hidden = [];
  20  
  21      /**
  22       * @var Element[] the elements of the form
  23       */
  24      protected $elements = [];
  25  
  26      /**
  27       * Creates a new, empty form with some default attributes
  28       *
  29       * @param array $attributes
  30       * @param bool  $unsafe     if true, then the security token is ommited
  31       */
  32      public function __construct($attributes = [], $unsafe = false)
  33      {
  34          global $ID;
  35  
  36          parent::__construct('form', $attributes);
  37  
  38          // use the current URL as default action
  39          if (!$this->attr('action')) {
  40              $get = $_GET;
  41              if (isset($get['id'])) unset($get['id']);
  42              $self = wl($ID, $get, false, '&'); //attributes are escaped later
  43              $this->attr('action', $self);
  44          }
  45  
  46          // post is default
  47          if (!$this->attr('method')) {
  48              $this->attr('method', 'post');
  49          }
  50  
  51          // we like UTF-8
  52          if (!$this->attr('accept-charset')) {
  53              $this->attr('accept-charset', 'utf-8');
  54          }
  55  
  56          // add the security token by default
  57          if (!$unsafe) {
  58              $this->setHiddenField('sectok', getSecurityToken());
  59          }
  60  
  61          // identify this as a new form based form in HTML
  62          $this->addClass('doku_form');
  63      }
  64  
  65      /**
  66       * Sets a hidden field
  67       *
  68       * @param string $name
  69       * @param string $value
  70       * @return $this
  71       */
  72      public function setHiddenField($name, $value)
  73      {
  74          $this->hidden[$name] = $value;
  75          return $this;
  76      }
  77  
  78      #region element query function
  79  
  80      /**
  81       * Returns the numbers of elements in the form
  82       *
  83       * @return int
  84       */
  85      public function elementCount()
  86      {
  87          return count($this->elements);
  88      }
  89  
  90      /**
  91       * Get the position of the element in the form or false if it is not in the form
  92       *
  93       * Warning: This function may return Boolean FALSE, but may also return a non-Boolean value which evaluates
  94       * to FALSE. Please read the section on Booleans for more information. Use the === operator for testing the
  95       * return value of this function.
  96       *
  97       * @param Element $element
  98       *
  99       * @return false|int
 100       */
 101      public function getElementPosition(Element $element)
 102      {
 103          return array_search($element, $this->elements, true);
 104      }
 105  
 106      /**
 107       * Returns a reference to the element at a position.
 108       * A position out-of-bounds will return either the
 109       * first (underflow) or last (overflow) element.
 110       *
 111       * @param int $pos
 112       * @return Element
 113       */
 114      public function getElementAt($pos)
 115      {
 116          if ($pos < 0) $pos = count($this->elements) + $pos;
 117          if ($pos < 0) $pos = 0;
 118          if ($pos >= count($this->elements)) $pos = count($this->elements) - 1;
 119          return $this->elements[$pos];
 120      }
 121  
 122      /**
 123       * Gets the position of the first of a type of element
 124       *
 125       * @param string $type Element type to look for.
 126       * @param int $offset search from this position onward
 127       * @return false|int position of element if found, otherwise false
 128       */
 129      public function findPositionByType($type, $offset = 0)
 130      {
 131          $len = $this->elementCount();
 132          for ($pos = $offset; $pos < $len; $pos++) {
 133              if ($this->elements[$pos]->getType() == $type) {
 134                  return $pos;
 135              }
 136          }
 137          return false;
 138      }
 139  
 140      /**
 141       * Gets the position of the first element matching the attribute
 142       *
 143       * @param string $name Name of the attribute
 144       * @param string $value Value the attribute should have
 145       * @param int $offset search from this position onward
 146       * @return false|int position of element if found, otherwise false
 147       */
 148      public function findPositionByAttribute($name, $value, $offset = 0)
 149      {
 150          $len = $this->elementCount();
 151          for ($pos = $offset; $pos < $len; $pos++) {
 152              if ($this->elements[$pos]->attr($name) == $value) {
 153                  return $pos;
 154              }
 155          }
 156          return false;
 157      }
 158  
 159      #endregion
 160  
 161      #region Element positioning functions
 162  
 163      /**
 164       * Adds or inserts an element to the form
 165       *
 166       * @param Element $element
 167       * @param int $pos 0-based position in the form, -1 for at the end
 168       * @return Element
 169       */
 170      public function addElement(Element $element, $pos = -1)
 171      {
 172          if (is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
 173              'You can\'t add a form to a form'
 174          );
 175          if ($pos < 0) {
 176              $this->elements[] = $element;
 177          } else {
 178              array_splice($this->elements, $pos, 0, [$element]);
 179          }
 180          return $element;
 181      }
 182  
 183      /**
 184       * Replaces an existing element with a new one
 185       *
 186       * @param Element $element the new element
 187       * @param int $pos 0-based position of the element to replace
 188       */
 189      public function replaceElement(Element $element, $pos)
 190      {
 191          if (is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
 192              'You can\'t add a form to a form'
 193          );
 194          array_splice($this->elements, $pos, 1, [$element]);
 195      }
 196  
 197      /**
 198       * Remove an element from the form completely
 199       *
 200       * @param int $pos 0-based position of the element to remove
 201       */
 202      public function removeElement($pos)
 203      {
 204          array_splice($this->elements, $pos, 1);
 205      }
 206  
 207      #endregion
 208  
 209      #region Element adding functions
 210  
 211      /**
 212       * Adds a text input field
 213       *
 214       * @param string $name
 215       * @param string $label
 216       * @param int $pos
 217       * @return InputElement
 218       */
 219      public function addTextInput($name, $label = '', $pos = -1)
 220      {
 221          return $this->addElement(new InputElement('text', $name, $label), $pos);
 222      }
 223  
 224      /**
 225       * Adds a password input field
 226       *
 227       * @param string $name
 228       * @param string $label
 229       * @param int $pos
 230       * @return InputElement
 231       */
 232      public function addPasswordInput($name, $label = '', $pos = -1)
 233      {
 234          return $this->addElement(new InputElement('password', $name, $label), $pos);
 235      }
 236  
 237      /**
 238       * Adds a radio button field
 239       *
 240       * @param string $name
 241       * @param string $label
 242       * @param int $pos
 243       * @return CheckableElement
 244       */
 245      public function addRadioButton($name, $label = '', $pos = -1)
 246      {
 247          return $this->addElement(new CheckableElement('radio', $name, $label), $pos);
 248      }
 249  
 250      /**
 251       * Adds a checkbox field
 252       *
 253       * @param string $name
 254       * @param string $label
 255       * @param int $pos
 256       * @return CheckableElement
 257       */
 258      public function addCheckbox($name, $label = '', $pos = -1)
 259      {
 260          return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
 261      }
 262  
 263      /**
 264       * Adds a dropdown field
 265       *
 266       * @param string $name
 267       * @param array $options
 268       * @param string $label
 269       * @param int $pos
 270       * @return DropdownElement
 271       */
 272      public function addDropdown($name, $options, $label = '', $pos = -1)
 273      {
 274          return $this->addElement(new DropdownElement($name, $options, $label), $pos);
 275      }
 276  
 277      /**
 278       * Adds a textarea field
 279       *
 280       * @param string $name
 281       * @param string $label
 282       * @param int $pos
 283       * @return TextareaElement
 284       */
 285      public function addTextarea($name, $label = '', $pos = -1)
 286      {
 287          return $this->addElement(new TextareaElement($name, $label), $pos);
 288      }
 289  
 290      /**
 291       * Adds a simple button, escapes the content for you
 292       *
 293       * @param string $name
 294       * @param string $content
 295       * @param int $pos
 296       * @return Element
 297       */
 298      public function addButton($name, $content, $pos = -1)
 299      {
 300          return $this->addElement(new ButtonElement($name, hsc($content)), $pos);
 301      }
 302  
 303      /**
 304       * Adds a simple button, allows HTML for content
 305       *
 306       * @param string $name
 307       * @param string $html
 308       * @param int $pos
 309       * @return Element
 310       */
 311      public function addButtonHTML($name, $html, $pos = -1)
 312      {
 313          return $this->addElement(new ButtonElement($name, $html), $pos);
 314      }
 315  
 316      /**
 317       * Adds a label referencing another input element, escapes the label for you
 318       *
 319       * @param string $label
 320       * @param string $for
 321       * @param int $pos
 322       * @return Element
 323       */
 324      public function addLabel($label, $for = '', $pos = -1)
 325      {
 326          return $this->addLabelHTML(hsc($label), $for, $pos);
 327      }
 328  
 329      /**
 330       * Adds a label referencing another input element, allows HTML for content
 331       *
 332       * @param string $content
 333       * @param string|Element $for
 334       * @param int $pos
 335       * @return Element
 336       */
 337      public function addLabelHTML($content, $for = '', $pos = -1)
 338      {
 339          $element = new LabelElement($content);
 340  
 341          if (is_a($for, '\dokuwiki\Form\Element')) {
 342              /** @var Element $for */
 343              $for = $for->id();
 344          }
 345          $for = (string) $for;
 346          if ($for !== '') {
 347              $element->attr('for', $for);
 348          }
 349  
 350          return $this->addElement($element, $pos);
 351      }
 352  
 353      /**
 354       * Add fixed HTML to the form
 355       *
 356       * @param string $html
 357       * @param int $pos
 358       * @return HTMLElement
 359       */
 360      public function addHTML($html, $pos = -1)
 361      {
 362          return $this->addElement(new HTMLElement($html), $pos);
 363      }
 364  
 365      /**
 366       * Add a closed HTML tag to the form
 367       *
 368       * @param string $tag
 369       * @param int $pos
 370       * @return TagElement
 371       */
 372      public function addTag($tag, $pos = -1)
 373      {
 374          return $this->addElement(new TagElement($tag), $pos);
 375      }
 376  
 377      /**
 378       * Add an open HTML tag to the form
 379       *
 380       * Be sure to close it again!
 381       *
 382       * @param string $tag
 383       * @param int $pos
 384       * @return TagOpenElement
 385       */
 386      public function addTagOpen($tag, $pos = -1)
 387      {
 388          return $this->addElement(new TagOpenElement($tag), $pos);
 389      }
 390  
 391      /**
 392       * Add a closing HTML tag to the form
 393       *
 394       * Be sure it had been opened before
 395       *
 396       * @param string $tag
 397       * @param int $pos
 398       * @return TagCloseElement
 399       */
 400      public function addTagClose($tag, $pos = -1)
 401      {
 402          return $this->addElement(new TagCloseElement($tag), $pos);
 403      }
 404  
 405      /**
 406       * Open a Fieldset
 407       *
 408       * @param string $legend
 409       * @param int $pos
 410       * @return FieldsetOpenElement
 411       */
 412      public function addFieldsetOpen($legend = '', $pos = -1)
 413      {
 414          return $this->addElement(new FieldsetOpenElement($legend), $pos);
 415      }
 416  
 417      /**
 418       * Close a fieldset
 419       *
 420       * @param int $pos
 421       * @return TagCloseElement
 422       */
 423      public function addFieldsetClose($pos = -1)
 424      {
 425          return $this->addElement(new FieldsetCloseElement(), $pos);
 426      }
 427  
 428      #endregion
 429  
 430      /**
 431       * Adjust the elements so that fieldset open and closes are matching
 432       */
 433      protected function balanceFieldsets()
 434      {
 435          $lastclose = 0;
 436          $isopen = false;
 437          $len = count($this->elements);
 438  
 439          for ($pos = 0; $pos < $len; $pos++) {
 440              $type = $this->elements[$pos]->getType();
 441              if ($type == 'fieldsetopen') {
 442                  if ($isopen) {
 443                      //close previous fieldset
 444                      $this->addFieldsetClose($pos);
 445                      $lastclose = $pos + 1;
 446                      $pos++;
 447                      $len++;
 448                  }
 449                  $isopen = true;
 450              } elseif ($type == 'fieldsetclose') {
 451                  if (!$isopen) {
 452                      // make sure there was a fieldsetopen
 453                      // either right after the last close or at the begining
 454                      $this->addFieldsetOpen('', $lastclose);
 455                      $len++;
 456                      $pos++;
 457                  }
 458                  $lastclose = $pos;
 459                  $isopen = false;
 460              }
 461          }
 462  
 463          // close open fieldset at the end
 464          if ($isopen) {
 465              $this->addFieldsetClose();
 466          }
 467      }
 468  
 469      /**
 470       * The HTML representation of the whole form
 471       *
 472       * @param string $eventName  (optional) name of the event: FORM_{$name}_OUTPUT
 473       * @return string
 474       */
 475      public function toHTML($eventName = null)
 476      {
 477          $this->balanceFieldsets();
 478  
 479          // trigger event to provide an opportunity to modify this form
 480          if (isset($eventName)) {
 481              $eventName = 'FORM_' . strtoupper($eventName) . '_OUTPUT';
 482              Event::createAndTrigger($eventName, $this, null, false);
 483          }
 484  
 485          $html = '<form ' . buildAttributes($this->attrs()) . '>';
 486  
 487          foreach ($this->hidden as $name => $value) {
 488              $html .= '<input type="hidden" name="' . $name . '" value="' . formText($value) . '" />';
 489          }
 490  
 491          foreach ($this->elements as $element) {
 492              $html .= $element->toHTML();
 493          }
 494  
 495          $html .= '</form>';
 496  
 497          return $html;
 498      }
 499  }