Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3c93cf3
When there's more pages, warm them
duncanmcclean Feb 2, 2024
31a699a
Merge remote-tracking branch 'origin/4.x' into warm-paginated-pages
duncanmcclean Feb 8, 2024
0bc87ea
wip
duncanmcclean Feb 8, 2024
d2fdfd7
make things work another way
duncanmcclean Feb 8, 2024
a697658
When `ignore_query_strings` is enabled, keep `page` parameter
duncanmcclean Feb 9, 2024
98ceb6f
Make a "whitelisted query parameters" option
duncanmcclean Feb 9, 2024
58d3d49
Add test
duncanmcclean Feb 10, 2024
1231a6a
Fix `?` being appended without any whitelisted params present
duncanmcclean Feb 10, 2024
4c7d410
Make pagination work with queued warming
duncanmcclean Feb 10, 2024
4221c7c
Refactor
duncanmcclean Feb 10, 2024
e2373b8
Fix styling
duncanmcclean Feb 10, 2024
f369ea6
Merge branch '4.x' into warm-paginated-pages
jasonvarga Mar 22, 2024
60ba73c
avoid controversial word
jasonvarga Mar 22, 2024
dfdb76e
Merge remote-tracking branch 'origin/5.x' into warm-paginated-pages
duncanmcclean May 13, 2024
8aa2997
Merge branch '5.x' into warm-paginated-pages
duncanmcclean Jul 16, 2024
3ee8e01
use `clientConfig()` method when new'ing up client
duncanmcclean Jul 16, 2024
001d870
this should be allowed
duncanmcclean Jul 16, 2024
529861f
Merge branch '5.x' into warm-paginated-pages
duncanmcclean Aug 9, 2024
bb344a5
Pass the Guzzle config through to the StaticWarmJob
duncanmcclean Aug 9, 2024
f59ed01
Merge remote-tracking branch 'origin/5.x' into warm-paginated-pages
duncanmcclean Aug 26, 2024
dc0a1fa
Merge remote-tracking branch 'origin/5.x' into warm-paginated-pages
duncanmcclean Sep 12, 2024
d3c23dd
Fix styling
duncanmcclean Sep 12, 2024
9991ea4
no longer needed
duncanmcclean Sep 12, 2024
83ee4da
Revert "no longer needed"
duncanmcclean Sep 12, 2024
33ffe10
tidy up
duncanmcclean Sep 12, 2024
9eb3f58
Merge branch 'master' into warm-paginated-pages
duncanmcclean Nov 5, 2025
7f9fdf5
Merge branch 'master' into warm-paginated-pages
duncanmcclean Dec 10, 2025
0c9c2df
don't need this anymore. allow/disallowed query param options aren't …
duncanmcclean Dec 10, 2025
fc86b84
Append recache token to URL when necessary
duncanmcclean Dec 10, 2025
70f42bb
Only the first page should dispatch jobs for paginated pages
duncanmcclean Dec 10, 2025
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
50 changes: 43 additions & 7 deletions src/Console/Commands/StaticWarm.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ public function handle()

private function warm(): void
{
$client = new Client($this->clientConfig());

$this->output->newLine();
$this->line('Compiling URLs...');

Expand All @@ -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'],
Expand All @@ -121,13 +119,45 @@ 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)), '<info>✓ Cached</info>');
},
'rejected' => [$this, 'outputFailureLine'],
]);

$promise = $pool->promise();

$promise->wait();
}

private function concurrency(): int
{
$strategy = config('statamic.static_caching.strategy');

return config("statamic.static_caching.strategies.$strategy.warm_concurrency", 25);
}

private function client(): Client
{
return new Client($this->clientConfig());
}

private function clientConfig(): array
{
return [
Expand All @@ -140,12 +170,18 @@ private function clientConfig(): array

public function outputSuccessLine(Response $response, $index): void
{
$this->components->twoColumnDetail($this->getRelativeUri($index), '<info>✓ Cached</info>');
$this->components->twoColumnDetail($this->getRelativeUri($this->uris()->get($index)), '<info>✓ Cached</info>');

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();
Expand All @@ -162,9 +198,9 @@ public function outputFailureLine($exception, $index): void
$this->components->twoColumnDetail($uri, "<fg=cyan>$message</fg=cyan>");
}

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()
Expand Down
33 changes: 32 additions & 1 deletion src/Console/Commands/StaticWarmJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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}=");
}
}
11 changes: 11 additions & 0 deletions src/StaticCaching/Middleware/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
63 changes: 63 additions & 0 deletions tests/Console/Commands/StaticWarmJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
}