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
16 changes: 16 additions & 0 deletions src/StaticCaching/DefaultInvalidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Statamic\Contracts\Globals\Variables;
use Statamic\Contracts\Structures\Nav;
use Statamic\Contracts\Structures\NavTree;
use Statamic\Facades;
use Statamic\Facades\Antlers;
use Statamic\Facades\Site;
use Statamic\Facades\URL;
Expand Down Expand Up @@ -270,6 +271,8 @@ protected function getCollectionTreeUrls($tree)
{
$rules = $this->parseInvalidationRules(Arr::get($this->rules, "collections.{$tree->collection()->handle()}.urls", []));

$urls = $this->getMovedEntryUrls($tree);

$absoluteUrls = $rules->filter(fn (string $rule) => URL::isAbsolute($rule))->all();

$prefixedRelativeUrls = $rules
Expand All @@ -278,11 +281,24 @@ protected function getCollectionTreeUrls($tree)
->all();

return [
...$urls,
...$absoluteUrls,
...$prefixedRelativeUrls,
];
}

private function getMovedEntryUrls($tree)
{
return collect($tree->diff()->ancestryChanged())
->map(fn ($id) => Facades\Entry::find($id))
->filter()
->reject(fn ($entry) => $entry->isRedirect())
->map->absoluteUrl()
->filter()
->values()
->all();
}

private function parseInvalidationRules(array $rules, array $context = []): IlluminateCollection
{
return collect($rules)
Expand Down
54 changes: 54 additions & 0 deletions tests/StaticCaching/DefaultInvalidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
use Statamic\Contracts\Structures\Nav;
use Statamic\Contracts\Taxonomies\Taxonomy;
use Statamic\Contracts\Taxonomies\Term;
use Statamic\Facades\Entry as EntryFacade;
use Statamic\Facades\Site;
use Statamic\Facades\URL;
use Statamic\Globals\Variables;
use Statamic\StaticCaching\Cacher;
use Statamic\StaticCaching\DefaultInvalidator as Invalidator;
use Statamic\Structures\CollectionTree;
use Statamic\Structures\CollectionTreeDiff;
use Statamic\Structures\NavTree;
use Statamic\Structures\Structure;
use Statamic\Taxonomies\LocalizedTerm;
Expand Down Expand Up @@ -224,6 +226,7 @@ public function collection_urls_can_be_invalidated_by_a_tree()
$m->shouldReceive('structure')->andReturn($structure);
$m->shouldReceive('collection')->andReturn($collection);
$m->shouldReceive('site')->andReturn(Site::default());
$m->shouldReceive('diff')->andReturn(new CollectionTreeDiff);
});

$invalidator = new Invalidator($cacher, [
Expand Down Expand Up @@ -273,6 +276,7 @@ public function collection_urls_can_be_invalidated_by_a_tree_in_a_multisite()
$m->shouldReceive('structure')->andReturn($structure);
$m->shouldReceive('collection')->andReturn($collection);
$m->shouldReceive('site')->andReturn(Site::get('fr'));
$m->shouldReceive('diff')->andReturn(new CollectionTreeDiff);
});

$invalidator = new Invalidator($cacher, [
Expand All @@ -293,6 +297,56 @@ public function collection_urls_can_be_invalidated_by_a_tree_in_a_multisite()
$this->assertNull($invalidator->invalidate($tree));
}

#[Test]
public function moved_entry_urls_can_be_invalidated_by_a_tree()
{
$cacher = tap(Mockery::mock(Cacher::class), function ($cacher) {
$cacher->shouldReceive('invalidateUrls')->with([
'http://localhost/parent/child',
'http://localhost/blog/one',
])->once();
});

$movedEntry = tap(Mockery::mock(Entry::class), function ($m) {
$m->shouldReceive('isRedirect')->andReturn(false);
$m->shouldReceive('absoluteUrl')->andReturn('http://localhost/parent/child');
});

$redirectEntry = tap(Mockery::mock(Entry::class), function ($m) {
$m->shouldReceive('isRedirect')->andReturn(true);
});

EntryFacade::shouldReceive('find')->with('child')->andReturn($movedEntry);
EntryFacade::shouldReceive('find')->with('redirect')->andReturn($redirectEntry);
EntryFacade::shouldReceive('find')->with('missing')->andReturnNull();

$diff = tap(Mockery::mock(CollectionTreeDiff::class), function ($m) {
$m->shouldReceive('ancestryChanged')->andReturn(['child', 'redirect', 'missing']);
});

$collection = tap(Mockery::mock(Collection::class), function ($m) {
$m->shouldReceive('handle')->andReturn('blog');
});

$tree = tap(Mockery::mock(CollectionTree::class), function ($m) use ($collection, $diff) {
$m->shouldReceive('collection')->andReturn($collection);
$m->shouldReceive('site')->andReturn(Site::default());
$m->shouldReceive('diff')->andReturn($diff);
});

$invalidator = new Invalidator($cacher, [
'collections' => [
'blog' => [
'urls' => [
'/blog/one',
],
],
],
]);

$this->assertNull($invalidator->invalidate($tree));
}

#[Test]
public function collection_urls_can_be_invalidated_by_an_entry()
{
Expand Down
26 changes: 26 additions & 0 deletions tests/StaticCaching/HalfMeasureStaticCachingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Orchestra\Testbench\Attributes\DefineEnvironment;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Console\Commands\StaticWarmJob;
use Statamic\Facades\Collection;
use Statamic\StaticCaching\Cacher;
use Statamic\StaticCaching\Replacer;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -295,6 +296,31 @@ public function invalidating_a_cached_404_lets_new_content_be_served()
$this->get('/about')->assertOk()->assertSee('The About Page');
}

#[Test]
public function moving_an_entry_in_the_tree_invalidates_the_cached_404_at_its_new_url()
{
\Illuminate\Support\Facades\Cache::flush();

$this->withStandardFakeViews();
$this->viewShouldReturnRaw('default', '{{ title }}');
$this->viewShouldReturnRaw('errors.404', '404 not found');

$collection = tap(Collection::make('pages')->routes('{parent_uri}/{slug}')->template('default'))->save();
$this->createPage('parent', ['with' => ['title' => 'The Parent Page']]);
$this->createPage('child', ['with' => ['title' => 'The Child Page']]);
$collection->structureContents(['root' => false])->save();
$collection->structure()->makeTree('en', [['entry' => 'parent'], ['entry' => 'child']])->save();

// Both pages are top level, so this URL 404s and the 404 gets cached.
$this->get('/parent/child')->assertNotFound();

// Moving the child page below the parent page makes the URL valid...
$collection->structure()->in('en')->tree([['entry' => 'parent', 'children' => [['entry' => 'child']]]])->save();

// ...so the page is served instead of the stale 404.
$this->get('/parent/child')->assertOk()->assertSee('The Child Page');
}

#[Test]
public function wildcard_refresh_invalidates_cached_404s_instead_of_warming_them()
{
Expand Down
Loading