diff --git a/src/Console/Commands/StaticWarm.php b/src/Console/Commands/StaticWarm.php
index b40908cbe5f..4ff482b5b74 100644
--- a/src/Console/Commands/StaticWarm.php
+++ b/src/Console/Commands/StaticWarm.php
@@ -84,8 +84,6 @@ public function handle()
private function warm(): void
{
- $client = new Client($this->clientConfig());
-
$this->output->newLine();
$this->line('Compiling URLs...');
@@ -109,7 +107,7 @@ private function warm(): void
} else {
$this->line('Visiting '.count($requests).' URLs...');
- $pool = new Pool($client, $requests, [
+ $pool = new Pool($this->client(), $requests, [
'concurrency' => $this->concurrency(),
'fulfilled' => [$this, 'outputSuccessLine'],
'rejected' => [$this, 'outputFailureLine'],
@@ -121,6 +119,33 @@ private function warm(): void
}
}
+ private function warmPaginatedPages(string $url, int $currentPage, int $totalPages, string $pageName): void
+ {
+ $urls = collect(range($currentPage, $totalPages))->map(function ($page) use ($url, $pageName) {
+ $url = "{$url}?{$pageName}={$page}";
+
+ if (config('statamic.static_caching.background_recache', false)) {
+ $url = RecacheToken::addToUrl($url);
+ }
+
+ return $url;
+ });
+
+ $requests = $urls->map(fn (string $url) => new Request('GET', $url))->all();
+
+ $pool = new Pool($this->client(), $requests, [
+ 'concurrency' => $this->concurrency(),
+ 'fulfilled' => function (Response $response, $index) use ($urls) {
+ $this->components->twoColumnDetail($this->getRelativeUri($urls->get($index)), '✓ Cached');
+ },
+ 'rejected' => [$this, 'outputFailureLine'],
+ ]);
+
+ $promise = $pool->promise();
+
+ $promise->wait();
+ }
+
private function concurrency(): int
{
$strategy = config('statamic.static_caching.strategy');
@@ -128,6 +153,11 @@ private function concurrency(): int
return config("statamic.static_caching.strategies.$strategy.warm_concurrency", 25);
}
+ private function client(): Client
+ {
+ return new Client($this->clientConfig());
+ }
+
private function clientConfig(): array
{
return [
@@ -140,12 +170,18 @@ private function clientConfig(): array
public function outputSuccessLine(Response $response, $index): void
{
- $this->components->twoColumnDetail($this->getRelativeUri($index), '✓ Cached');
+ $this->components->twoColumnDetail($this->getRelativeUri($this->uris()->get($index)), '✓ Cached');
+
+ if ($response->hasHeader('X-Statamic-Pagination')) {
+ [$currentPage, $totalPages, $pageName] = $response->getHeader('X-Statamic-Pagination');
+
+ $this->warmPaginatedPages($this->uris()->get($index), $currentPage, $totalPages, $pageName);
+ }
}
public function outputFailureLine($exception, $index): void
{
- $uri = $this->getRelativeUri($index);
+ $uri = $this->getRelativeUri($this->uris()->get($index));
if ($exception instanceof RequestException && $exception->hasResponse()) {
$response = $exception->getResponse();
@@ -162,9 +198,9 @@ public function outputFailureLine($exception, $index): void
$this->components->twoColumnDetail($uri, "$message");
}
- private function getRelativeUri(int $index): string
+ private function getRelativeUri(string $uri): string
{
- return Str::start(Str::after($this->uris()->get($index), config('app.url')), '/');
+ return Str::start(Str::after($uri, config('app.url')), '/');
}
private function requests()
diff --git a/src/Console/Commands/StaticWarmJob.php b/src/Console/Commands/StaticWarmJob.php
index 4ae92b1b429..31298d97e4a 100644
--- a/src/Console/Commands/StaticWarmJob.php
+++ b/src/Console/Commands/StaticWarmJob.php
@@ -9,6 +9,7 @@
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
+use Psr\Http\Message\ResponseInterface;
class StaticWarmJob implements ShouldBeUnique, ShouldQueue
{
@@ -24,6 +25,36 @@ public function __construct(public Request $request, public array $clientConfig)
public function handle()
{
- (new Client($this->clientConfig))->send($this->request);
+ $response = (new Client($this->clientConfig))->send($this->request);
+
+ if ($this->shouldWarmPaginatedPages($response)) {
+ [$currentPage, $totalPages, $pageName] = $response->getHeader('X-Statamic-Pagination');
+
+ collect(range($currentPage, $totalPages))
+ ->map(function (int $page) use ($pageName): string {
+ $url = $this->request->getUri();
+
+ return implode('', [
+ $url,
+ str_contains($url, '?') ? '&' : '?',
+ "{$pageName}={$page}",
+ ]);
+ })
+ ->each(fn (string $uri) => StaticWarmJob::dispatch(
+ new Request('GET', $uri),
+ $this->clientConfig
+ ));
+ }
+ }
+
+ private function shouldWarmPaginatedPages(ResponseInterface $response): bool
+ {
+ if (! $response->hasHeader('X-Statamic-Pagination')) {
+ return false;
+ }
+
+ [$currentPage, $totalPages, $pageName] = $response->getHeader('X-Statamic-Pagination');
+
+ return ! str_contains($this->request->getUri()->getQuery(), "{$pageName}=");
}
}
diff --git a/src/StaticCaching/Middleware/Cache.php b/src/StaticCaching/Middleware/Cache.php
index ddbe646e4e0..2323462120c 100644
--- a/src/StaticCaching/Middleware/Cache.php
+++ b/src/StaticCaching/Middleware/Cache.php
@@ -10,6 +10,7 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache as AppCache;
use Illuminate\Support\Facades\Log;
+use Statamic\Facades\Blink;
use Statamic\Facades\StaticCache;
use Statamic\Statamic;
use Statamic\StaticCaching\Cacher;
@@ -83,6 +84,16 @@ private function handleRequest($request, Closure $next)
$this->makeReplacementsAndCacheResponse($request, $response);
$this->nocache->write();
+
+ if ($paginator = Blink::get('tag-paginator')) {
+ if ($paginator->hasMorePages()) {
+ $response->headers->set('X-Statamic-Pagination', [
+ 'current' => $paginator->currentPage(),
+ 'total' => $paginator->lastPage(),
+ 'name' => $paginator->getPageName(),
+ ]);
+ }
+ }
} elseif (! $response->isRedirect()) {
$this->makeReplacements($response);
}
diff --git a/tests/Console/Commands/StaticWarmJobTest.php b/tests/Console/Commands/StaticWarmJobTest.php
index b92c0e2941c..ab2695e61a8 100644
--- a/tests/Console/Commands/StaticWarmJobTest.php
+++ b/tests/Console/Commands/StaticWarmJobTest.php
@@ -6,6 +6,7 @@
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
+use Illuminate\Support\Facades\Queue;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Console\Commands\StaticWarmJob;
use Tests\TestCase;
@@ -27,4 +28,66 @@ public function it_sends_a_get_request()
$this->assertEquals('/about', $mock->getLastRequest()->getUri()->getPath());
}
+
+ #[Test]
+ public function it_sends_a_get_request_and_dispatches_static_warm_job_for_page_with_pagination()
+ {
+ Queue::fake();
+
+ $mock = new MockHandler([
+ (new Response(200))->withHeader('X-Statamic-Pagination', [
+ 'current' => 1,
+ 'total' => 3,
+ 'name' => 'page',
+ ]),
+ ]);
+
+ $handlerStack = HandlerStack::create($mock);
+
+ $job = new StaticWarmJob(new Request('GET', '/blog'), ['handler' => $handlerStack]);
+
+ $job->handle();
+
+ $this->assertEquals('/blog', $mock->getLastRequest()->getUri()->getPath());
+
+ Queue::assertPushed(StaticWarmJob::class, function (StaticWarmJob $job) {
+ return $job->request->getUri()->getPath() === '/blog'
+ && $job->request->getUri()->getQuery() === 'page=1';
+ });
+
+ Queue::assertPushed(StaticWarmJob::class, function (StaticWarmJob $job) {
+ return $job->request->getUri()->getPath() === '/blog'
+ && $job->request->getUri()->getQuery() === 'page=2';
+ });
+
+ Queue::assertPushed(StaticWarmJob::class, function (StaticWarmJob $job) {
+ return $job->request->getUri()->getPath() === '/blog'
+ && $job->request->getUri()->getQuery() === 'page=3';
+ });
+ }
+
+ #[Test]
+ public function subsequent_paginated_pages_dont_dispatch_static_warm_jobs()
+ {
+ Queue::fake();
+
+ $mock = new MockHandler([
+ (new Response(200))->withHeader('X-Statamic-Pagination', [
+ 'current' => 2,
+ 'total' => 3,
+ 'name' => 'page',
+ ]),
+ ]);
+
+ $handlerStack = HandlerStack::create($mock);
+
+ $job = new StaticWarmJob(new Request('GET', '/blog?page=2'), ['handler' => $handlerStack]);
+
+ $job->handle();
+
+ $this->assertEquals('/blog', $mock->getLastRequest()->getUri()->getPath());
+
+ // The first page is responsible for dispatchin jobs. Not subsequent pages.
+ Queue::assertNothingPushed();
+ }
}