From 7b937531ee1ed08a3c66a91b58209bf0c26f61dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joachim=20R=C3=BCtter?= Date: Thu, 25 Jun 2026 21:51:09 +0000 Subject: [PATCH] Tolerate a folded X-Statamic-Pagination header when warming A proxy or CDN can fold the three X-Statamic-Pagination header values into a single comma-joined line, so statamic:static:warm and its queued job threw an Undefined array key error when destructuring it. Normalize both framings before destructuring, keeping a page name that contains a comma intact. --- .../Concerns/NormalizesPaginationHeader.php | 24 +++++++++ src/Console/Commands/StaticWarm.php | 4 +- src/Console/Commands/StaticWarmJob.php | 7 +-- tests/Console/Commands/StaticWarmJobTest.php | 52 +++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 src/Console/Commands/Concerns/NormalizesPaginationHeader.php diff --git a/src/Console/Commands/Concerns/NormalizesPaginationHeader.php b/src/Console/Commands/Concerns/NormalizesPaginationHeader.php new file mode 100644 index 00000000000..702a87a6965 --- /dev/null +++ b/src/Console/Commands/Concerns/NormalizesPaginationHeader.php @@ -0,0 +1,24 @@ +getHeader('X-Statamic-Pagination')), 3) + ); + + return [(int) $current, (int) $total, $name]; + } +} diff --git a/src/Console/Commands/StaticWarm.php b/src/Console/Commands/StaticWarm.php index 4ff482b5b74..d8b3867aa26 100644 --- a/src/Console/Commands/StaticWarm.php +++ b/src/Console/Commands/StaticWarm.php @@ -12,6 +12,7 @@ use Illuminate\Http\Request as HttpRequest; use Illuminate\Routing\Route; use Illuminate\Support\Collection; +use Statamic\Console\Commands\Concerns\NormalizesPaginationHeader; use Statamic\Console\EnhancesCommands; use Statamic\Console\RunsInPlease; use Statamic\Entries\Collection as EntriesCollection; @@ -31,6 +32,7 @@ class StaticWarm extends Command { use EnhancesCommands; use Hookable; + use NormalizesPaginationHeader; use RunsInPlease; protected $signature = 'statamic:static:warm @@ -173,7 +175,7 @@ public function outputSuccessLine(Response $response, $index): void $this->components->twoColumnDetail($this->getRelativeUri($this->uris()->get($index)), '✓ Cached'); if ($response->hasHeader('X-Statamic-Pagination')) { - [$currentPage, $totalPages, $pageName] = $response->getHeader('X-Statamic-Pagination'); + [$currentPage, $totalPages, $pageName] = $this->paginationHeader($response); $this->warmPaginatedPages($this->uris()->get($index), $currentPage, $totalPages, $pageName); } diff --git a/src/Console/Commands/StaticWarmJob.php b/src/Console/Commands/StaticWarmJob.php index 31298d97e4a..e387ad0c5a9 100644 --- a/src/Console/Commands/StaticWarmJob.php +++ b/src/Console/Commands/StaticWarmJob.php @@ -10,10 +10,11 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Psr\Http\Message\ResponseInterface; +use Statamic\Console\Commands\Concerns\NormalizesPaginationHeader; class StaticWarmJob implements ShouldBeUnique, ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable; + use Dispatchable, InteractsWithQueue, NormalizesPaginationHeader, Queueable; public $uniqueId; public $tries = 1; @@ -28,7 +29,7 @@ public function handle() $response = (new Client($this->clientConfig))->send($this->request); if ($this->shouldWarmPaginatedPages($response)) { - [$currentPage, $totalPages, $pageName] = $response->getHeader('X-Statamic-Pagination'); + [$currentPage, $totalPages, $pageName] = $this->paginationHeader($response); collect(range($currentPage, $totalPages)) ->map(function (int $page) use ($pageName): string { @@ -53,7 +54,7 @@ private function shouldWarmPaginatedPages(ResponseInterface $response): bool return false; } - [$currentPage, $totalPages, $pageName] = $response->getHeader('X-Statamic-Pagination'); + [$currentPage, $totalPages, $pageName] = $this->paginationHeader($response); return ! str_contains($this->request->getUri()->getQuery(), "{$pageName}="); } diff --git a/tests/Console/Commands/StaticWarmJobTest.php b/tests/Console/Commands/StaticWarmJobTest.php index ab2695e61a8..d741381cb19 100644 --- a/tests/Console/Commands/StaticWarmJobTest.php +++ b/tests/Console/Commands/StaticWarmJobTest.php @@ -8,6 +8,7 @@ use GuzzleHttp\Psr7\Response; use Illuminate\Support\Facades\Queue; use PHPUnit\Framework\Attributes\Test; +use Statamic\Console\Commands\Concerns\NormalizesPaginationHeader; use Statamic\Console\Commands\StaticWarmJob; use Tests\TestCase; @@ -90,4 +91,55 @@ public function subsequent_paginated_pages_dont_dispatch_static_warm_jobs() // The first page is responsible for dispatchin jobs. Not subsequent pages. Queue::assertNothingPushed(); } + + #[Test] + public function it_dispatches_paginated_jobs_when_the_pagination_header_is_folded_into_one_line() + { + Queue::fake(); + + // A proxy or CDN may coalesce the repeated header into a single comma-joined line. + $mock = new MockHandler([ + (new Response(200))->withHeader('X-Statamic-Pagination', '1, 3, page'), + ]); + + $handlerStack = HandlerStack::create($mock); + + $job = new StaticWarmJob(new Request('GET', '/blog'), ['handler' => $handlerStack]); + + $job->handle(); + + Queue::assertPushed(StaticWarmJob::class, function (StaticWarmJob $job) { + return $job->request->getUri()->getQuery() === 'page=1'; + }); + + Queue::assertPushed(StaticWarmJob::class, function (StaticWarmJob $job) { + return $job->request->getUri()->getQuery() === 'page=2'; + }); + + Queue::assertPushed(StaticWarmJob::class, function (StaticWarmJob $job) { + return $job->request->getUri()->getQuery() === 'page=3'; + }); + } + + #[Test] + public function it_keeps_a_page_name_that_contains_a_comma() + { + $parser = new class + { + use NormalizesPaginationHeader; + + public function parse($response) + { + return $this->paginationHeader($response); + } + }; + + // Three separate header values, as set by the static caching middleware. + $separate = (new Response(200))->withHeader('X-Statamic-Pagination', ['current' => 1, 'total' => 3, 'name' => 'pa,ge']); + $this->assertSame([1, 3, 'pa,ge'], $parser->parse($separate)); + + // The same header folded into one comma-joined line by a proxy. + $folded = (new Response(200))->withHeader('X-Statamic-Pagination', '1, 3, pa,ge'); + $this->assertSame([1, 3, 'pa,ge'], $parser->parse($folded)); + } }