diff --git a/examples/Resources/Private/Singles/Variables.html b/examples/Resources/Private/Singles/Variables.html
index 3c443c6f5..8e849ea88 100644
--- a/examples/Resources/Private/Singles/Variables.html
+++ b/examples/Resources/Private/Singles/Variables.html
@@ -38,7 +38,8 @@
xyz: '{
foobar: \'Escaped sub-string\'
}'
- }
+ },
+ unsafeHTML: unsafeHTML
}"/>
@@ -51,4 +52,7 @@
Received $array.baz with value {array.baz}
Received $array.xyz.foobar with value {array.xyz.foobar}
Received $myVariable with value {myVariable}
+Received $unsafeHTML with unescaped value {unsafeHTML}
+Received $unsafeHTML with format.raw {unsafeHTML -> f:format.raw()}
+Received $unsafeHTML with format.htmlspecialchars {unsafeHTML -> f:format.htmlspecialchars()}
diff --git a/examples/example_variables.php b/examples/example_variables.php
index 21df591b4..e1cefa5ec 100644
--- a/examples/example_variables.php
+++ b/examples/example_variables.php
@@ -13,6 +13,7 @@
* how dynamic variable access works.
*/
+use TYPO3Fluid\Fluid\Core\Parser\UnsafeHTMLString;
use TYPO3Fluid\FluidExamples\Helper\ExampleHelper;
require_once __DIR__ . '/../vendor/autoload.php';
@@ -56,6 +57,8 @@
'123numericprefix' => 'Numeric prefixed variable',
// A variable whose value refers to another variable name
'dynamicVariableName' => 'foobar',
+ // An UnsafeHTML variable that will not be escaped when rendered
+ 'unsafeHTML' => new UnsafeHTMLString('Safe HTML String'),
]);
// Assigning the template path and filename to be rendered. Doing this overrides
diff --git a/src/Core/Parser/SyntaxTree/BooleanNode.php b/src/Core/Parser/SyntaxTree/BooleanNode.php
index ef7630ee0..f46e993f2 100644
--- a/src/Core/Parser/SyntaxTree/BooleanNode.php
+++ b/src/Core/Parser/SyntaxTree/BooleanNode.php
@@ -11,6 +11,7 @@
use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
use TYPO3Fluid\Fluid\Core\Parser\BooleanParser;
+use TYPO3Fluid\Fluid\Core\Parser\UnsafeHTML;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
/**
@@ -128,6 +129,10 @@ public static function convertToBoolean(mixed $value, RenderingContextInterface
if (is_numeric($value)) {
return (bool)((float)$value);
}
+ if ($value instanceof UnsafeHTML) {
+ // unpack UnsafeHTML to string, as it may be empty
+ $value = (string)$value;
+ }
if (is_string($value)) {
if (strlen($value) === 0) {
return false;
diff --git a/src/Core/Parser/SyntaxTree/EscapingNode.php b/src/Core/Parser/SyntaxTree/EscapingNode.php
index 2fb00ab7b..0f75ebb4a 100644
--- a/src/Core/Parser/SyntaxTree/EscapingNode.php
+++ b/src/Core/Parser/SyntaxTree/EscapingNode.php
@@ -10,6 +10,7 @@
namespace TYPO3Fluid\Fluid\Core\Parser\SyntaxTree;
use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
+use TYPO3Fluid\Fluid\Core\Parser\UnsafeHTML;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
/**
@@ -39,6 +40,9 @@ public function __construct(NodeInterface $node)
public function evaluate(RenderingContextInterface $renderingContext): mixed
{
$evaluated = $this->node->evaluate($renderingContext);
+ if ($evaluated instanceof UnsafeHTML) {
+ return (string)$evaluated;
+ }
if (is_string($evaluated) || (is_object($evaluated) && method_exists($evaluated, '__toString'))) {
return htmlspecialchars((string)$evaluated, ENT_QUOTES);
}
@@ -66,6 +70,7 @@ public function convert(TemplateCompiler $templateCompiler): array
if ($configuration['execution'] !== '\'\'') {
$configuration['execution'] = sprintf(
'call_user_func_array( function ($var) { '
+ . 'if ($var instanceof ' . UnsafeHTML::class . ') { return (string)$var; }'
. 'return (is_string($var) || (is_object($var) && method_exists($var, \'__toString\')) '
. '? htmlspecialchars((string) $var, ENT_QUOTES) : $var); }, [%s])',
$configuration['execution'],
diff --git a/src/Core/Parser/UnsafeHTML.php b/src/Core/Parser/UnsafeHTML.php
new file mode 100644
index 000000000..40694b90f
--- /dev/null
+++ b/src/Core/Parser/UnsafeHTML.php
@@ -0,0 +1,23 @@
+html;
+ }
+}
diff --git a/tests/Functional/Core/ViewHelper/ConditionViewHelperTest.php b/tests/Functional/Core/ViewHelper/ConditionViewHelperTest.php
index f1f685d7f..38538ed16 100644
--- a/tests/Functional/Core/ViewHelper/ConditionViewHelperTest.php
+++ b/tests/Functional/Core/ViewHelper/ConditionViewHelperTest.php
@@ -11,6 +11,7 @@
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
+use TYPO3Fluid\Fluid\Core\Parser\UnsafeHTMLString;
use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase;
use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\UserWithToString;
use TYPO3Fluid\Fluid\View\TemplateView;
@@ -78,6 +79,7 @@ public static function variableConditionDataProvider(): array
'foo' => 'bar',
];
$emptyCountable = new \SplObjectStorage();
+ $htmlString = new UnsafeHTMLString('baz');
return [
// simple assignments
@@ -94,6 +96,12 @@ public static function variableConditionDataProvider(): array
['{test1} === {test2}', false, ['test1' => 1, 'test2' => true]],
['{test1} == {test2}', true, ['test1' => 1, 'test2' => true]],
+ // conditions with UnsafeHTMLString
+ ['{test}', true, ['test' => $htmlString]],
+ ['{test} == \'baz\'', true, ['test' => $htmlString]],
+ ['{test1} === {test2}', false, ['test1' => 'baz', 'test2' => $htmlString]],
+ ['{test1} == {test2}', true, ['test1' => 'baz', 'test2' => $htmlString]],
+
// conditions with objects
['{user1} == {user1}', true, ['user1' => $user1]],
['{user1} === {user1}', true, ['user1' => $user1]],
diff --git a/tests/Functional/ExamplesTest.php b/tests/Functional/ExamplesTest.php
index b472ef6d1..b39bd289c 100644
--- a/tests/Functional/ExamplesTest.php
+++ b/tests/Functional/ExamplesTest.php
@@ -167,6 +167,9 @@ public static function exampleScriptValuesDataProvider(): array
'Received $array.baz with value 42',
'Received $array.xyz.foobar with value Escaped sub-string',
'Received $myVariable with value Nice string',
+ 'Received $unsafeHTML with unescaped value Safe HTML String',
+ 'Received $unsafeHTML with format.raw Safe HTML String',
+ 'Received $unsafeHTML with format.htmlspecialchars <strong>Safe HTML String</strong>',
],
],
'example_variableprovider.php' => [
diff --git a/tests/Unit/Core/Parser/BooleanParserTest.php b/tests/Unit/Core/Parser/BooleanParserTest.php
index 9b497bfc3..d4c2fc15c 100644
--- a/tests/Unit/Core/Parser/BooleanParserTest.php
+++ b/tests/Unit/Core/Parser/BooleanParserTest.php
@@ -15,6 +15,7 @@
use TYPO3Fluid\Fluid\Core\Parser\BooleanParser;
use TYPO3Fluid\Fluid\Core\Parser\Exception;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\BooleanNode;
+use TYPO3Fluid\Fluid\Core\Parser\UnsafeHTMLString;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContext;
final class BooleanParserTest extends TestCase
@@ -105,6 +106,15 @@ public static function getSomeEvaluationTestValues(): array
['{foo} == FALSE', true, ['foo' => false]],
['!{foo}', true, ['foo' => false]],
+ ['{foo}', false, ['foo' => new UnsafeHTMLString('')]],
+ ["{foo} == ''", true, ['foo' => new UnsafeHTMLString('')]],
+ ['{foo}', true, ['foo' => new UnsafeHTMLString('test')]],
+ ['{foo} == FALSE', false, ['foo' => new UnsafeHTMLString('test')]],
+ ['{foo} == TRUE', true, ['foo' => new UnsafeHTMLString('test')]],
+ ["{foo} == 'test'", true, ['foo' => new UnsafeHTMLString('test')]],
+ ['{foo} === TRUE', false, ['foo' => new UnsafeHTMLString('0')]],
+ ['{foo} === \'0\'', false, ['foo' => new UnsafeHTMLString('0')]],
+
/*
* @todo This should work but doesn't at the moment. This is probably related to the boolean
* parser not converting variable nodes correctly. There is a related todo in the IfThenElseViewHelperTest.