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
',
]
];
}