From 12a4e4d1ed827c59290838d5a11d75ad32aa28f1 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 8 May 2015 16:15:16 +0200 Subject: start of rewriting the form handling This is jsut a small beginning. Not all elements are there, yet. It's also completely untested so far. --- inc/Form/CheckableElement.php | 62 ++++++++++++++++ inc/Form/Element.php | 142 +++++++++++++++++++++++++++++++++++ inc/Form/Form.php | 167 ++++++++++++++++++++++++++++++++++++++++++ inc/Form/InputElement.php | 145 ++++++++++++++++++++++++++++++++++++ inc/Form/Label.php | 46 ++++++++++++ inc/Form/TextareaElement.php | 50 +++++++++++++ 6 files changed, 612 insertions(+) create mode 100644 inc/Form/CheckableElement.php create mode 100644 inc/Form/Element.php create mode 100644 inc/Form/Form.php create mode 100644 inc/Form/InputElement.php create mode 100644 inc/Form/Label.php create mode 100644 inc/Form/TextareaElement.php 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 @@ +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..6f23e2631 --- /dev/null +++ b/inc/Form/Element.php @@ -0,0 +1,142 @@ +type = $type; + $this->attributes = $attributes; + } + + /** + * 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); + $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/Form.php b/inc/Form/Form.php new file mode 100644 index 000000000..3a8b590e7 --- /dev/null +++ b/inc/Form/Form.php @@ -0,0 +1,167 @@ +attr('action')) { + $get = $_GET; + if(isset($get['id'])) unset($get['id']); + $self = wl($ID, $get); + $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 form2 based form in HTML + $this->addClass('doku_form2'); + } + + /** + * Sets a hidden field + * + * @param $name + * @param $value + * @return $this + */ + public function setHiddenField($name, $value) { + $this->hidden[$name] = $value; + return $this; + } + + /** + * Adds an element to the end of 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, 'Doku_Form2')) 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; + } + + /** + * 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); + } + + /** + * The HTML representation of the whole form + * + * @return string + */ + public function toHTML() { + $html = '
attrs()) . '>' . DOKU_LF; + + foreach($this->hidden as $name => $value) { + $html .= '' . DOKU_LF; + } + + foreach($this->elements as $element) { + $html .= $element->toHTML() . DOKU_LF; + } + + $html .= '
' . DOKU_LF; + + return $html; + } +} diff --git a/inc/Form/InputElement.php b/inc/Form/InputElement.php new file mode 100644 index 000000000..59310174e --- /dev/null +++ b/inc/Form/InputElement.php @@ -0,0 +1,145 @@ + $name)); + $this->attr('name', $name); + } + + /** + * 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) { + $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) { + $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 'attrs()) . ' />'; + } + + /** + * The HTML representation of this element wrapped in a label + * + * @return string + */ + public function toHTML() { + return ''; + } +} diff --git a/inc/Form/Label.php b/inc/Form/Label.php new file mode 100644 index 000000000..c8a862613 --- /dev/null +++ b/inc/Form/Label.php @@ -0,0 +1,46 @@ +label = $label; + } + + /** + * Get or set the element's label text + * + * @param null|string $value + * @return string|$this + */ + public function val($value = null) { + if($value !== null) { + $this->label = $value; + return $this; + } + return $this->label; + } + + /** + * The HTML representation of this element + * + * @return string + */ + public function toHTML() { + return ''; + } +} diff --git a/inc/Form/TextareaElement.php b/inc/Form/TextareaElement.php new file mode 100644 index 000000000..a8486266f --- /dev/null +++ b/inc/Form/TextareaElement.php @@ -0,0 +1,50 @@ +text = $value; + return $this; + } + return $this->text; + } + + /** + * The HTML representation of this element + * + * @return string + */ + protected function mainElementHTML() { + if($this->useInput) $this->prefillInput(); + return ''; + } + +} -- cgit v1.2.3 From e7a32b176701c088bab045437819448bb9adad41 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 8 May 2015 16:27:17 +0200 Subject: added autoloading for namespaced classes --- _test/tests/inc/form/form.test.php | 0 inc/load.php | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 _test/tests/inc/form/form.test.php diff --git a/_test/tests/inc/form/form.test.php b/_test/tests/inc/form/form.test.php new file mode 100644 index 000000000..e69de29bb diff --git a/inc/load.php b/inc/load.php index 18786dc79..19a8caa85 100644 --- a/inc/load.php +++ b/inc/load.php @@ -113,6 +113,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)) { -- cgit v1.2.3 From 6d0ceaf93ca31dfb83fd4325ef2eecd9cef733c0 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 8 May 2015 17:26:11 +0200 Subject: added a first few tests. this is far from comprehensible, but should give an idea how the new library works and how to write tests --- _test/tests/inc/form/checkableelement.test.php | 49 ++++++++++++++++++++++++++ _test/tests/inc/form/form.test.php | 28 +++++++++++++++ _test/tests/inc/form/inputelement.test.php | 41 +++++++++++++++++++++ inc/Form/Element.php | 1 + inc/Form/Form.php | 6 ++-- inc/Form/InputElement.php | 1 + 6 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 _test/tests/inc/form/checkableelement.test.php create mode 100644 _test/tests/inc/form/inputelement.test.php 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 @@ +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 index e69de29bb..cdf3e5a3a 100644 --- a/_test/tests/inc/form/form.test.php +++ b/_test/tests/inc/form/form.test.php @@ -0,0 +1,28 @@ +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); + } + +} 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 @@ +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/Element.php b/inc/Form/Element.php index 6f23e2631..3fd170a80 100644 --- a/inc/Form/Element.php +++ b/inc/Form/Element.php @@ -101,6 +101,7 @@ abstract class Element { $classes = explode(' ', $this->attr('class')); $classes[] = $class; $classes = array_unique($classes); + $classes = array_filter($classes); $this->attr('class', join(' ', $classes)); return $this; } diff --git a/inc/Form/Form.php b/inc/Form/Form.php index 3a8b590e7..dc502e021 100644 --- a/inc/Form/Form.php +++ b/inc/Form/Form.php @@ -34,7 +34,7 @@ class Form extends Element { if(!$this->attr('action')) { $get = $_GET; if(isset($get['id'])) unset($get['id']); - $self = wl($ID, $get); + $self = wl($ID, $get, false, '&'); //attributes are escaped later $this->attr('action', $self); } @@ -51,8 +51,8 @@ class Form extends Element { // add the security token by default $this->setHiddenField('sectok', getSecurityToken()); - // identify this as a form2 based form in HTML - $this->addClass('doku_form2'); + // identify this as a new form based form in HTML + $this->addClass('doku_form'); } /** diff --git a/inc/Form/InputElement.php b/inc/Form/InputElement.php index 59310174e..4f644c0f1 100644 --- a/inc/Form/InputElement.php +++ b/inc/Form/InputElement.php @@ -28,6 +28,7 @@ class InputElement extends Element { public function __construct($type, $name, $label) { parent::__construct($type, array('name' => $name)); $this->attr('name', $name); + $this->label = new Label($label); } /** -- cgit v1.2.3 From de19515f04567db78bd41d5bff68a88bfb8c2a22 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 8 May 2015 19:12:21 +0200 Subject: started with the compatibility layer --- inc/Form/Element.php | 10 ++- inc/Form/Form.php | 27 ++++++-- inc/Form/HTMLElement.php | 48 +++++++++++++ inc/Form/InputElement.php | 34 +++++++--- inc/Form/LegacyForm.php | 156 +++++++++++++++++++++++++++++++++++++++++++ inc/Form/TextareaElement.php | 1 + 6 files changed, 260 insertions(+), 16 deletions(-) create mode 100644 inc/Form/HTMLElement.php create mode 100644 inc/Form/LegacyForm.php diff --git a/inc/Form/Element.php b/inc/Form/Element.php index 3fd170a80..7938ee009 100644 --- a/inc/Form/Element.php +++ b/inc/Form/Element.php @@ -1,7 +1,6 @@ attributes = $attributes; } + /** + * Type of this element + * + * @return string + */ + public function getType() { + return $this->type; + } + /** * Gets or sets an attribute * diff --git a/inc/Form/Form.php b/inc/Form/Form.php index dc502e021..19cc05065 100644 --- a/inc/Form/Form.php +++ b/inc/Form/Form.php @@ -92,7 +92,7 @@ class Form extends Element { * @param int $pos * @return InputElement */ - public function addTextInput($name, $label, $pos = -1) { + public function addTextInput($name, $label = '', $pos = -1) { return $this->addElement(new InputElement('text', $name, $label), $pos); } @@ -104,7 +104,7 @@ class Form extends Element { * @param int $pos * @return InputElement */ - public function addPasswordInput($name, $label, $pos = -1) { + public function addPasswordInput($name, $label = '', $pos = -1) { return $this->addElement(new InputElement('password', $name, $label), $pos); } @@ -116,7 +116,7 @@ class Form extends Element { * @param int $pos * @return CheckableElement */ - public function addRadioButton($name, $label, $pos = -1) { + public function addRadioButton($name, $label = '', $pos = -1) { return $this->addElement(new CheckableElement('radio', $name, $label), $pos); } @@ -128,7 +128,7 @@ class Form extends Element { * @param int $pos * @return CheckableElement */ - public function addCheckbox($name, $label, $pos = -1) { + public function addCheckbox($name, $label = '', $pos = -1) { return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos); } @@ -140,16 +140,33 @@ class Form extends Element { * @param int $pos * @return TextareaElement */ - public function addTextarea($name, $label, $pos = -1) { + 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 Element + */ + public function addHTML($html, $pos = -1) { + return $this->addElement(new HTMLElement($html), $pos); + } + + protected function balanceFieldsets() { + //todo implement! + } + /** * The HTML representation of the whole form * * @return string */ public function toHTML() { + $this->balanceFieldsets(); + $html = '
attrs()) . '>' . DOKU_LF; foreach($this->hidden as $name => $value) { diff --git a/inc/Form/HTMLElement.php b/inc/Form/HTMLElement.php new file mode 100644 index 000000000..06f27d736 --- /dev/null +++ b/inc/Form/HTMLElement.php @@ -0,0 +1,48 @@ +val($html); + } + + /** + * Get or set the element's content + * + * @param null|string $html + * @return string|$this + */ + public function val($html = null) { + if($html !== null) { + $this->html = $html; + return $this; + } + return $this->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 index 4f644c0f1..939d911c1 100644 --- a/inc/Form/InputElement.php +++ b/inc/Form/InputElement.php @@ -4,7 +4,8 @@ namespace dokuwiki\Form; /** * Class InputElement * - * Base class for all input elements. Uses a wrapping label. + * 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 @@ -13,7 +14,7 @@ class InputElement extends Element { /** * @var Label */ - protected $label; + protected $label = null; /** * @var bool if the element should reflect posted values @@ -25,10 +26,19 @@ class InputElement extends 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) { + public function __construct($type, $name, $label = '') { parent::__construct($type, array('name' => $name)); $this->attr('name', $name); - $this->label = new Label($label); + 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; } /** @@ -52,7 +62,7 @@ class InputElement extends Element { * @return string|$this */ public function id($id = null) { - $this->label->attr('for', $id); + if($this->label) $this->label->attr('for', $id); return parent::id($id); } @@ -65,7 +75,7 @@ class InputElement extends Element { * @return $this */ public function addClass($class) { - $this->label->addClass($class); + if($this->label) $this->label->addClass($class); return parent::addClass($class); } @@ -138,9 +148,13 @@ class InputElement extends Element { * @return string */ public function toHTML() { - return ''; + if($this->label) { + return ''; + } else { + return $this->mainElementHTML(); + } } } diff --git a/inc/Form/LegacyForm.php b/inc/Form/LegacyForm.php new file mode 100644 index 000000000..edd263ee7 --- /dev/null +++ b/inc/Form/LegacyForm.php @@ -0,0 +1,156 @@ +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': + case 'opentag': + case 'closetag': + case 'openfieldset': + case 'closefieldset': + 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', + ); + 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/TextareaElement.php b/inc/Form/TextareaElement.php index a8486266f..9d461fdf5 100644 --- a/inc/Form/TextareaElement.php +++ b/inc/Form/TextareaElement.php @@ -18,6 +18,7 @@ class TextareaElement extends InputElement { */ public function __construct($name, $label) { parent::__construct('textarea', $name, $label); + $this->attr('dir', 'auto'); } /** -- cgit v1.2.3 From 64744a10c5578602141ae2977274eec3fcff1f44 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 8 May 2015 20:37:06 +0200 Subject: more elements and work on the legacy support --- inc/Form/FieldsetCloseElement.php | 64 ++++++++++++++++++++++++++++++++++++++ inc/Form/FieldsetOpenElement.php | 36 ++++++++++++++++++++++ inc/Form/Form.php | 65 ++++++++++++++++++++++++++++++++++++++- inc/Form/HTMLElement.php | 23 ++------------ inc/Form/Label.php | 25 ++------------- inc/Form/LegacyForm.php | 27 +++++++++++++++- inc/Form/TagCloseElement.php | 30 ++++++++++++++++++ inc/Form/TagElement.php | 29 +++++++++++++++++ inc/Form/TagOpenElement.php | 30 ++++++++++++++++++ inc/Form/ValueElement.php | 45 +++++++++++++++++++++++++++ 10 files changed, 329 insertions(+), 45 deletions(-) create mode 100644 inc/Form/FieldsetCloseElement.php create mode 100644 inc/Form/FieldsetOpenElement.php create mode 100644 inc/Form/TagCloseElement.php create mode 100644 inc/Form/TagElement.php create mode 100644 inc/Form/TagOpenElement.php create mode 100644 inc/Form/ValueElement.php diff --git a/inc/Form/FieldsetCloseElement.php b/inc/Form/FieldsetCloseElement.php new file mode 100644 index 000000000..0de84e251 --- /dev/null +++ b/inc/Form/FieldsetCloseElement.php @@ -0,0 +1,64 @@ +type = 'fieldsetopen'; + } + + /** + * The HTML representation of this element + * + * @return string + */ + public function toHTML() { + $html = '
attrs()).'>'; + $legend = $this->val(); + if($legend) $html .= DOKU_LF.''.hsc($legend).''; + return $html; + } +} diff --git a/inc/Form/Form.php b/inc/Form/Form.php index 19cc05065..420399fb1 100644 --- a/inc/Form/Form.php +++ b/inc/Form/Form.php @@ -67,6 +67,8 @@ class Form extends Element { return $this; } + #region Element adding functions + /** * Adds an element to the end of the form * @@ -149,12 +151,73 @@ class Form extends Element { * * @param $html * @param int $pos - * @return Element + * @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 + protected function balanceFieldsets() { //todo implement! } diff --git a/inc/Form/HTMLElement.php b/inc/Form/HTMLElement.php index 06f27d736..591cf472f 100644 --- a/inc/Form/HTMLElement.php +++ b/inc/Form/HTMLElement.php @@ -8,33 +8,14 @@ namespace dokuwiki\Form; * * @package dokuwiki\Form */ -class HTMLElement extends Element { +class HTMLElement extends ValueElement { - /** - * @var string the raw HTML held by this element - */ - protected $html = ''; /** * @param string $html */ public function __construct($html) { - parent::__construct('html'); - $this->val($html); - } - - /** - * Get or set the element's content - * - * @param null|string $html - * @return string|$this - */ - public function val($html = null) { - if($html !== null) { - $this->html = $html; - return $this; - } - return $this->html; + parent::__construct('html', $html); } /** diff --git a/inc/Form/Label.php b/inc/Form/Label.php index c8a862613..8dcd7cd5f 100644 --- a/inc/Form/Label.php +++ b/inc/Form/Label.php @@ -5,11 +5,7 @@ namespace dokuwiki\Form; * Class Label * @package dokuwiki\Form */ -class Label extends Element { - /** - * @var string the actual label text - */ - public $label = ''; +class Label extends ValueElement { /** * Creates a new Label @@ -17,22 +13,7 @@ class Label extends Element { * @param string $label */ public function __construct($label) { - parent::__construct('label'); - $this->label = $label; - } - - /** - * Get or set the element's label text - * - * @param null|string $value - * @return string|$this - */ - public function val($value = null) { - if($value !== null) { - $this->label = $value; - return $this; - } - return $this->label; + parent::__construct('label', $label); } /** @@ -41,6 +22,6 @@ class Label extends Element { * @return string */ public function toHTML() { - return ''; + return ''; } } diff --git a/inc/Form/LegacyForm.php b/inc/Form/LegacyForm.php index edd263ee7..1b47ba204 100644 --- a/inc/Form/LegacyForm.php +++ b/inc/Form/LegacyForm.php @@ -59,12 +59,33 @@ class LegacyForm extends Form { ->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': @@ -121,6 +142,10 @@ class LegacyForm extends Form { 'password' => 'passwordfield', 'checkbox' => 'checkboxfield', 'radio' => 'radiofield', + 'tagopen' => 'opentag', + 'tagclose' => 'closetag', + 'fieldsetopen' => 'openfieldset', + 'fieldsetclose' => 'closefieldset', ); if(isset($types[$type])) return $types[$type]; return $type; diff --git a/inc/Form/TagCloseElement.php b/inc/Form/TagCloseElement.php new file mode 100644 index 000000000..896945b97 --- /dev/null +++ b/inc/Form/TagCloseElement.php @@ -0,0 +1,30 @@ +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 @@ +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 @@ +val().' '.buildAttributes($this->attrs()).'>'; + } +} diff --git a/inc/Form/ValueElement.php b/inc/Form/ValueElement.php new file mode 100644 index 000000000..753704c70 --- /dev/null +++ b/inc/Form/ValueElement.php @@ -0,0 +1,45 @@ +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; + } + +} -- cgit v1.2.3 From 370c645068595c6750cee2b38eb96aeaac3a9dbb Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 11 May 2015 19:52:12 +0200 Subject: fixed label output --- inc/Form/InputElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Form/InputElement.php b/inc/Form/InputElement.php index 939d911c1..5908f7d11 100644 --- a/inc/Form/InputElement.php +++ b/inc/Form/InputElement.php @@ -150,7 +150,7 @@ class InputElement extends Element { public function toHTML() { if($this->label) { return ''; } else { -- cgit v1.2.3 From 1f5d8b65a983fe0914971ee0bb4e5e58cbf8c8a7 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 11 May 2015 20:31:16 +0200 Subject: balance fieldsets --- _test/tests/inc/form/form.test.php | 87 ++++++++++++++++++++++++++++++++++++++ inc/Form/FieldsetCloseElement.php | 46 +++----------------- inc/Form/Form.php | 39 +++++++++++++++-- inc/Form/TagCloseElement.php | 46 ++++++++++++++++++++ 4 files changed, 175 insertions(+), 43 deletions(-) diff --git a/_test/tests/inc/form/form.test.php b/_test/tests/inc/form/form.test.php index cdf3e5a3a..3ae832b2c 100644 --- a/_test/tests/inc/form/form.test.php +++ b/_test/tests/inc/form/form.test.php @@ -2,6 +2,25 @@ 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 { /** @@ -25,4 +44,72 @@ class form_form_test extends DokuWikiTest { $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/inc/Form/FieldsetCloseElement.php b/inc/Form/FieldsetCloseElement.php index 0de84e251..8f26717aa 100644 --- a/inc/Form/FieldsetCloseElement.php +++ b/inc/Form/FieldsetCloseElement.php @@ -14,51 +14,17 @@ class FieldsetCloseElement extends TagCloseElement { * @param array $attributes */ public function __construct($attributes = array()) { - parent::__construct('tagclose', $attributes); + parent::__construct('', $attributes); + $this->type = 'fieldsetclose'; } - /** - * 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 + * The HTML representation of this element * - * @param $attributes - * @return void - * @throws \BadMethodCallException + * @return string */ - public function attrs($attributes = null) { - throw new \BadMethodCallException('You can\t add attributes to closing tag'); + public function toHTML() { + return '
'; } } diff --git a/inc/Form/Form.php b/inc/Form/Form.php index 420399fb1..30e16615c 100644 --- a/inc/Form/Form.php +++ b/inc/Form/Form.php @@ -194,7 +194,6 @@ class Form extends Element { return $this->addElement(new TagCloseElement($tag), $pos); } - /** * Open a Fieldset * @@ -202,7 +201,7 @@ class Form extends Element { * @param int $pos * @return FieldsetOpenElement */ - public function addFieldsetOpen($legend='', $pos = -1) { + public function addFieldsetOpen($legend = '', $pos = -1) { return $this->addElement(new FieldsetOpenElement($legend), $pos); } @@ -218,8 +217,42 @@ class Form extends Element { #endregion + /** + * Adjust the elements so that fieldset open and closes are matching + */ protected function balanceFieldsets() { - //todo implement! + $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 feldset + $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(); + } } /** diff --git a/inc/Form/TagCloseElement.php b/inc/Form/TagCloseElement.php index 896945b97..dc0264c21 100644 --- a/inc/Form/TagCloseElement.php +++ b/inc/Form/TagCloseElement.php @@ -19,6 +19,51 @@ class TagCloseElement extends ValueElement { 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 * @@ -27,4 +72,5 @@ class TagCloseElement extends ValueElement { public function toHTML() { return 'val().'>'; } + } -- cgit v1.2.3 From ef0c211b2ac87b8e3c6e85b600021389be6209bf Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Mon, 11 May 2015 20:51:58 +0200 Subject: added element query functions --- inc/Form/Form.php | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/inc/Form/Form.php b/inc/Form/Form.php index 30e16615c..2c893fbbc 100644 --- a/inc/Form/Form.php +++ b/inc/Form/Form.php @@ -67,17 +67,80 @@ class Form extends Element { return $this; } - #region Element adding functions + #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]; + } /** - * Adds an element to the end of the form + * 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, 'Doku_Form2')) throw new \InvalidArgumentException('You can\'t add a form to a form'); + 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 { @@ -86,6 +149,30 @@ class Form extends 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 * -- cgit v1.2.3 From 80b13baa48275cef1ca6c6d3c715d21afd1138e7 Mon Sep 17 00:00:00 2001 From: Gerrit Uitslag Date: Sun, 2 Aug 2015 00:16:05 +0200 Subject: typo --- inc/Form/Form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Form/Form.php b/inc/Form/Form.php index 2c893fbbc..625557fa1 100644 --- a/inc/Form/Form.php +++ b/inc/Form/Form.php @@ -316,7 +316,7 @@ class Form extends Element { $type = $this->elements[$pos]->getType(); if($type == 'fieldsetopen') { if($isopen) { - //close previous feldset + //close previous fieldset $this->addFieldsetClose($pos); $lastclose = $pos + 1; $pos++; -- cgit v1.2.3 From 3c8e094acdcf6d556f022bc224b81ef17e449281 Mon Sep 17 00:00:00 2001 From: Gerrit Uitslag Date: Sun, 2 Aug 2015 00:46:52 +0200 Subject: improve PHPDocs --- inc/Form/ValueElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Form/ValueElement.php b/inc/Form/ValueElement.php index 753704c70..9dc2fd0df 100644 --- a/inc/Form/ValueElement.php +++ b/inc/Form/ValueElement.php @@ -20,7 +20,7 @@ abstract class ValueElement extends Element { /** * @param string $type - * @param array $value + * @param array|string $value * @param array $attributes */ public function __construct($type, $value, $attributes = array()) { -- cgit v1.2.3