diff --git a/src/View/Blade/Concerns/CompilesNocache.php b/src/View/Blade/Concerns/CompilesNocache.php index 50b07ab83d9..90f219d81ff 100644 --- a/src/View/Blade/Concerns/CompilesNocache.php +++ b/src/View/Blade/Concerns/CompilesNocache.php @@ -2,6 +2,8 @@ namespace Statamic\View\Blade\Concerns; +use Illuminate\Support\Facades\Blade; +use Illuminate\View\Compilers\BladeCompiler; use Statamic\View\Blade\StatamicTagCompiler; use Stillat\BladeParser\Nodes\Components\ComponentNode; @@ -10,10 +12,49 @@ trait CompilesNocache protected function compileNocache(ComponentNode $component): string { $compiled = (new StatamicTagCompiler())->compile($component->innerDocumentContent); + + // The nocache region is written to its own view file which Blade compiles + // independently. Any @php or @verbatim blocks have already been swapped + // for raw placeholders by the outer compiler, so we need to restore them + // here otherwise they'd be lost by the time that view is compiled. + $compiled = $this->restoreRawBlocks($compiled); + $viewName = '_nocache'.sha1($compiled); $path = storage_path('framework/views/'.$viewName.'.blade.php'); file_put_contents($path, $compiled); return '@nocache(\'compiled__views::'.$viewName.'\')'; } + + private function restoreRawBlocks(string $compiled): string + { + $compiler = Blade::getFacadeRoot(); + + if (! $compiler instanceof BladeCompiler) { + return $compiled; + } + + $rawBlocks = \Closure::bind(fn () => $this->rawBlocks, $compiler, BladeCompiler::class)(); + + if (empty($rawBlocks)) { + return $compiled; + } + + return preg_replace_callback('/@__raw_block_(\d+)__@/', function ($matches) use ($rawBlocks) { + if (! isset($rawBlocks[$matches[1]])) { + return $matches[0]; + } + + $raw = $rawBlocks[$matches[1]]; + + // @php blocks are stored as raw PHP tags and can be written back as-is. + if (str_starts_with($raw, '')) { + return $raw; + } + + // @verbatim blocks are stored as literal content. Re-wrap them so the + // separate compile pass on the nocache view leaves them untouched. + return '@verbatim'.$raw.'@endverbatim'; + }, $compiled); + } } diff --git a/tests/StaticCaching/NocacheBladeTest.php b/tests/StaticCaching/NocacheBladeTest.php index 7309d48166a..5c3ca331b66 100644 --- a/tests/StaticCaching/NocacheBladeTest.php +++ b/tests/StaticCaching/NocacheBladeTest.php @@ -18,6 +18,37 @@ public function it_renders_the_view_inline_when_there_is_no_matched_route() $this->assertSame('

region

', trim(Blade::render('@nocache("nocache-probe")'))); } + #[Test] + public function it_preserves_php_blocks_inside_a_nocache_region() + { + $this->artisan('view:clear'); + + $template = <<<'BLADE' + +@php +$text = 'text'; +@endphp +@if($text) +

{{ $text }}

+@endif +
+BLADE; + + $this->assertSame('

text

', trim(Blade::render($template))); + } + + #[Test] + public function it_preserves_verbatim_blocks_inside_a_nocache_region() + { + $this->artisan('view:clear'); + + $template = <<<'BLADE' +@verbatim

{{ text }}

@endverbatim
+BLADE; + + $this->assertSame('

{{ text }}

', trim(Blade::render($template))); + } + public function bladeViewPaths($app) { $app['config']->set('view.paths', [