From 066093216cf6595a4efb6cbe074910664718e4af Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 13 Aug 2026 09:16:10 +0100 Subject: [PATCH] parse antlers enabled fields in blade templates Fields configured with `antlers: true` were only parsed by the Antlers runtime, so blade templates echoed the raw tags. A blade echo handler now runs `Value::antlersValue()` for those fields, using the cascade as its context, with the same user content sandboxing the antlers runtime applies. Co-Authored-By: Claude Opus 5 --- src/Providers/ViewServiceProvider.php | 4 ++ src/View/Blade/ValueEchoHandler.php | 31 ++++++++++++++ tests/View/Blade/AntlersValueTest.php | 60 +++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/View/Blade/ValueEchoHandler.php create mode 100644 tests/View/Blade/AntlersValueTest.php diff --git a/src/Providers/ViewServiceProvider.php b/src/Providers/ViewServiceProvider.php index a5661315580..249bd4ecf27 100644 --- a/src/Providers/ViewServiceProvider.php +++ b/src/Providers/ViewServiceProvider.php @@ -9,6 +9,7 @@ use Illuminate\View\View; use Statamic\Contracts\View\Antlers\Parser as ParserContract; use Statamic\Facades\Site; +use Statamic\Fields\Value; use Statamic\Statamic; use Statamic\View\Antlers\Engine; use Statamic\View\Antlers\Language\Analyzers\NodeTypeAnalyzer; @@ -22,6 +23,7 @@ use Statamic\View\Antlers\Language\Utilities\StringUtilities; use Statamic\View\Blade\AntlersBladePrecompiler; use Statamic\View\Blade\StatamicTagCompiler; +use Statamic\View\Blade\ValueEchoHandler; use Statamic\View\Cascade; use Statamic\View\Debugbar\AntlersProfiler\PerformanceCollector; use Statamic\View\Debugbar\AntlersProfiler\PerformanceTracer; @@ -435,6 +437,8 @@ public function boot() return AntlersBladePrecompiler::compile($content); }); + Blade::stringable(Value::class, [ValueEchoHandler::class, 'handle']); + View::macro('withoutExtractions', function () { if ($this->getEngine() instanceof Engine) { $this->getEngine()->withoutExtractions(); diff --git a/src/View/Blade/ValueEchoHandler.php b/src/View/Blade/ValueEchoHandler.php new file mode 100644 index 00000000000..f3a20fd922b --- /dev/null +++ b/src/View/Blade/ValueEchoHandler.php @@ -0,0 +1,31 @@ +shouldParseAntlers()) { + return $value; + } + + $data = Cascade::toArray() ?: Cascade::hydrate()->toArray(); + + $wasEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData; + GlobalRuntimeState::$isEvaluatingUserData = true; + GlobalRuntimeState::$userContentEvalState = [$value, null]; + + try { + return $value->antlersValue(Antlers::parser(), $data); + } finally { + GlobalRuntimeState::$userContentEvalState = null; + GlobalRuntimeState::$isEvaluatingUserData = $wasEvaluatingUserData; + } + } +} diff --git a/tests/View/Blade/AntlersValueTest.php b/tests/View/Blade/AntlersValueTest.php new file mode 100644 index 00000000000..e25ccefe7c0 --- /dev/null +++ b/tests/View/Blade/AntlersValueTest.php @@ -0,0 +1,60 @@ +value(new Textarea, 'Hello {{ name }}', ['antlers' => true]); + + $this->assertSame('Hello World', Blade::render('{!! $text !!}', ['text' => $value])); + } + + #[Test] + public function it_doesnt_parse_antlers_in_a_value_when_the_field_has_antlers_disabled() + { + Cascade::set('name', 'World'); + + $value = $this->value(new Textarea, 'Hello {{ name }}', ['antlers' => false]); + + $this->assertSame('Hello {{ name }}', Blade::render('{!! $text !!}', ['text' => $value])); + } + + #[Test] + public function it_parses_antlers_before_augmenting_a_markdown_value() + { + Cascade::set('name', 'World'); + + $value = $this->value(new Markdown, '# Hello {{ name }}', ['antlers' => true]); + + $this->assertSame("

Hello World

\n", Blade::render('{!! $text !!}', ['text' => $value])); + } + + #[Test] + public function it_escapes_parsed_antlers_in_an_escaped_echo() + { + Cascade::set('name', 'World'); + + $value = $this->value(new Textarea, 'Hello {{ name }}', ['antlers' => true]); + + $this->assertSame('Hello <b>World</b>', Blade::render('{{ $text }}', ['text' => $value])); + } + + private function value($fieldtype, $raw, array $config) + { + return new Value($raw, 'text', $fieldtype->setField(new Field('text', $config))); + } +}