Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 10 additions & 24 deletions src/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,37 +67,23 @@ public static function isEmptyValue($value): bool
}

/**
* Escape reserved chars in the given string
* Return the given string unchanged.
*
* The characters '.', ' ' and optionally brackets are converted to unused control characters
* File Separator: ␜, Group Separator: ␝, Record Separator: ␝, and Unit Separator: ␟ respectively.
*
* This is done because:
* PHP converts dots and spaces in form element names to underscores by default in the request data.
* For example, <input name="a.b" /> becomes $_REQUEST["a_b"].
*
* And if an external variable name begins with a valid array syntax, trailing characters are silently ignored.
* For example, <input name="foo[bar]baz"> becomes $_REQUEST['foo']['bar'].
* See https://www.php.net/manual/en/language.variables.external.php
*
* @param string $string The string to escape
* @param bool $escapeBrackets Whether to escape brackets
* @param string $string
* @param bool $escapeBrackets
*
* @return string
* @deprecated Escaping element names must be explicitly done. This method does not escape
* anymore and will be removed in a future version.
*/
public static function escapeReservedChars(string $string, bool $escapeBrackets = true): string
{
$escapeMap = [
'.' => chr(28), // File Separator
' ' => chr(29) // Group Separator
];

if ($escapeBrackets) {
$escapeMap['['] = chr(30); // Record Separator
$escapeMap[']'] = chr(31); // Unit Separator
}
trigger_error(sprintf(
'%s is deprecated. Escaping element names must be explicitly done.',
__METHOD__
), E_USER_DEPRECATED);

return strtr($string, $escapeMap);
return $string;
}

public function getAction()
Expand Down
27 changes: 2 additions & 25 deletions src/FormElement/BaseFormElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ abstract class BaseFormElement extends BaseHtmlElement implements FormElement, V
/** @var string Name of the element */
protected $name;

/** @var string Escaped name of the element */
protected string $escapedName;

/** @var bool Whether the element is ignored */
protected $ignored = false;

Expand Down Expand Up @@ -130,16 +127,6 @@ public function setName($name)
{
$this->name = $name;

// TODO: phpdoc always expressed that only strings are accepted, but some usages passed null despite that.
// The isset check is only a compatibility measure to not break BC. Remove once the interface actually
// requires a string. And don't ever dare to allow null (`?string`) due to this check!!!1
if (isset($this->name)) {
// Name is always escaped
$this->escapedName = Form::escapeReservedChars($name);
} else {
$this->escapedName = '';
}

return $this;
}

Expand All @@ -162,16 +149,6 @@ public function setIgnored($ignored = true)
return $this;
}

/**
* Get the escaped name of the element
*
* @return string
*/
public function getEscapedName(): string
{
return $this->escapedName;
}

public function isRequired()
{
return $this->required;
Expand Down Expand Up @@ -324,7 +301,7 @@ public function hasBeenValidated()
*/
public function getNameAttribute()
{
return $this->getEscapedName();
return $this->getName();
}

/**
Expand Down Expand Up @@ -420,7 +397,7 @@ protected function getValueOfNameAttribute()
return $name;
}

return $this->getEscapedName();
return $this->getName();
}

public function getDecorators(): DecoratorChain
Expand Down
10 changes: 1 addition & 9 deletions src/FormElement/FieldsetElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,6 @@ protected function onElementRegistered(FormElement $element)
$multiple = $element->isMultiple();
}

// This check is required as the getEscapedName method is not implemented in
// the FormElement interface
if ($element instanceof BaseFormElement) {
$name = $element->getEscapedName();
} else {
$name = $element->getName();
}

/**
* We don't change the {@see BaseFormElement::$name} property of the element,
* otherwise methods like {@see FormElements::populate() and {@see FormElements::getElement()} would fail,
Expand All @@ -132,7 +124,7 @@ protected function onElementRegistered(FormElement $element)
return sprintf(
'%s[%s]%s',
$this->getValueOfNameAttribute(),
$name,
$element->getName(),
$multiple ? '[]' : ''
);
});
Expand Down
6 changes: 3 additions & 3 deletions src/FormElement/FileElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function getValueAttribute()

public function getNameAttribute()
{
$name = $this->getEscapedName();
$name = $this->getName();

return $this->isMultiple() ? ($name . '[]') : $name;
}
Expand Down Expand Up @@ -260,12 +260,12 @@ public function onRegistered(Form $form)
$form->setAttribute('enctype', 'multipart/form-data');
}

$chosenFiles = (array) $form->getPopulatedValue('chosen_file_' . $this->getEscapedName(), []);
$chosenFiles = (array) $form->getPopulatedValue('chosen_file_' . $this->getName(), []);
foreach ($chosenFiles as $chosenFile) {
$this->files[$chosenFile] = null;
}

$this->filesToRemove = (array) $form->getPopulatedValue('remove_file_' . $this->getEscapedName(), []);
$this->filesToRemove = (array) $form->getPopulatedValue('remove_file_' . $this->getName(), []);
}

protected function addDefaultValidators(ValidatorChain $chain): void
Expand Down
20 changes: 4 additions & 16 deletions src/FormElement/FormElements.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,6 @@ public function registerElement(FormElement $element)
{
$name = $element->getName();

// This check is required as the getEscapedName method is not implemented in
// the FormElement interface
if ($element instanceof BaseFormElement) {
$escapedName = $element->getEscapedName();
} else {
$escapedName = $name;
}

if ($name === null) {
throw new InvalidArgumentException(sprintf(
'%s expects the element to provide a name',
Expand All @@ -247,13 +239,11 @@ public function registerElement(FormElement $element)

$this->elements[$name] = $element;

if (array_key_exists($escapedName, $this->populatedValues)) {
$element->setValue(
$this->populatedValues[$escapedName][count($this->populatedValues[$escapedName]) - 1]
);
if (array_key_exists($name, $this->populatedValues)) {
$element->setValue($this->populatedValues[$name][count($this->populatedValues[$name]) - 1]);

if ($element instanceof ValueCandidates) {
$element->setValueCandidates($this->populatedValues[$escapedName]);
$element->setValueCandidates($this->populatedValues[$name]);
}
}

Expand Down Expand Up @@ -369,7 +359,7 @@ public function getValues()
public function populate($values)
{
foreach ($values as $name => $value) {
$this->populatedValues[Form::escapeReservedChars($name)][] = $value;
$this->populatedValues[$name][] = $value;
if ($this->hasElement($name)) {
$this->getElement($name)->setValue($value);
}
Expand All @@ -390,7 +380,6 @@ public function populate($values)
*/
public function getPopulatedValue($name, $default = null)
{
$name = Form::escapeReservedChars($name);
return isset($this->populatedValues[$name])
? $this->populatedValues[$name][count($this->populatedValues[$name]) - 1]
: $default;
Expand All @@ -405,7 +394,6 @@ public function getPopulatedValue($name, $default = null)
*/
public function clearPopulatedValue($name)
{
$name = Form::escapeReservedChars($name);
if (isset($this->populatedValues[$name])) {
unset($this->populatedValues[$name]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/FormElement/SelectElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public function getValueAttribute()

public function getNameAttribute()
{
$name = $this->getEscapedName();
$name = $this->getName();

return $this->isMultiple() ? ($name . '[]') : $name;
}
Expand Down
47 changes: 0 additions & 47 deletions tests/FormTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace ipl\Tests\Html;

use ipl\Html\Form;
use ipl\Html\FormElement\BaseFormElement;
use ipl\Html\FormElement\FieldsetElement;
use ipl\Html\Test\TestCase;
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -52,18 +51,6 @@ protected function assemble()
$form->handleRequest($request2);
}

public function testEscapeReservedChars(): void
{
// Reserved chars '.', ' ', '[' and ']' are escaped
$this->assertSame(Form::escapeReservedChars('foo.bar'), 'foo' . chr(28) . 'bar');
$this->assertSame(Form::escapeReservedChars('foo bar'), 'foo' . chr(29) . 'bar');
$this->assertSame(Form::escapeReservedChars('foo[bar]'), 'foo' . chr(30) . 'bar' . chr(31));

// The string remains the same
$this->assertSame(Form::escapeReservedChars('foo[bar]', false), 'foo[bar]');
$this->assertSame(Form::escapeReservedChars('foo-bar123'), 'foo-bar123');
}

public function testValidatePartialOnlyValidatesFieldsetChildrenWithAValue(): void
{
$fieldset = (new FieldsetElement('set'))
Expand Down Expand Up @@ -103,38 +90,4 @@ public function testValidatePartialRecursesIntoNestedFieldsets(): void
'Nested fieldset child with a value is not validated during partial validation'
);
}

public function testFormElementsWithReservedCharsInName(): void
{
$form = new Form();

// Test that the element name gets escaped as soon as the name is set
/** @var BaseFormElement $el1 */
$el1 = $form->createElement('text', 'foo.bar');
$this->assertSame('foo' . chr(28) . 'bar', $el1->getEscapedName());

/** @var BaseFormElement $el2 */
$el2 = $form->createElement('text', 'foo[bar]');
$this->assertSame('foo' . chr(30) . 'bar' . chr(31), $el2->getEscapedName());

$el2->setName('foo bar');
$this->assertSame('foo' . chr(29) . 'bar', $el2->getEscapedName());

$form->registerElement($el1);
$form->registerElement($el2);

// Test that the element name is escaped when rendered
$this->assertHtml('<input name="foo' . chr(28) . 'bar" type="text" />', $form->getElement('foo.bar'));
$this->assertHtml('<input name="foo' . chr(29) . 'bar" type="text" />', $form->getElement('foo bar'));

// Test for data population; Initial data is not escaped
$form->populate(['foo.bar' => 'foo', 'foo bar' => 'bar']);
$this->assertSame('foo', $form->getPopulatedValue('foo.bar'));
$this->assertSame('bar', $form->getPopulatedValue('foo bar'));

// Test for data population; The POST data contains the escaped chars
$form->populate(['foo' . chr(28) . 'bar' => 'foo1', 'foo' . chr(29) . 'bar' => 'bar1']);
$this->assertSame('foo1', $form->getPopulatedValue('foo.bar'));
$this->assertSame('bar1', $form->getPopulatedValue('foo bar'));
}
}
Loading