From c9b6f125aa7b8bbe1ad3d566267bf2b5c46801cd Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Fri, 15 Aug 2008 18:57:44 -0400 Subject: [PATCH] Forms implementation for %HTML.Trusted. Some backend changes: * Added Charsets and Character attribute types * Fix a heavily recursive form of ContentSets, this allows a content-set to include another content-set which includes another content-set, and so forth. Signed-off-by: Edward Z. Yang --- NEWS | 1 + TODO | 4 - configdoc/usage.xml | 12 +- library/HTMLPurifier.includes.php | 3 + library/HTMLPurifier.safe-includes.php | 3 + library/HTMLPurifier/AttrTransform/Input.php | 39 +++++ .../HTMLPurifier/AttrTransform/Textarea.php | 16 ++ library/HTMLPurifier/AttrTypes.php | 3 + library/HTMLPurifier/ChildDef/Custom.php | 2 - library/HTMLPurifier/ContentSets.php | 54 ++++--- library/HTMLPurifier/HTMLModule/Forms.php | 117 +++++++++++++++ library/HTMLPurifier/HTMLModuleManager.php | 7 +- .../HTMLPurifier/AttrTransform/InputTest.php | 93 ++++++++++++ tests/HTMLPurifier/ChildDef/CustomTest.php | 15 ++ tests/HTMLPurifier/ChildDef/RequiredTest.php | 2 - tests/HTMLPurifier/ComplexHarness.php | 4 + tests/HTMLPurifier/HTMLModule/FormsTest.php | 137 ++++++++++++++++++ 17 files changed, 475 insertions(+), 37 deletions(-) create mode 100644 library/HTMLPurifier/AttrTransform/Input.php create mode 100644 library/HTMLPurifier/AttrTransform/Textarea.php create mode 100644 library/HTMLPurifier/HTMLModule/Forms.php create mode 100644 tests/HTMLPurifier/AttrTransform/InputTest.php create mode 100644 tests/HTMLPurifier/HTMLModule/FormsTest.php diff --git a/NEWS b/NEWS index d3f5c1fb..32b8be9a 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,7 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier Users who are not performing inbound filtering, this may seem a little useless, but as a bonus, the test suite and handling of edge cases is also improved. +! Experimental implementation of forms for %HTML.Trusted - Fix two bugs in %URI.MakeAbsolute; one involving empty paths in base URLs, the other involving an undefined $is_folder error. - Throw error when %Core.Encoding is set to a spurious value. Previously, diff --git a/TODO b/TODO index 69222190..6886708f 100644 --- a/TODO +++ b/TODO @@ -22,7 +22,6 @@ FUTURE VERSIONS 3.2 release [It's All About Trust] (floating) # Implement untrusted, dangerous elements/attributes - - Forms are especially wanted # Implement IDREF support (harder than it seems, since you cannot have IDREFs to non-existent IDs) # Frameset XHTML 1.0 and HTML 4.01 doctypes @@ -44,7 +43,6 @@ FUTURE VERSIONS contents should be dropped or not (currently, there's code that could do something like this if it didn't drop the inner text too.) - Remove tags that don't do anything (no attributes) - - Remove empty inline tags - Append something to duplicate IDs so they're still usable (impl. note: the dupe detector would also need to detect the suffix as well) - Externalize inline CSS to promote clean HTML, proposed by Sander Tekelenburg @@ -60,8 +58,6 @@ FUTURE VERSIONS Also, enable disabling of directionality 5.0 release [To XML and Beyond] - - AllowedAttributes and ForbiddenAttributes step on the toes of XML by - using periods; this needs to be changed. - Extended HTML capabilities based on namespacing and tag transforms (COMPLEX) - Hooks for adding custom processors to custom namespaced tags and attributes, offer default implementation diff --git a/configdoc/usage.xml b/configdoc/usage.xml index 84d6b751..d3011b22 100644 --- a/configdoc/usage.xml +++ b/configdoc/usage.xml @@ -151,7 +151,7 @@ - 199 + 202 233 @@ -168,27 +168,27 @@ - 206 + 209 - 207 + 210 - 221 + 224 - 226 + 229 - 229 + 232 diff --git a/library/HTMLPurifier.includes.php b/library/HTMLPurifier.includes.php index fbeebf93..4d787e8b 100644 --- a/library/HTMLPurifier.includes.php +++ b/library/HTMLPurifier.includes.php @@ -115,6 +115,7 @@ require 'HTMLPurifier/AttrTransform/Border.php'; require 'HTMLPurifier/AttrTransform/EnumToCSS.php'; require 'HTMLPurifier/AttrTransform/ImgRequired.php'; require 'HTMLPurifier/AttrTransform/ImgSpace.php'; +require 'HTMLPurifier/AttrTransform/Input.php'; require 'HTMLPurifier/AttrTransform/Lang.php'; require 'HTMLPurifier/AttrTransform/Length.php'; require 'HTMLPurifier/AttrTransform/Name.php'; @@ -122,6 +123,7 @@ require 'HTMLPurifier/AttrTransform/SafeEmbed.php'; require 'HTMLPurifier/AttrTransform/SafeObject.php'; require 'HTMLPurifier/AttrTransform/SafeParam.php'; require 'HTMLPurifier/AttrTransform/ScriptRequired.php'; +require 'HTMLPurifier/AttrTransform/Textarea.php'; require 'HTMLPurifier/ChildDef/Chameleon.php'; require 'HTMLPurifier/ChildDef/Custom.php'; require 'HTMLPurifier/ChildDef/Empty.php'; @@ -137,6 +139,7 @@ require 'HTMLPurifier/DefinitionCache/Decorator/Memory.php'; require 'HTMLPurifier/HTMLModule/Bdo.php'; require 'HTMLPurifier/HTMLModule/CommonAttributes.php'; require 'HTMLPurifier/HTMLModule/Edit.php'; +require 'HTMLPurifier/HTMLModule/Forms.php'; require 'HTMLPurifier/HTMLModule/Hypertext.php'; require 'HTMLPurifier/HTMLModule/Image.php'; require 'HTMLPurifier/HTMLModule/Legacy.php'; diff --git a/library/HTMLPurifier.safe-includes.php b/library/HTMLPurifier.safe-includes.php index 0cf8d791..13d3e6d2 100644 --- a/library/HTMLPurifier.safe-includes.php +++ b/library/HTMLPurifier.safe-includes.php @@ -109,6 +109,7 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/Border.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/EnumToCSS.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/ImgRequired.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/ImgSpace.php'; +require_once $__dir . '/HTMLPurifier/AttrTransform/Input.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php'; @@ -116,6 +117,7 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php'; +require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Empty.php'; @@ -131,6 +133,7 @@ require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Memory.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Bdo.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/CommonAttributes.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php'; +require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Hypertext.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Image.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php'; diff --git a/library/HTMLPurifier/AttrTransform/Input.php b/library/HTMLPurifier/AttrTransform/Input.php new file mode 100644 index 00000000..f082c4de --- /dev/null +++ b/library/HTMLPurifier/AttrTransform/Input.php @@ -0,0 +1,39 @@ +pixels = new HTMLPurifier_AttrDef_HTML_Pixels(); + } + + public function transform($attr, $config, $context) { + if (!isset($attr['type'])) $t = 'text'; + else $t = strtolower($attr['type']); + if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') { + unset($attr['checked']); + } + if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') { + unset($attr['maxlength']); + } + if (isset($attr['size']) && $t !== 'text' && $t !== 'password') { + $result = $this->pixels->validate($attr['size'], $config, $context); + if ($result === false) unset($attr['size']); + else $attr['size'] = $result; + } + if (isset($attr['src']) && $t !== 'image') { + unset($attr['src']); + } + if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) { + $attr['value'] = ''; + } + return $attr; + } + +} + diff --git a/library/HTMLPurifier/AttrTransform/Textarea.php b/library/HTMLPurifier/AttrTransform/Textarea.php new file mode 100644 index 00000000..c7cc9888 --- /dev/null +++ b/library/HTMLPurifier/AttrTransform/Textarea.php @@ -0,0 +1,16 @@ + + */ +class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform +{ + + public function transform($attr, $config, $context) { + // Calculated from Firefox + if (!isset($attr['cols'])) $attr['cols'] = '22'; + if (!isset($attr['rows'])) $attr['rows'] = '3'; + return $attr; + } + +} \ No newline at end of file diff --git a/library/HTMLPurifier/AttrTypes.php b/library/HTMLPurifier/AttrTypes.php index 9262a098..c3116a16 100644 --- a/library/HTMLPurifier/AttrTypes.php +++ b/library/HTMLPurifier/AttrTypes.php @@ -32,6 +32,9 @@ class HTMLPurifier_AttrTypes // unimplemented aliases $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Character'] = new HTMLPurifier_AttrDef_Text(); // number is really a positive integer (one or more digits) // FIXME: ^^ not always, see start and value of list items diff --git a/library/HTMLPurifier/ChildDef/Custom.php b/library/HTMLPurifier/ChildDef/Custom.php index 4ba07888..f79bfcae 100644 --- a/library/HTMLPurifier/ChildDef/Custom.php +++ b/library/HTMLPurifier/ChildDef/Custom.php @@ -5,8 +5,6 @@ * * @warning Currently this class is an all or nothing proposition, that is, * it will only give a bool return value. - * @note This class is currently not used by any code, although it is unit - * tested. */ class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef { diff --git a/library/HTMLPurifier/ContentSets.php b/library/HTMLPurifier/ContentSets.php index 16438fda..15c8d97f 100644 --- a/library/HTMLPurifier/ContentSets.php +++ b/library/HTMLPurifier/ContentSets.php @@ -37,33 +37,35 @@ class HTMLPurifier_ContentSets // sorry, no way of overloading foreach ($modules as $module_i => $module) { foreach ($module->content_sets as $key => $value) { - if (isset($this->info[$key])) { + $temp = $this->convertToLookup($value); + if (isset($this->lookup[$key])) { // add it into the existing content set - $this->info[$key] = $this->info[$key] . ' | ' . $value; + $this->lookup[$key] = array_merge($this->lookup[$key], $temp); } else { - $this->info[$key] = $value; + $this->lookup[$key] = $temp; } } } - // perform content_set expansions - $this->keys = array_keys($this->info); - foreach ($this->info as $i => $set) { - // only performed once, so infinite recursion is not - // a problem - $this->info[$i] = - str_replace( - $this->keys, - // must be recalculated each time due to - // changing substitutions - array_values($this->info), - $set); + $old_lookup = false; + while ($old_lookup !== $this->lookup) { + $old_lookup = $this->lookup; + foreach ($this->lookup as $i => $set) { + $add = array(); + foreach ($set as $element => $x) { + if (isset($this->lookup[$element])) { + $add += $this->lookup[$element]; + unset($this->lookup[$i][$element]); + } + } + $this->lookup[$i] += $add; + } } - $this->values = array_values($this->info); - // generate lookup tables - foreach ($this->info as $name => $set) { - $this->lookup[$name] = $this->convertToLookup($set); + foreach ($this->lookup as $key => $lookup) { + $this->info[$key] = implode(' | ', array_keys($lookup)); } + $this->keys = array_keys($this->info); + $this->values = array_values($this->info); } /** @@ -75,12 +77,22 @@ class HTMLPurifier_ContentSets if (!empty($def->child)) return; // already done! $content_model = $def->content_model; if (is_string($content_model)) { - $def->content_model = str_replace( - $this->keys, $this->values, $content_model); + // Assume that $this->keys is alphanumeric + $def->content_model = preg_replace_callback( + '/\b(' . implode('|', $this->keys) . ')\b/', + array($this, 'generateChildDefCallback'), + $content_model + ); + //$def->content_model = str_replace( + // $this->keys, $this->values, $content_model); } $def->child = $this->getChildDef($def, $module); } + public function generateChildDefCallback($matches) { + return $this->info[$matches[0]]; + } + /** * Instantiates a ChildDef based on content_model and content_model_type * member variables in HTMLPurifier_ElementDef diff --git a/library/HTMLPurifier/HTMLModule/Forms.php b/library/HTMLPurifier/HTMLModule/Forms.php new file mode 100644 index 00000000..6a2dc69a --- /dev/null +++ b/library/HTMLPurifier/HTMLModule/Forms.php @@ -0,0 +1,117 @@ + 'Form', + 'Inline' => 'Formctrl', + ); + + public function setup($config) { + $form = $this->addElement('form', 'Form', + 'Required: Heading | List | Block | fieldset', 'Common', array( + 'accept' => 'ContentTypes', + 'accept-charset' => 'Charsets', + 'action*' => 'URI', + 'method' => 'Enum#get,post', + // really ContentType, but these two are the only ones used today + 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data', + )); + $form->excludes = array('form' => true); + + $input = $this->addElement('input', 'Formctrl', 'Empty', 'Common', array( + 'accept' => 'ContentTypes', + 'accesskey' => 'Character', + 'alt' => 'Text', + 'checked' => 'Bool#checked', + 'disabled' => 'Bool#disabled', + 'maxlength' => 'Number', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'size' => 'Number', + 'src' => 'URI#embeds', + 'tabindex' => 'Number', + 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image', + 'value' => 'CDATA', + )); + $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input(); + + $this->addElement('select', 'Formctrl', 'Required: optgroup | option', 'Common', array( + 'disabled' => 'Bool#disabled', + 'multiple' => 'Bool#multiple', + 'name' => 'CDATA', + 'size' => 'Number', + 'tabindex' => 'Number', + )); + + $this->addElement('option', false, 'Optional: #PCDATA', 'Common', array( + 'disabled' => 'Bool#disabled', + 'label' => 'Text', + 'selected' => 'Bool#selected', + 'value' => 'CDATA', + )); + // It's illegal for there to be more than one selected, but not + // be multiple. Also, no selected means undefined behavior. This might + // be difficult to implement; perhaps an injector, or a context variable. + + $textarea = $this->addElement('textarea', 'Formctrl', 'Optional: #PCDATA', 'Common', array( + 'accesskey' => 'Character', + 'cols*' => 'Number', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'rows*' => 'Number', + 'tabindex' => 'Number', + )); + $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea(); + + $button = $this->addElement('button', 'Formctrl', 'Optional: #PCDATA | Heading | List | Block | Inline', 'Common', array( + 'accesskey' => 'Character', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'tabindex' => 'Number', + 'type' => 'Enum#button,submit,reset', + 'value' => 'CDATA', + )); + + // For exclusions, ideally we'd specify content sets, not literal elements + $button->excludes = $this->makeLookup( + 'form', 'fieldset', // Form + 'input', 'select', 'textarea', 'label', 'button', // Formctrl + 'a' // as per HTML 4.01 spec, this is omitted by modularization + ); + + // Extra exclusion: img usemap="" is not permitted within this element. + // We'll omit this for now, since we don't have any good way of + // indicating it yet. + + // This is HIGHLY user-unfriendly; we need a custom child-def for this + $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common'); + + $label = $this->addElement('label', 'Formctrl', 'Optional: #PCDATA | Inline', 'Common', array( + 'accesskey' => 'Character', + // 'for' => 'IDREF', // IDREF not implemented, cannot allow + )); + $label->excludes = array('label' => true); + + $this->addElement('legend', false, 'Optional: #PCDATA | Inline', 'Common', array( + 'accesskey' => 'Character', + )); + + $this->addElement('optgroup', false, 'Required: option', 'Common', array( + 'disabled' => 'Bool#disabled', + 'label*' => 'Text', + )); + + // Don't forget an injector for . This one's a little complex + // because it maps to multiple elements. + + } +} + diff --git a/library/HTMLPurifier/HTMLModuleManager.php b/library/HTMLPurifier/HTMLModuleManager.php index cf6d38c7..5ac942eb 100644 --- a/library/HTMLPurifier/HTMLModuleManager.php +++ b/library/HTMLPurifier/HTMLModuleManager.php @@ -63,8 +63,11 @@ class HTMLPurifier_HTMLModuleManager $common = array( 'CommonAttributes', 'Text', 'Hypertext', 'List', 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image', - 'StyleAttribute', 'Scripting', 'Object', - 'Name' // technically legacy, but present in all the specs + 'StyleAttribute', + // Unsafe: + 'Scripting', 'Object', 'Forms', + // Sorta legacy, but present in strict: + 'Name', ); $transitional = array('Legacy', 'Target'); $xml = array('XMLCommonAttributes'); diff --git a/tests/HTMLPurifier/AttrTransform/InputTest.php b/tests/HTMLPurifier/AttrTransform/InputTest.php new file mode 100644 index 00000000..6b597e1d --- /dev/null +++ b/tests/HTMLPurifier/AttrTransform/InputTest.php @@ -0,0 +1,93 @@ +obj = new HTMLPurifier_AttrTransform_Input(); + } + + function testEmptyInput() { + $this->assertResult(array()); + } + + function testInvalidCheckedWithEmpty() { + $this->assertResult(array('checked' => 'checked'), array()); + } + + function testInvalidCheckedWithPassword() { + $this->assertResult(array( + 'checked' => 'checked', + 'type' => 'password' + ), array( + 'type' => 'password' + )); + } + + function testValidCheckedWithUcCheckbox() { + $this->assertResult(array( + 'checked' => 'checked', + 'type' => 'CHECKBOX', + 'value' => 'bar', + )); + } + + function testInvalidMaxlength() { + $this->assertResult(array( + 'maxlength' => '10', + 'type' => 'checkbox', + 'value' => 'foo', + ), array( + 'type' => 'checkbox', + 'value' => 'foo', + )); + } + + function testValidMaxLength() { + $this->assertResult(array( + 'maxlength' => '10', + )); + } + + // these two are really bad test-cases + + function testSizeWithCheckbox() { + $this->assertResult(array( + 'type' => 'checkbox', + 'value' => 'foo', + 'size' => '100px', + ), array( + 'type' => 'checkbox', + 'value' => 'foo', + 'size' => '100', + )); + } + + function testSizeWithText() { + $this->assertResult(array( + 'type' => 'password', + 'size' => '100px', // spurious value, to indicate no validation takes place + ), array( + 'type' => 'password', + 'size' => '100px', + )); + } + + function testInvalidSrc() { + $this->assertResult(array( + 'src' => 'img.png', + ), array()); + } + + function testMissingValue() { + $this->assertResult(array( + 'type' => 'checkbox', + ), array( + 'type' => 'checkbox', + 'value' => '', + )); + } + +} + diff --git a/tests/HTMLPurifier/ChildDef/CustomTest.php b/tests/HTMLPurifier/ChildDef/CustomTest.php index 4cb088de..76fb9fb1 100644 --- a/tests/HTMLPurifier/ChildDef/CustomTest.php +++ b/tests/HTMLPurifier/ChildDef/CustomTest.php @@ -69,5 +69,20 @@ class HTMLPurifier_ChildDef_CustomTest extends HTMLPurifier_ChildDefHarness } + function testPcdata() { + $this->obj = new HTMLPurifier_ChildDef_Custom('#PCDATA,a'); + $this->assertEqual($this->obj->elements, array('#PCDATA' => true, 'a' => true)); + $this->assertResult('foo'); + $this->assertResult('', false); + } + + function testWhitespace() { + $this->obj = new HTMLPurifier_ChildDef_Custom('a'); + $this->assertEqual($this->obj->elements, array('a' => true)); + $this->assertResult('foo', false); + $this->assertResult(''); + $this->assertResult(' '); + } + } diff --git a/tests/HTMLPurifier/ChildDef/RequiredTest.php b/tests/HTMLPurifier/ChildDef/RequiredTest.php index 4215cb02..44047c54 100644 --- a/tests/HTMLPurifier/ChildDef/RequiredTest.php +++ b/tests/HTMLPurifier/ChildDef/RequiredTest.php @@ -67,8 +67,6 @@ class HTMLPurifier_ChildDef_RequiredTest extends HTMLPurifier_ChildDefHarness 'Out Bold text', 'Out Bold text<img />' ); - } - } diff --git a/tests/HTMLPurifier/ComplexHarness.php b/tests/HTMLPurifier/ComplexHarness.php index 1047501f..cdb0a5a7 100644 --- a/tests/HTMLPurifier/ComplexHarness.php +++ b/tests/HTMLPurifier/ComplexHarness.php @@ -80,6 +80,10 @@ class HTMLPurifier_ComplexHarness extends HTMLPurifier_Harness } $this->assertIdentical($expect, $result); + if ($expect !== $result) { + echo '
' . htmlspecialchars($result) . '
'; + } + } /** diff --git a/tests/HTMLPurifier/HTMLModule/FormsTest.php b/tests/HTMLPurifier/HTMLModule/FormsTest.php new file mode 100644 index 00000000..ee990cdb --- /dev/null +++ b/tests/HTMLPurifier/HTMLModule/FormsTest.php @@ -0,0 +1,137 @@ +config->set('HTML', 'Trusted', true); + $this->config->set('Attr', 'EnableID', true); + $this->config->set('Cache', 'DefinitionImpl', null); + } + + function testBasicUse() { + $this->assertResult( // need support for label for later + ' +
+

+ +
+ +
+ +
+ Male
+ Female
+ +

+
' + ); + } + + function testSelectOption() { + $this->assertResult(' +
+

+ + +

+
+ '); + } + + function testSelectOptgroup() { + $this->assertResult(' +
+

+ +

+
+ '); + } + + function testTextarea() { + $this->assertResult(' +
+

+ + +

+
+ '); + } + + // label tests omitted + + function testFieldset() { + $this->assertResult(' +
+
+ Personal Information + Last Name: + First Name: + Address: + ...more personal information... +
+
+ Medical History + Smallpox + Mumps + Dizziness + Sneezing + ...more medical history... +
+
+ Current Medication + Are you currently taking any medication? + Yes + No + + If you are currently taking medication, please indicate + it in the space below: + +
+
+ '); + } + + function testInputTransform() { + $this->assertResult('', ''); + } + + function testTextareaTransform() { + $this->assertResult('', ''); + } + + function testTextInFieldset() { + $this->assertResult('
foo
'); + } + +} +