Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/View/Blade/Concerns/CompilesNocache.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Comment on lines +37 to +41

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PHP-vs-verbatim discriminator is a content-shape heuristic (str_starts_with($raw, '<?php') && str_ends_with($raw, '?>')), not a real type tag — Laravel's rawBlocks doesn't retain origin type. If a @verbatim block's trimmed content happens to itself be exactly <?php ... ?> (e.g. someone documenting Blade/PHP syntax verbatim), it will be misclassified as a passthrough PHP block and actually executed as PHP in the second compile pass instead of rendered as literal text. Narrow edge case, template-author-controlled content (not a remote-attacker vector), but it's a genuine logic bug in new code, not pre-existing. No test covers this collision case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A fix would mean shielding @php/@verbatim blocks inside statamic:nocache regions before Laravel's own storeUncompiledBlocks() runs which requires subclassing BladeCompiler and rebinding the blade.compiler container singleton so we can intercept ahead of parent::compileString().

Given it's template-author-controlled content and requires deliberately writing verbatim content that's byte-for-byte an empty PHP tag pair, could we document the limitation in a comment for now and come back to the bigger change if required down the line?


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, '<?php') && str_ends_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);
}
}
31 changes: 31 additions & 0 deletions tests/StaticCaching/NocacheBladeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,37 @@ public function it_renders_the_view_inline_when_there_is_no_matched_route()
$this->assertSame('<p>region</p>', trim(Blade::render('@nocache("nocache-probe")')));
}

#[Test]
public function it_preserves_php_blocks_inside_a_nocache_region()
{
$this->artisan('view:clear');

$template = <<<'BLADE'
<statamic:nocache>
@php
$text = 'text';
@endphp
@if($text)
<p>{{ $text }}</p>
@endif
</statamic:nocache>
BLADE;

$this->assertSame('<p>text</p>', trim(Blade::render($template)));
}

#[Test]
public function it_preserves_verbatim_blocks_inside_a_nocache_region()
{
$this->artisan('view:clear');

$template = <<<'BLADE'
<statamic:nocache>@verbatim<p>{{ text }}</p>@endverbatim</statamic:nocache>
BLADE;

$this->assertSame('<p>{{ text }}</p>', trim(Blade::render($template)));
}

public function bladeViewPaths($app)
{
$app['config']->set('view.paths', [
Expand Down
Loading