diff --git a/Neos.Fusion/Classes/Service/HtmlAugmenter.php b/Neos.Fusion/Classes/Service/HtmlAugmenter.php index b933ef9b027..bb9c49ec56d 100644 --- a/Neos.Fusion/Classes/Service/HtmlAugmenter.php +++ b/Neos.Fusion/Classes/Service/HtmlAugmenter.php @@ -99,7 +99,7 @@ protected function mergeAttributes(\DOMNode $element, array &$newAttributes) /** @var $attribute \DOMAttr */ foreach ($element->attributes as $attribute) { $oldAttributeValue = $attribute->hasChildNodes() ? $attribute->value : true; - $hasNewAttributeValue = array_key_exists($attribute->name, $newAttributes); + $hasNewAttributeValue = isset($newAttributes[$attribute->name]); $newAttributeValue = $newAttributes[$attribute->name] ?? null; if ($hasNewAttributeValue === false) { diff --git a/Neos.Fusion/Classes/Service/RenderAttributesTrait.php b/Neos.Fusion/Classes/Service/RenderAttributesTrait.php index 7cd05fe5429..e779d4fcf24 100644 --- a/Neos.Fusion/Classes/Service/RenderAttributesTrait.php +++ b/Neos.Fusion/Classes/Service/RenderAttributesTrait.php @@ -19,32 +19,39 @@ trait RenderAttributesTrait { /** - * Render the tag attributes for the given key->values as atring, - * if an value is an iterable it will be concatenated with spaces as seperator + * Render the tag attributes for the given key->values as string, + * if a value is an iterable it will be concatenated with spaces as separator * - * @param iterable $attributes a + * @param iterable> $attributes * @param bool $allowEmpty */ - protected function renderAttributes(iterable $attributes, $allowEmpty = true): string + protected function renderAttributes(iterable $attributes, bool $allowEmpty = true): string { $renderedAttributes = ''; foreach ($attributes as $attributeName => $attributeValue) { if ($attributeValue === null || $attributeValue === false) { continue; } + if (is_array($attributeValue)) { + // [] => empty attribute ? questionable + // [true] => empty attribute + // [false] => empty attribute + // ["foo", null, false, "bar"] => "foo bar" + // [""] => empty attribute + $joinedAttributeValue = ''; + foreach ($attributeValue as $attributeValuePart) { + $joinedAttributeValue .= match (gettype($attributeValuePart)) { + 'boolean', 'NULL' => '', + 'string' => ' ' . trim($attributeValuePart), + default => throw new \InvalidArgumentException('$attributes may contain values of type array type: array<' . get_debug_type($attributeValuePart) . '> given') + }; + } + $attributeValue = trim($joinedAttributeValue); + } $encodedAttributeName = htmlspecialchars((string)$attributeName, ENT_COMPAT, 'UTF-8', false); if ($attributeValue === true || $attributeValue === '') { $renderedAttributes .= ' ' . $encodedAttributeName . ($allowEmpty ? '' : '=""'); } else { - if (is_array($attributeValue)) { - $joinedAttributeValue = ''; - foreach ($attributeValue as $attributeValuePart) { - if ((string)$attributeValuePart !== '') { - $joinedAttributeValue .= ' ' . trim($attributeValuePart); - } - } - $attributeValue = trim($joinedAttributeValue); - } $encodedAttributeValue = htmlspecialchars((string)$attributeValue, ENT_COMPAT, 'UTF-8', false); $renderedAttributes .= ' ' . $encodedAttributeName . '="' . $encodedAttributeValue . '"'; } diff --git a/Neos.Fusion/Tests/Unit/Service/HtmlAugmenterTest.php b/Neos.Fusion/Tests/Unit/Service/HtmlAugmenterTest.php index c94810401a3..1d7775de606 100644 --- a/Neos.Fusion/Tests/Unit/Service/HtmlAugmenterTest.php +++ b/Neos.Fusion/Tests/Unit/Service/HtmlAugmenterTest.php @@ -342,7 +342,54 @@ public function __toString() { 'fallbackTagName' => null, 'exclusiveAttributes' => null, 'allowEmpty' => true, - 'expectedResult' => '

Array attribute

', + 'expectedResult' => '

Array attribute

', + ], + + // https://github.com/neos/neos-development-collection/issues/3582 + 'empty string rendered as empty attribute' => [ + 'html' => '

text

', + 'attributes' => ['data-bla' => ''], + 'fallbackTagName' => null, + 'exclusiveAttributes' => null, + 'allowEmpty' => true, + 'expectedResult' => '

text

', + ], + + // https://github.com/neos/neos-development-collection/issues/4213 + 'null should not remove existing attribute' => [ + 'html' => '

text

', + 'attributes' => ['data-bla' => null], + 'fallbackTagName' => null, + 'exclusiveAttributes' => null, + 'allowEmpty' => true, + 'expectedResult' => '

text

', + ], + + 'apply empty string on existing empty attribute' => [ + 'html' => '

text

', + 'attributes' => ['data-bla' => ''], + 'fallbackTagName' => null, + 'exclusiveAttributes' => null, + 'allowEmpty' => true, + 'expectedResult' => '

text

', + ], + + 'apply string on existing empty attribute' => [ + 'html' => '

text

', + 'attributes' => ['data-bla' => 'foobar'], + 'fallbackTagName' => null, + 'exclusiveAttributes' => null, + 'allowEmpty' => true, + 'expectedResult' => '

text

', + ], + + 'false removes attribute' => [ + 'html' => '

text

', + 'attributes' => ['data-bla' => false], + 'fallbackTagName' => null, + 'exclusiveAttributes' => null, + 'allowEmpty' => true, + 'expectedResult' => '

text

', ] ]; }