summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Gohr <andi@splitbrain.org>2015-08-03 18:47:19 +0200
committerAndreas Gohr <andi@splitbrain.org>2015-08-03 18:47:19 +0200
commit8909ab7629ec67708b589c35d380c68da04a8b44 (patch)
treea35ec57bebc7c0fcc45853fe377b5b0fcc2d5846
parentb20571a714d774d231f80f95643c6f84ef9833ed (diff)
parent3c8e094acdcf6d556f022bc224b81ef17e449281 (diff)
downloadrpg-8909ab7629ec67708b589c35d380c68da04a8b44.tar.gz
rpg-8909ab7629ec67708b589c35d380c68da04a8b44.tar.bz2
Merge pull request #1265 from splitbrain/form2
Form 2
-rw-r--r--_test/tests/inc/form/checkableelement.test.php49
-rw-r--r--_test/tests/inc/form/form.test.php115
-rw-r--r--_test/tests/inc/form/inputelement.test.php41
-rw-r--r--inc/Form/CheckableElement.php62
-rw-r--r--inc/Form/Element.php151
-rw-r--r--inc/Form/FieldsetCloseElement.php30
-rw-r--r--inc/Form/FieldsetOpenElement.php36
-rw-r--r--inc/Form/Form.php367
-rw-r--r--inc/Form/HTMLElement.php29
-rw-r--r--inc/Form/InputElement.php160
-rw-r--r--inc/Form/Label.php27
-rw-r--r--inc/Form/LegacyForm.php181
-rw-r--r--inc/Form/TagCloseElement.php76
-rw-r--r--inc/Form/TagElement.php29
-rw-r--r--inc/Form/TagOpenElement.php30
-rw-r--r--inc/Form/TextareaElement.php51
-rw-r--r--inc/Form/ValueElement.php45
-rw-r--r--inc/load.php6
18 files changed, 1485 insertions, 0 deletions
diff --git a/_test/tests/inc/form/checkableelement.test.php b/_test/tests/inc/form/checkableelement.test.php
new file mode 100644
index 000000000..a0e4173e8
--- /dev/null
+++ b/_test/tests/inc/form/checkableelement.test.php
@@ -0,0 +1,49 @@
+<?php
+
+use dokuwiki\Form;
+
+class form_checkableelement_test extends DokuWikiTest {
+
+ function test_defaults() {
+ $form = new Form\Form();
+ $form->addRadioButton('foo', 'label text first')->val('first')->attr('checked', 'checked');
+ $form->addRadioButton('foo', 'label text second')->val('second');
+
+ $html = $form->toHTML();
+ $pq = phpQuery::newDocumentXHTML($html);
+
+ $input = $pq->find('input[name=foo]');
+ $this->assertTrue($input->length == 2);
+
+ $label = $pq->find('label');
+ $this->assertTrue($label->length == 2);
+
+ $inputs = $pq->find('input[name=foo]');
+ $this->assertEquals('first', pq($inputs->elements[0])->val());
+ $this->assertEquals('second', pq($inputs->elements[1])->val());
+ $this->assertEquals('checked', pq($inputs->elements[0])->attr('checked'));
+ $this->assertEquals('', pq($inputs->elements[1])->attr('checked'));
+ }
+
+ /**
+ * check that posted values overwrite preset default
+ */
+ function test_prefill() {
+ global $INPUT;
+ $INPUT->post->set('foo', 'second');
+
+
+ $form = new Form\Form();
+ $form->addRadioButton('foo', 'label text first')->val('first')->attr('checked', 'checked');
+ $form->addRadioButton('foo', 'label text second')->val('second');
+
+ $html = $form->toHTML();
+ $pq = phpQuery::newDocumentXHTML($html);
+
+ $inputs = $pq->find('input[name=foo]');
+ $this->assertEquals('first', pq($inputs->elements[0])->val());
+ $this->assertEquals('second', pq($inputs->elements[1])->val());
+ $this->assertEquals('', pq($inputs->elements[0])->attr('checked'));
+ $this->assertEquals('checked', pq($inputs->elements[1])->attr('checked'));
+ }
+}
diff --git a/_test/tests/inc/form/form.test.php b/_test/tests/inc/form/form.test.php
new file mode 100644
index 000000000..3ae832b2c
--- /dev/null
+++ b/_test/tests/inc/form/form.test.php
@@ -0,0 +1,115 @@
+<?php
+
+use dokuwiki\Form;
+
+/**
+ * makes form internals accessible for testing
+ */
+class TestForm extends Form\Form {
+ /**
+ * @return array list of element types
+ */
+ function getElementTypeList() {
+ $list = array();
+ foreach($this->elements as $element) $list[] = $element->getType();
+ return $list;
+ }
+
+ public function balanceFieldsets() {
+ parent::balanceFieldsets();
+ }
+
+}
+
+class form_form_test extends DokuWikiTest {
+
+ /**
+ * checks that an empty form is initialized correctly
+ */
+ function test_defaults() {
+ global $INPUT;
+ global $ID;
+ $ID = 'some:test';
+ $INPUT->get->set('id', $ID);
+ $INPUT->get->set('foo', 'bar');
+
+ $form = new Form\Form();
+ $html = $form->toHTML();
+ $pq = phpQuery::newDocumentXHTML($html);
+
+ $this->assertTrue($pq->find('form')->hasClass('doku_form'));
+ $this->assertEquals(wl($ID, array('foo' => 'bar'), false, '&'), $pq->find('form')->attr('action'));
+ $this->assertEquals('post', $pq->find('form')->attr('method'));
+
+ $this->assertTrue($pq->find('input[name=sectok]')->length == 1);
+ }
+
+
+ function test_fieldsetbalance() {
+ $form = new TestForm();
+ $form->addFieldsetOpen();
+ $form->addHTML('ignored');
+ $form->addFieldsetClose();
+ $form->balanceFieldsets();
+
+ $this->assertEquals(
+ array(
+ 'fieldsetopen',
+ 'html',
+ 'fieldsetclose'
+ ),
+ $form->getElementTypeList()
+ );
+
+ $form = new TestForm();
+ $form->addHTML('ignored');
+ $form->addFieldsetClose();
+ $form->balanceFieldsets();
+
+ $this->assertEquals(
+ array(
+ 'fieldsetopen',
+ 'html',
+ 'fieldsetclose'
+ ),
+ $form->getElementTypeList()
+ );
+
+
+ $form = new TestForm();
+ $form->addFieldsetOpen();
+ $form->addHTML('ignored');
+ $form->balanceFieldsets();
+
+ $this->assertEquals(
+ array(
+ 'fieldsetopen',
+ 'html',
+ 'fieldsetclose'
+ ),
+ $form->getElementTypeList()
+ );
+
+ $form = new TestForm();
+ $form->addHTML('ignored');
+ $form->addFieldsetClose();
+ $form->addHTML('ignored');
+ $form->addFieldsetOpen();
+ $form->addHTML('ignored');
+ $form->balanceFieldsets();
+
+ $this->assertEquals(
+ array(
+ 'fieldsetopen',
+ 'html',
+ 'fieldsetclose',
+ 'html',
+ 'fieldsetopen',
+ 'html',
+ 'fieldsetclose'
+ ),
+ $form->getElementTypeList()
+ );
+ }
+
+}
diff --git a/_test/tests/inc/form/inputelement.test.php b/_test/tests/inc/form/inputelement.test.php
new file mode 100644
index 000000000..7a5e6d2ea
--- /dev/null
+++ b/_test/tests/inc/form/inputelement.test.php
@@ -0,0 +1,41 @@
+<?php
+
+use dokuwiki\Form;
+
+class form_inputelement_test extends DokuWikiTest {
+
+ function test_defaults() {
+ $form = new Form\Form();
+ $form->addTextInput('foo', 'label text')->val('this is text');
+
+ $html = $form->toHTML();
+ $pq = phpQuery::newDocumentXHTML($html);
+
+ $input = $pq->find('input[name=foo]');
+ $this->assertTrue($input->length == 1);
+ $this->assertEquals('this is text', $input->val());
+
+ $label = $pq->find('label');
+ $this->assertTrue($label->length == 1);
+ $this->assertEquals('label text', $label->find('span')->text());
+ }
+
+ /**
+ * check that posted values overwrite preset default
+ */
+ function test_prefill() {
+ global $INPUT;
+ $INPUT->post->set('foo', 'a new text');
+
+ $form = new Form\Form();
+ $form->addTextInput('foo', 'label text')->val('this is text');
+
+ $html = $form->toHTML();
+ $pq = phpQuery::newDocumentXHTML($html);
+
+ $input = $pq->find('input[name=foo]');
+ $this->assertTrue($input->length == 1);
+ $this->assertEquals('a new text', $input->val());
+ }
+
+}
diff --git a/inc/Form/CheckableElement.php b/inc/Form/CheckableElement.php
new file mode 100644
index 000000000..a57bbc1f6
--- /dev/null
+++ b/inc/Form/CheckableElement.php
@@ -0,0 +1,62 @@
+<?php
+namespace dokuwiki\Form;
+
+/**
+ * Class CheckableElement
+ *
+ * For Radio- and Checkboxes
+ *
+ * @package DokuForm
+ */
+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');
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/inc/Form/Element.php b/inc/Form/Element.php
new file mode 100644
index 000000000..7938ee009
--- /dev/null
+++ b/inc/Form/Element.php
@@ -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 $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();
+}
diff --git a/inc/Form/FieldsetCloseElement.php b/inc/Form/FieldsetCloseElement.php
new file mode 100644
index 000000000..8f26717aa
--- /dev/null
+++ b/inc/Form/FieldsetCloseElement.php
@@ -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>';
+ }
+}
diff --git a/inc/Form/FieldsetOpenElement.php b/inc/Form/FieldsetOpenElement.php
new file mode 100644
index 000000000..a7de461fa
--- /dev/null
+++ b/inc/Form/FieldsetOpenElement.php
@@ -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;
+ }
+}
diff --git a/inc/Form/Form.php b/inc/Form/Form.php
new file mode 100644
index 000000000..625557fa1
--- /dev/null
+++ b/inc/Form/Form.php
@@ -0,0 +1,367 @@
+<?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
+ */
+ public function __construct($attributes = array()) {
+ 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
+ $this->setHiddenField('sectok', getSecurityToken());
+
+ // identify this as a new form based form in HTML
+ $this->addClass('doku_form');
+ }
+
+ /**
+ * Sets a hidden field
+ *
+ * @param $name
+ * @param $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);
+ }
+
+ /**
+ * 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 $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')) 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 $pos 0-based position of the element to replace
+ */
+ public function replaceElement(Element $element, $pos) {
+ if(is_a($element, '\dokuwiki\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 $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 $name
+ * @param $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 $name
+ * @param $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 $name
+ * @param $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 $name
+ * @param $label
+ * @param int $pos
+ * @return CheckableElement
+ */
+ public function addCheckbox($name, $label = '', $pos = -1) {
+ return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
+ }
+
+ /**
+ * Adds a textarea field
+ *
+ * @param $name
+ * @param $label
+ * @param int $pos
+ * @return TextareaElement
+ */
+ public function addTextarea($name, $label = '', $pos = -1) {
+ return $this->addElement(new TextareaElement($name, $label), $pos);
+ }
+
+ /**
+ * Add fixed HTML to the form
+ *
+ * @param $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 $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 $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 $tag
+ * @param int $pos
+ * @return TagCloseElement
+ */
+ public function addTagClose($tag, $pos = -1) {
+ return $this->addElement(new TagCloseElement($tag), $pos);
+ }
+
+ /**
+ * Open a Fieldset
+ *
+ * @param $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()) . '>' . DOKU_LF;
+
+ foreach($this->hidden as $name => $value) {
+ $html .= '<input type="hidden" name="' . $name . '" value="' . formText($value) . '" />' . DOKU_LF;
+ }
+
+ foreach($this->elements as $element) {
+ $html .= $element->toHTML() . DOKU_LF;
+ }
+
+ $html .= '</form>' . DOKU_LF;
+
+ return $html;
+ }
+}
diff --git a/inc/Form/HTMLElement.php b/inc/Form/HTMLElement.php
new file mode 100644
index 000000000..591cf472f
--- /dev/null
+++ b/inc/Form/HTMLElement.php
@@ -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();
+ }
+}
diff --git a/inc/Form/InputElement.php b/inc/Form/InputElement.php
new file mode 100644
index 000000000..5908f7d11
--- /dev/null
+++ b/inc/Form/InputElement.php
@@ -0,0 +1,160 @@
+<?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 Label
+ */
+ 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
+ */
+ public function __construct($type, $name, $label = '') {
+ parent::__construct($type, array('name' => $name));
+ $this->attr('name', $name);
+ if($label) $this->label = new Label($label);
+ }
+
+ /**
+ * Returns the label element if there's one set
+ *
+ * @return Label|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 = '';
+ }
+ }
+ if($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();
+ }
+ }
+}
diff --git a/inc/Form/Label.php b/inc/Form/Label.php
new file mode 100644
index 000000000..8dcd7cd5f
--- /dev/null
+++ b/inc/Form/Label.php
@@ -0,0 +1,27 @@
+<?php
+namespace dokuwiki\Form;
+
+/**
+ * Class Label
+ * @package dokuwiki\Form
+ */
+class Label extends ValueElement {
+
+ /**
+ * Creates a new Label
+ *
+ * @param string $label
+ */
+ public function __construct($label) {
+ parent::__construct('label', $label);
+ }
+
+ /**
+ * The HTML representation of this element
+ *
+ * @return string
+ */
+ public function toHTML() {
+ return '<label ' . buildAttributes($this->attrs()) . '>' . hsc($this->val()) . '</label>';
+ }
+}
diff --git a/inc/Form/LegacyForm.php b/inc/Form/LegacyForm.php
new file mode 100644
index 000000000..1b47ba204
--- /dev/null
+++ b/inc/Form/LegacyForm.php
@@ -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;
+ }
+}
diff --git a/inc/Form/TagCloseElement.php b/inc/Form/TagCloseElement.php
new file mode 100644
index 000000000..dc0264c21
--- /dev/null
+++ b/inc/Form/TagCloseElement.php
@@ -0,0 +1,76 @@
+<?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 $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 $id
+ * @return void
+ * @throws \BadMethodCallException
+ */
+ public function id($id = null) {
+ throw new \BadMethodCallException('You can\t add ID to closing tag');
+ }
+
+ /**
+ * do not call this
+ *
+ * @param $name
+ * @param $value
+ * @return void
+ * @throws \BadMethodCallException
+ */
+ public function attr($name, $value = null) {
+ throw new \BadMethodCallException('You can\t add attributes to closing tag');
+ }
+
+ /**
+ * do not call this
+ *
+ * @param $attributes
+ * @return void
+ * @throws \BadMethodCallException
+ */
+ public function attrs($attributes = null) {
+ 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().'>';
+ }
+
+}
diff --git a/inc/Form/TagElement.php b/inc/Form/TagElement.php
new file mode 100644
index 000000000..ea5144c9c
--- /dev/null
+++ b/inc/Form/TagElement.php
@@ -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()).' />';
+ }
+}
diff --git a/inc/Form/TagOpenElement.php b/inc/Form/TagOpenElement.php
new file mode 100644
index 000000000..0afe97b45
--- /dev/null
+++ b/inc/Form/TagOpenElement.php
@@ -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()).'>';
+ }
+}
diff --git a/inc/Form/TextareaElement.php b/inc/Form/TextareaElement.php
new file mode 100644
index 000000000..9d461fdf5
--- /dev/null
+++ b/inc/Form/TextareaElement.php
@@ -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 = $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>';
+ }
+
+}
diff --git a/inc/Form/ValueElement.php b/inc/Form/ValueElement.php
new file mode 100644
index 000000000..9dc2fd0df
--- /dev/null
+++ b/inc/Form/ValueElement.php
@@ -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 array|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;
+ }
+
+}
diff --git a/inc/load.php b/inc/load.php
index b78a9ee38..42a6a6362 100644
--- a/inc/load.php
+++ b/inc/load.php
@@ -111,6 +111,12 @@ function load_autoload($name){
return;
}
+ // our own namespace
+ $name = str_replace('\\', '/', $name);
+ if(substr($name, 0, 9) == 'dokuwiki/') {
+ require_once(substr($name, 9) . '.php');
+ }
+
// Plugin loading
if(preg_match('/^(auth|helper|syntax|action|admin|renderer|remote)_plugin_('.DOKU_PLUGIN_NAME_REGEX.')(?:_([^_]+))?$/',
$name, $m)) {