Initial commit

This commit is contained in:
2021-10-26 13:02:53 +02:00
commit 73843b66ce
4678 changed files with 319494 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
<?php
namespace dokuwiki\Form;
/**
* Class ButtonElement
*
* Represents a simple button
*
* @package dokuwiki\Form
*/
class ButtonElement extends Element {
/** @var string HTML content */
protected $content = '';
/**
* @param string $name
* @param string $content HTML content of the button. You have to escape it yourself.
*/
public function __construct($name, $content = '') {
parent::__construct('button', array('name' => $name, 'value' => 1));
$this->content = $content;
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
return '<button ' . buildAttributes($this->attrs(), true) . '>'.$this->content.'</button>';
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace dokuwiki\Form;
/**
* Class CheckableElement
*
* For Radio- and Checkboxes
*
* @package dokuwiki\Form
*/
class CheckableElement extends InputElement {
/**
* @param string $type The type of this element
* @param string $name The name of this form element
* @param string $label The label text for this element
*/
public function __construct($type, $name, $label) {
parent::__construct($type, $name, $label);
// default value is 1
$this->attr('value', 1);
}
/**
* Handles the useInput flag and sets the checked attribute accordingly
*/
protected function prefillInput() {
global $INPUT;
list($name, $key) = $this->getInputName();
$myvalue = $this->val();
if(!$INPUT->has($name)) return;
if($key === null) {
// no key - single value
$value = $INPUT->str($name);
if($value == $myvalue) {
$this->attr('checked', 'checked');
} else {
$this->rmattr('checked');
}
} else {
// we have an array, there might be several values in it
$input = $INPUT->arr($name);
if(isset($input[$key])) {
$this->rmattr('checked');
// values seem to be in another sub array
if(is_array($input[$key])) {
$input = $input[$key];
}
foreach($input as $value) {
if($value == $myvalue) {
$this->attr('checked', 'checked');
}
}
}
}
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace dokuwiki\Form;
/**
* Class DropdownElement
*
* Represents a HTML select. Please note that this does not support multiple selected options!
*
* @package dokuwiki\Form
*/
class DropdownElement extends InputElement {
/** @var array OptGroup[] */
protected $optGroups = array();
/**
* @param string $name The name of this form element
* @param array $options The available options
* @param string $label The label text for this element (will be autoescaped)
*/
public function __construct($name, $options, $label = '') {
parent::__construct('dropdown', $name, $label);
$this->rmattr('type');
$this->optGroups[''] = new OptGroup(null, $options);
$this->val('');
}
/**
* Add an `<optgroup>` and respective options
*
* @param string $label
* @param array $options
* @return OptGroup a reference to the added optgroup
* @throws \Exception
*/
public function addOptGroup($label, $options) {
if (empty($label)) {
throw new \InvalidArgumentException(hsc('<optgroup> must have a label!'));
}
$this->optGroups[$label] = new OptGroup($label, $options);
return end($this->optGroups);
}
/**
* Set or get the optgroups of an Dropdown-Element.
*
* optgroups have to be given as associative array
* * the key being the label of the group
* * the value being an array of options as defined in @see OptGroup::options()
*
* @param null|array $optGroups
* @return OptGroup[]|DropdownElement
*/
public function optGroups($optGroups = null) {
if($optGroups === null) {
return $this->optGroups;
}
if (!is_array($optGroups)) {
throw new \InvalidArgumentException(hsc('Argument must be an associative array of label => [options]!'));
}
$this->optGroups = array();
foreach ($optGroups as $label => $options) {
$this->addOptGroup($label, $options);
}
return $this;
}
/**
* Get or set the options of the Dropdown
*
* Options can be given as associative array (value => label) or as an
* indexd array (label = value) or as an array of arrays. In the latter
* case an element has to look as follows:
* option-value => array (
* 'label' => option-label,
* 'attrs' => array (
* attr-key => attr-value, ...
* )
* )
*
* @param null|array $options
* @return $this|array
*/
public function options($options = null) {
if ($options === null) {
return $this->optGroups['']->options();
}
$this->optGroups[''] = new OptGroup(null, $options);
return $this;
}
/**
* Gets or sets an attribute
*
* When no $value is given, the current content of the attribute is returned.
* An empty string is returned for unset attributes.
*
* When a $value is given, the content is set to that value and the Element
* itself is returned for easy chaining
*
* @param string $name Name of the attribute to access
* @param null|string $value New value to set
* @return string|$this
*/
public function attr($name, $value = null) {
if(strtolower($name) == 'multiple') {
throw new \InvalidArgumentException(
'Sorry, the dropdown element does not support the "multiple" attribute'
);
}
return parent::attr($name, $value);
}
/**
* Get or set the current value
*
* When setting a value that is not defined in the options, the value is ignored
* and the first option's value is selected instead
*
* @param null|string $value The value to set
* @return $this|string
*/
public function val($value = null) {
if($value === null) return $this->value;
$value_exists = $this->setValueInOptGroups($value);
if($value_exists) {
$this->value = $value;
} else {
// unknown value set, select first option instead
$this->value = $this->getFirstOption();
$this->setValueInOptGroups($this->value);
}
return $this;
}
/**
* Returns the first options as it will be rendered in HTML
*
* @return string
*/
protected function getFirstOption() {
$options = $this->options();
if (!empty($options)) {
$keys = array_keys($options);
return (string) array_shift($keys);
}
foreach ($this->optGroups as $optGroup) {
$options = $optGroup->options();
if (!empty($options)) {
$keys = array_keys($options);
return (string) array_shift($keys);
}
}
}
/**
* Set the value in the OptGroups, including the optgroup for the options without optgroup.
*
* @param string $value
* @return bool
*/
protected function setValueInOptGroups($value) {
$value_exists = false;
/** @var OptGroup $optGroup */
foreach ($this->optGroups as $optGroup) {
$value_exists = $optGroup->storeValue($value) || $value_exists;
if ($value_exists) {
$value = null;
}
}
return $value_exists;
}
/**
* Create the HTML for the select it self
*
* @return string
*/
protected function mainElementHTML() {
if($this->useInput) $this->prefillInput();
$html = '<select ' . buildAttributes($this->attrs()) . '>';
$html = array_reduce(
$this->optGroups,
function ($html, OptGroup $optGroup) {
return $html . $optGroup->toHTML();
},
$html
);
$html .= '</select>';
return $html;
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace dokuwiki\Form;
/**
* Class Element
*
* The basic building block of a form
*
* @package dokuwiki\Form
*/
abstract class Element {
/**
* @var array the attributes of this element
*/
protected $attributes = array();
/**
* @var string The type of this element
*/
protected $type;
/**
* @param string $type The type of this element
* @param array $attributes
*/
public function __construct($type, $attributes = array()) {
$this->type = $type;
$this->attributes = $attributes;
}
/**
* Type of this element
*
* @return string
*/
public function getType() {
return $this->type;
}
/**
* Gets or sets an attribute
*
* When no $value is given, the current content of the attribute is returned.
* An empty string is returned for unset attributes.
*
* When a $value is given, the content is set to that value and the Element
* itself is returned for easy chaining
*
* @param string $name Name of the attribute to access
* @param null|string $value New value to set
* @return string|$this
*/
public function attr($name, $value = null) {
// set
if($value !== null) {
$this->attributes[$name] = $value;
return $this;
}
// get
if(isset($this->attributes[$name])) {
return $this->attributes[$name];
} else {
return '';
}
}
/**
* Removes the given attribute if it exists
*
* @param string $name
* @return $this
*/
public function rmattr($name) {
if(isset($this->attributes[$name])) {
unset($this->attributes[$name]);
}
return $this;
}
/**
* Gets or adds a all given attributes at once
*
* @param array|null $attributes
* @return array|$this
*/
public function attrs($attributes = null) {
// set
if($attributes) {
foreach((array) $attributes as $key => $val) {
$this->attr($key, $val);
}
return $this;
}
// get
return $this->attributes;
}
/**
* Adds a class to the class attribute
*
* This is the preferred method of setting the element's class
*
* @param string $class the new class to add
* @return $this
*/
public function addClass($class) {
$classes = explode(' ', $this->attr('class'));
$classes[] = $class;
$classes = array_unique($classes);
$classes = array_filter($classes);
$this->attr('class', join(' ', $classes));
return $this;
}
/**
* Get or set the element's ID
*
* This is the preferred way of setting the element's ID
*
* @param null|string $id
* @return string|$this
*/
public function id($id = null) {
if(strpos($id, '__') === false) {
throw new \InvalidArgumentException('IDs in DokuWiki have to contain two subsequent underscores');
}
return $this->attr('id', $id);
}
/**
* Get or set the element's value
*
* This is the preferred way of setting the element's value
*
* @param null|string $value
* @return string|$this
*/
public function val($value = null) {
return $this->attr('value', $value);
}
/**
* The HTML representation of this element
*
* @return string
*/
abstract public function toHTML();
}

View File

@@ -0,0 +1,30 @@
<?php
namespace dokuwiki\Form;
/**
* Class FieldsetCloseElement
*
* Closes an open Fieldset
*
* @package dokuwiki\Form
*/
class FieldsetCloseElement extends TagCloseElement {
/**
* @param array $attributes
*/
public function __construct($attributes = array()) {
parent::__construct('', $attributes);
$this->type = 'fieldsetclose';
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
return '</fieldset>';
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace dokuwiki\Form;
/**
* Class FieldsetOpenElement
*
* Opens a Fieldset with an optional legend
*
* @package dokuwiki\Form
*/
class FieldsetOpenElement extends TagOpenElement {
/**
* @param string $legend
* @param array $attributes
*/
public function __construct($legend='', $attributes = array()) {
// this is a bit messy and we just do it for the nicer class hierarchy
// the parent would expect the tag in $value but we're storing the
// legend there, so we have to set the type manually
parent::__construct($legend, $attributes);
$this->type = 'fieldsetopen';
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
$html = '<fieldset '.buildAttributes($this->attrs()).'>';
$legend = $this->val();
if($legend) $html .= DOKU_LF.'<legend>'.hsc($legend).'</legend>';
return $html;
}
}

462
content/inc/Form/Form.php Normal file
View File

@@ -0,0 +1,462 @@
<?php
namespace dokuwiki\Form;
/**
* Class Form
*
* Represents the whole Form. This is what you work on, and add Elements to
*
* @package dokuwiki\Form
*/
class Form extends Element {
/**
* @var array name value pairs for hidden values
*/
protected $hidden = array();
/**
* @var Element[] the elements of the form
*/
protected $elements = array();
/**
* Creates a new, empty form with some default attributes
*
* @param array $attributes
* @param bool $unsafe if true, then the security token is ommited
*/
public function __construct($attributes = array(), $unsafe = false) {
global $ID;
parent::__construct('form', $attributes);
// use the current URL as default action
if(!$this->attr('action')) {
$get = $_GET;
if(isset($get['id'])) unset($get['id']);
$self = wl($ID, $get, false, '&'); //attributes are escaped later
$this->attr('action', $self);
}
// post is default
if(!$this->attr('method')) {
$this->attr('method', 'post');
}
// we like UTF-8
if(!$this->attr('accept-charset')) {
$this->attr('accept-charset', 'utf-8');
}
// add the security token by default
if (!$unsafe) {
$this->setHiddenField('sectok', getSecurityToken());
}
// identify this as a new form based form in HTML
$this->addClass('doku_form');
}
/**
* Sets a hidden field
*
* @param string $name
* @param string $value
* @return $this
*/
public function setHiddenField($name, $value) {
$this->hidden[$name] = $value;
return $this;
}
#region element query function
/**
* Returns the numbers of elements in the form
*
* @return int
*/
public function elementCount() {
return count($this->elements);
}
/**
* Get the position of the element in the form or false if it is not in the form
*
* 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.
*
* @param Element $element
*
* @return false|int
*/
public function getElementPosition(Element $element)
{
return array_search($element, $this->elements, true);
}
/**
* Returns a reference to the element at a position.
* A position out-of-bounds will return either the
* first (underflow) or last (overflow) element.
*
* @param int $pos
* @return Element
*/
public function getElementAt($pos) {
if($pos < 0) $pos = count($this->elements) + $pos;
if($pos < 0) $pos = 0;
if($pos >= count($this->elements)) $pos = count($this->elements) - 1;
return $this->elements[$pos];
}
/**
* Gets the position of the first of a type of element
*
* @param string $type Element type to look for.
* @param int $offset search from this position onward
* @return false|int position of element if found, otherwise false
*/
public function findPositionByType($type, $offset = 0) {
$len = $this->elementCount();
for($pos = $offset; $pos < $len; $pos++) {
if($this->elements[$pos]->getType() == $type) {
return $pos;
}
}
return false;
}
/**
* Gets the position of the first element matching the attribute
*
* @param string $name Name of the attribute
* @param string $value Value the attribute should have
* @param int $offset search from this position onward
* @return false|int position of element if found, otherwise false
*/
public function findPositionByAttribute($name, $value, $offset = 0) {
$len = $this->elementCount();
for($pos = $offset; $pos < $len; $pos++) {
if($this->elements[$pos]->attr($name) == $value) {
return $pos;
}
}
return false;
}
#endregion
#region Element positioning functions
/**
* Adds or inserts an element to the form
*
* @param Element $element
* @param int $pos 0-based position in the form, -1 for at the end
* @return Element
*/
public function addElement(Element $element, $pos = -1) {
if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
'You can\'t add a form to a form'
);
if($pos < 0) {
$this->elements[] = $element;
} else {
array_splice($this->elements, $pos, 0, array($element));
}
return $element;
}
/**
* Replaces an existing element with a new one
*
* @param Element $element the new element
* @param int $pos 0-based position of the element to replace
*/
public function replaceElement(Element $element, $pos) {
if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
'You can\'t add a form to a form'
);
array_splice($this->elements, $pos, 1, array($element));
}
/**
* Remove an element from the form completely
*
* @param int $pos 0-based position of the element to remove
*/
public function removeElement($pos) {
array_splice($this->elements, $pos, 1);
}
#endregion
#region Element adding functions
/**
* Adds a text input field
*
* @param string $name
* @param string $label
* @param int $pos
* @return InputElement
*/
public function addTextInput($name, $label = '', $pos = -1) {
return $this->addElement(new InputElement('text', $name, $label), $pos);
}
/**
* Adds a password input field
*
* @param string $name
* @param string $label
* @param int $pos
* @return InputElement
*/
public function addPasswordInput($name, $label = '', $pos = -1) {
return $this->addElement(new InputElement('password', $name, $label), $pos);
}
/**
* Adds a radio button field
*
* @param string $name
* @param string $label
* @param int $pos
* @return CheckableElement
*/
public function addRadioButton($name, $label = '', $pos = -1) {
return $this->addElement(new CheckableElement('radio', $name, $label), $pos);
}
/**
* Adds a checkbox field
*
* @param string $name
* @param string $label
* @param int $pos
* @return CheckableElement
*/
public function addCheckbox($name, $label = '', $pos = -1) {
return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
}
/**
* Adds a dropdown field
*
* @param string $name
* @param array $options
* @param string $label
* @param int $pos
* @return DropdownElement
*/
public function addDropdown($name, $options, $label = '', $pos = -1) {
return $this->addElement(new DropdownElement($name, $options, $label), $pos);
}
/**
* Adds a textarea field
*
* @param string $name
* @param string $label
* @param int $pos
* @return TextareaElement
*/
public function addTextarea($name, $label = '', $pos = -1) {
return $this->addElement(new TextareaElement($name, $label), $pos);
}
/**
* Adds a simple button, escapes the content for you
*
* @param string $name
* @param string $content
* @param int $pos
* @return Element
*/
public function addButton($name, $content, $pos = -1) {
return $this->addElement(new ButtonElement($name, hsc($content)), $pos);
}
/**
* Adds a simple button, allows HTML for content
*
* @param string $name
* @param string $html
* @param int $pos
* @return Element
*/
public function addButtonHTML($name, $html, $pos = -1) {
return $this->addElement(new ButtonElement($name, $html), $pos);
}
/**
* Adds a label referencing another input element, escapes the label for you
*
* @param string $label
* @param string $for
* @param int $pos
* @return Element
*/
public function addLabel($label, $for='', $pos = -1) {
return $this->addLabelHTML(hsc($label), $for, $pos);
}
/**
* Adds a label referencing another input element, allows HTML for content
*
* @param string $content
* @param string|Element $for
* @param int $pos
* @return Element
*/
public function addLabelHTML($content, $for='', $pos = -1) {
$element = new LabelElement(hsc($content));
if(is_a($for, '\dokuwiki\Form\Element')) {
/** @var Element $for */
$for = $for->id();
}
$for = (string) $for;
if($for !== '') {
$element->attr('for', $for);
}
return $this->addElement($element, $pos);
}
/**
* Add fixed HTML to the form
*
* @param string $html
* @param int $pos
* @return HTMLElement
*/
public function addHTML($html, $pos = -1) {
return $this->addElement(new HTMLElement($html), $pos);
}
/**
* Add a closed HTML tag to the form
*
* @param string $tag
* @param int $pos
* @return TagElement
*/
public function addTag($tag, $pos = -1) {
return $this->addElement(new TagElement($tag), $pos);
}
/**
* Add an open HTML tag to the form
*
* Be sure to close it again!
*
* @param string $tag
* @param int $pos
* @return TagOpenElement
*/
public function addTagOpen($tag, $pos = -1) {
return $this->addElement(new TagOpenElement($tag), $pos);
}
/**
* Add a closing HTML tag to the form
*
* Be sure it had been opened before
*
* @param string $tag
* @param int $pos
* @return TagCloseElement
*/
public function addTagClose($tag, $pos = -1) {
return $this->addElement(new TagCloseElement($tag), $pos);
}
/**
* Open a Fieldset
*
* @param string $legend
* @param int $pos
* @return FieldsetOpenElement
*/
public function addFieldsetOpen($legend = '', $pos = -1) {
return $this->addElement(new FieldsetOpenElement($legend), $pos);
}
/**
* Close a fieldset
*
* @param int $pos
* @return TagCloseElement
*/
public function addFieldsetClose($pos = -1) {
return $this->addElement(new FieldsetCloseElement(), $pos);
}
#endregion
/**
* Adjust the elements so that fieldset open and closes are matching
*/
protected function balanceFieldsets() {
$lastclose = 0;
$isopen = false;
$len = count($this->elements);
for($pos = 0; $pos < $len; $pos++) {
$type = $this->elements[$pos]->getType();
if($type == 'fieldsetopen') {
if($isopen) {
//close previous fieldset
$this->addFieldsetClose($pos);
$lastclose = $pos + 1;
$pos++;
$len++;
}
$isopen = true;
} else if($type == 'fieldsetclose') {
if(!$isopen) {
// make sure there was a fieldsetopen
// either right after the last close or at the begining
$this->addFieldsetOpen('', $lastclose);
$len++;
$pos++;
}
$lastclose = $pos;
$isopen = false;
}
}
// close open fieldset at the end
if($isopen) {
$this->addFieldsetClose();
}
}
/**
* The HTML representation of the whole form
*
* @return string
*/
public function toHTML() {
$this->balanceFieldsets();
$html = '<form ' . buildAttributes($this->attrs()) . '>';
foreach($this->hidden as $name => $value) {
$html .= '<input type="hidden" name="' . $name . '" value="' . formText($value) . '" />';
}
foreach($this->elements as $element) {
$html .= $element->toHTML();
}
$html .= '</form>';
return $html;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace dokuwiki\Form;
/**
* Class HTMLElement
*
* Holds arbitrary HTML that is added as is to the Form
*
* @package dokuwiki\Form
*/
class HTMLElement extends ValueElement {
/**
* @param string $html
*/
public function __construct($html) {
parent::__construct('html', $html);
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
return $this->val();
}
}

View File

@@ -0,0 +1,159 @@
<?php
namespace dokuwiki\Form;
/**
* Class InputElement
*
* Base class for all input elements. Uses a wrapping label when label
* text is given.
*
* @todo figure out how to make wrapping or related label configurable
* @package dokuwiki\Form
*/
class InputElement extends Element {
/**
* @var LabelElement
*/
protected $label = null;
/**
* @var bool if the element should reflect posted values
*/
protected $useInput = true;
/**
* @param string $type The type of this element
* @param string $name The name of this form element
* @param string $label The label text for this element (will be autoescaped)
*/
public function __construct($type, $name, $label = '') {
parent::__construct($type, array('name' => $name));
$this->attr('name', $name);
$this->attr('type', $type);
if($label) $this->label = new LabelElement($label);
}
/**
* Returns the label element if there's one set
*
* @return LabelElement|null
*/
public function getLabel() {
return $this->label;
}
/**
* Should the user sent input be used to initialize the input field
*
* The default is true. Any set values will be overwritten by the INPUT
* provided values.
*
* @param bool $useinput
* @return $this
*/
public function useInput($useinput) {
$this->useInput = (bool) $useinput;
return $this;
}
/**
* Get or set the element's ID
*
* @param null|string $id
* @return string|$this
*/
public function id($id = null) {
if($this->label) $this->label->attr('for', $id);
return parent::id($id);
}
/**
* Adds a class to the class attribute
*
* This is the preferred method of setting the element's class
*
* @param string $class the new class to add
* @return $this
*/
public function addClass($class) {
if($this->label) $this->label->addClass($class);
return parent::addClass($class);
}
/**
* Figures out how to access the value for this field from INPUT data
*
* The element's name could have been given as a simple string ('foo')
* or in array notation ('foo[bar]').
*
* Note: this function only handles one level of arrays. If your data
* is nested deeper, you should call useInput(false) and set the
* correct value yourself
*
* @return array name and array key (null if not an array)
*/
protected function getInputName() {
$name = $this->attr('name');
parse_str("$name=1", $parsed);
$name = array_keys($parsed);
$name = array_shift($name);
if(is_array($parsed[$name])) {
$key = array_keys($parsed[$name]);
$key = array_shift($key);
} else {
$key = null;
}
return array($name, $key);
}
/**
* Handles the useInput flag and set the value attribute accordingly
*/
protected function prefillInput() {
global $INPUT;
list($name, $key) = $this->getInputName();
if(!$INPUT->has($name)) return;
if($key === null) {
$value = $INPUT->str($name);
} else {
$value = $INPUT->arr($name);
if(isset($value[$key])) {
$value = $value[$key];
} else {
$value = '';
}
}
$this->val($value);
}
/**
* The HTML representation of this element
*
* @return string
*/
protected function mainElementHTML() {
if($this->useInput) $this->prefillInput();
return '<input ' . buildAttributes($this->attrs()) . ' />';
}
/**
* The HTML representation of this element wrapped in a label
*
* @return string
*/
public function toHTML() {
if($this->label) {
return '<label ' . buildAttributes($this->label->attrs()) . '>' . DOKU_LF .
'<span>' . hsc($this->label->val()) . '</span>' . DOKU_LF .
$this->mainElementHTML() . DOKU_LF .
'</label>';
} else {
return $this->mainElementHTML();
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace dokuwiki\Form;
/**
* Class Label
* @package dokuwiki\Form
*/
class LabelElement extends ValueElement {
/**
* Creates a new Label
*
* @param string $label This is is raw HTML and will not be escaped
*/
public function __construct($label) {
parent::__construct('label', $label);
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
return '<label ' . buildAttributes($this->attrs()) . '>' . $this->val() . '</label>';
}
}

View File

@@ -0,0 +1,181 @@
<?php
namespace dokuwiki\Form;
/**
* Class LegacyForm
*
* Provides a compatibility layer to the old Doku_Form API
*
* This can be used to work with the modern API on forms provided by old events for
* example. When you start new forms, just use Form\Form
*
* @package dokuwiki\Form
*/
class LegacyForm extends Form {
/**
* Creates a new modern form from an old legacy Doku_Form
*
* @param \Doku_Form $oldform
*/
public function __construct(\Doku_Form $oldform) {
parent::__construct($oldform->params);
$this->hidden = $oldform->_hidden;
foreach($oldform->_content as $element) {
list($ctl, $attr) = $this->parseLegacyAttr($element);
if(is_array($element)) {
switch($ctl['elem']) {
case 'wikitext':
$this->addTextarea('wikitext')
->attrs($attr)
->id('wiki__text')
->val($ctl['text'])
->addClass($ctl['class']);
break;
case 'textfield':
$this->addTextInput($ctl['name'], $ctl['text'])
->attrs($attr)
->id($ctl['id'])
->addClass($ctl['class']);
break;
case 'passwordfield':
$this->addPasswordInput($ctl['name'], $ctl['text'])
->attrs($attr)
->id($ctl['id'])
->addClass($ctl['class']);
break;
case 'checkboxfield':
$this->addCheckbox($ctl['name'], $ctl['text'])
->attrs($attr)
->id($ctl['id'])
->addClass($ctl['class']);
break;
case 'radiofield':
$this->addRadioButton($ctl['name'], $ctl['text'])
->attrs($attr)
->id($ctl['id'])
->addClass($ctl['class']);
break;
case 'tag':
$this->addTag($ctl['tag'])
->attrs($attr)
->attr('name', $ctl['name'])
->id($ctl['id'])
->addClass($ctl['class']);
break;
case 'opentag':
$this->addTagOpen($ctl['tag'])
->attrs($attr)
->attr('name', $ctl['name'])
->id($ctl['id'])
->addClass($ctl['class']);
break;
case 'closetag':
$this->addTagClose($ctl['tag']);
break;
case 'openfieldset':
$this->addFieldsetOpen($ctl['legend'])
->attrs($attr)
->attr('name', $ctl['name'])
->id($ctl['id'])
->addClass($ctl['class']);
break;
case 'closefieldset':
$this->addFieldsetClose();
break;
case 'button':
case 'field':
case 'fieldright':
case 'filefield':
case 'menufield':
case 'listboxfield':
throw new \UnexpectedValueException('Unsupported legacy field ' . $ctl['elem']);
break;
default:
throw new \UnexpectedValueException('Unknown legacy field ' . $ctl['elem']);
}
} else {
$this->addHTML($element);
}
}
}
/**
* Parses out what is the elements attributes and what is control info
*
* @param array $legacy
* @return array
*/
protected function parseLegacyAttr($legacy) {
$attributes = array();
$control = array();
foreach($legacy as $key => $val) {
if($key[0] == '_') {
$control[substr($key, 1)] = $val;
} elseif($key == 'name') {
$control[$key] = $val;
} elseif($key == 'id') {
$control[$key] = $val;
} else {
$attributes[$key] = $val;
}
}
return array($control, $attributes);
}
/**
* Translates our types to the legacy types
*
* @param string $type
* @return string
*/
protected function legacyType($type) {
static $types = array(
'text' => 'textfield',
'password' => 'passwordfield',
'checkbox' => 'checkboxfield',
'radio' => 'radiofield',
'tagopen' => 'opentag',
'tagclose' => 'closetag',
'fieldsetopen' => 'openfieldset',
'fieldsetclose' => 'closefieldset',
);
if(isset($types[$type])) return $types[$type];
return $type;
}
/**
* Creates an old legacy form from this modern form's data
*
* @return \Doku_Form
*/
public function toLegacy() {
$this->balanceFieldsets();
$legacy = new \Doku_Form($this->attrs());
$legacy->_hidden = $this->hidden;
foreach($this->elements as $element) {
if(is_a($element, 'dokuwiki\Form\HTMLElement')) {
$legacy->_content[] = $element->toHTML();
} elseif(is_a($element, 'dokuwiki\Form\InputElement')) {
/** @var InputElement $element */
$data = $element->attrs();
$data['_elem'] = $this->legacyType($element->getType());
$label = $element->getLabel();
if($label) {
$data['_class'] = $label->attr('class');
}
$legacy->_content[] = $data;
}
}
return $legacy;
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace dokuwiki\Form;
class OptGroup extends Element {
protected $options = array();
protected $value;
/**
* @param string $label The label text for this element (will be autoescaped)
* @param array $options The available options
*/
public function __construct($label, $options) {
parent::__construct('optGroup', array('label' => $label));
$this->options($options);
}
/**
* Store the given value so it can be used during rendering
*
* This is intended to be only called from within @see DropdownElement::val()
*
* @param string $value
* @return bool true if an option with the given value exists, false otherwise
*/
public function storeValue($value) {
$this->value = $value;
return isset($this->options[$value]);
}
/**
* Get or set the options of the optgroup
*
* Options can be given as associative array (value => label) or as an
* indexd array (label = value) or as an array of arrays. In the latter
* case an element has to look as follows:
* option-value => array (
* 'label' => option-label,
* 'attrs' => array (
* attr-key => attr-value, ...
* )
* )
*
* @param null|array $options
* @return $this|array
*/
public function options($options = null) {
if($options === null) return $this->options;
if(!is_array($options)) throw new \InvalidArgumentException('Options have to be an array');
$this->options = array();
foreach($options as $key => $val) {
if (is_array($val)) {
if (!key_exists('label', $val)) throw new \InvalidArgumentException(
'If option is given as array, it has to have a "label"-key!'
);
if (key_exists('attrs', $val) && is_array($val['attrs']) && key_exists('selected', $val['attrs'])) {
throw new \InvalidArgumentException(
'Please use function "DropdownElement::val()" to set the selected option'
);
}
$this->options[$key] = $val;
} elseif(is_int($key)) {
$this->options[$val] = array('label' => (string) $val);
} else {
$this->options[$key] = array('label' => (string) $val);
}
}
return $this;
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
if ($this->attributes['label'] === null) {
return $this->renderOptions();
}
$html = '<optgroup '. buildAttributes($this->attrs()) . '>';
$html .= $this->renderOptions();
$html .= '</optgroup>';
return $html;
}
/**
* @return string
*/
protected function renderOptions() {
$html = '';
foreach($this->options as $key => $val) {
$selected = ((string)$key === (string)$this->value) ? ' selected="selected"' : '';
$attrs = '';
if (!empty($val['attrs']) && is_array($val['attrs'])) {
$attrs = buildAttributes($val['attrs']);
}
$html .= '<option' . $selected . ' value="' . hsc($key) . '" '.$attrs.'>';
$html .= hsc($val['label']);
$html .= '</option>';
}
return $html;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace dokuwiki\Form;
/**
* Class TagCloseElement
*
* Creates an HTML close tag. You have to make sure it has been opened
* before or this will produce invalid HTML
*
* @package dokuwiki\Form
*/
class TagCloseElement extends ValueElement {
/**
* @param string $tag
* @param array $attributes
*/
public function __construct($tag, $attributes = array()) {
parent::__construct('tagclose', $tag, $attributes);
}
/**
* do not call this
*
* @param string $class
* @return void
* @throws \BadMethodCallException
*/
public function addClass($class) {
throw new \BadMethodCallException('You can\t add classes to closing tag');
}
/**
* do not call this
*
* @param null|string $id
* @return string
* @throws \BadMethodCallException
*/
public function id($id = null) {
if ($id === null) {
return '';
} else {
throw new \BadMethodCallException('You can\t add ID to closing tag');
}
}
/**
* do not call this
*
* @param string $name
* @param null|string $value
* @return string
* @throws \BadMethodCallException
*/
public function attr($name, $value = null) {
if ($value === null) {
return '';
} else {
throw new \BadMethodCallException('You can\t add attributes to closing tag');
}
}
/**
* do not call this
*
* @param array|null $attributes
* @return array
* @throws \BadMethodCallException
*/
public function attrs($attributes = null) {
if ($attributes === null) {
return array();
} else {
throw new \BadMethodCallException('You can\t add attributes to closing tag');
}
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
return '</'.$this->val().'>';
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace dokuwiki\Form;
/**
* Class TagElement
*
* Creates a self closing HTML tag
*
* @package dokuwiki\Form
*/
class TagElement extends ValueElement {
/**
* @param string $tag
* @param array $attributes
*/
public function __construct($tag, $attributes = array()) {
parent::__construct('tag', $tag, $attributes);
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
return '<'.$this->val().' '.buildAttributes($this->attrs()).' />';
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace dokuwiki\Form;
/**
* Class TagOpenElement
*
* Creates an open HTML tag. You have to make sure you close it
* again or this will produce invalid HTML
*
* @package dokuwiki\Form
*/
class TagOpenElement extends ValueElement {
/**
* @param string $tag
* @param array $attributes
*/
public function __construct($tag, $attributes = array()) {
parent::__construct('tagopen', $tag, $attributes);
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
return '<'.$this->val().' '.buildAttributes($this->attrs()).'>';
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace dokuwiki\Form;
/**
* Class TextareaElement
* @package dokuwiki\Form
*/
class TextareaElement extends InputElement {
/**
* @var string the actual text within the area
*/
protected $text;
/**
* @param string $name The name of this form element
* @param string $label The label text for this element
*/
public function __construct($name, $label) {
parent::__construct('textarea', $name, $label);
$this->attr('dir', 'auto');
}
/**
* Get or set the element's value
*
* This is the preferred way of setting the element's value
*
* @param null|string $value
* @return string|$this
*/
public function val($value = null) {
if($value !== null) {
$this->text = cleanText($value);
return $this;
}
return $this->text;
}
/**
* The HTML representation of this element
*
* @return string
*/
protected function mainElementHTML() {
if($this->useInput) $this->prefillInput();
return '<textarea ' . buildAttributes($this->attrs()) . '>' .
formText($this->val()) . '</textarea>';
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace dokuwiki\Form;
/**
* Class ValueElement
*
* Just like an Element but it's value is not part of its attributes
*
* What the value is (tag name, content, etc) is defined by the actual implementations
*
* @package dokuwiki\Form
*/
abstract class ValueElement extends Element {
/**
* @var string holds the element's value
*/
protected $value = '';
/**
* @param string $type
* @param string $value
* @param array $attributes
*/
public function __construct($type, $value, $attributes = array()) {
parent::__construct($type, $attributes);
$this->val($value);
}
/**
* Get or set the element's value
*
* @param null|string $value
* @return string|$this
*/
public function val($value = null) {
if($value !== null) {
$this->value = $value;
return $this;
}
return $this->value;
}
}