diff --git a/src/StaticCaching/DefaultInvalidator.php b/src/StaticCaching/DefaultInvalidator.php index 00ffdce651..4e924e3311 100644 --- a/src/StaticCaching/DefaultInvalidator.php +++ b/src/StaticCaching/DefaultInvalidator.php @@ -6,10 +6,14 @@ use Statamic\Contracts\Entries\Collection; use Statamic\Contracts\Entries\Entry; use Statamic\Contracts\Forms\Form; -use Statamic\Contracts\Globals\GlobalSet; +use Statamic\Contracts\Globals\Variables; use Statamic\Contracts\Structures\Nav; -use Statamic\Contracts\Taxonomies\Term; +use Statamic\Contracts\Structures\NavTree; +use Statamic\Facades\Site; +use Statamic\Structures\CollectionTree; use Statamic\Support\Arr; +use Statamic\Support\Str; +use Statamic\Taxonomies\LocalizedTerm; class DefaultInvalidator implements Invalidator { @@ -22,22 +26,28 @@ public function __construct(Cacher $cacher, $rules = []) $this->rules = $rules; } - public function invalidate($item) + public function invalidate($item): void { if ($this->rules === 'all') { - return $this->cacher->flush(); + $this->cacher->flush(); + + return; } if ($item instanceof Entry) { $this->invalidateEntryUrls($item); - } elseif ($item instanceof Term) { + } elseif ($item instanceof LocalizedTerm) { $this->invalidateTermUrls($item); } elseif ($item instanceof Nav) { $this->invalidateNavUrls($item); - } elseif ($item instanceof GlobalSet) { + } elseif ($item instanceof NavTree) { + $this->invalidateNavTreeUrls($item); + } elseif ($item instanceof Variables) { $this->invalidateGlobalUrls($item); } elseif ($item instanceof Collection) { $this->invalidateCollectionUrls($item); + } elseif ($item instanceof CollectionTree) { + $this->invalidateCollectionTreeUrls($item); } elseif ($item instanceof Asset) { $this->invalidateAssetUrls($item); } elseif ($item instanceof Form) { @@ -45,35 +55,61 @@ public function invalidate($item) } } - protected function invalidateFormUrls($form) + protected function invalidateFormUrls($form): void { - $this->cacher->invalidateUrls( - Arr::get($this->rules, "forms.{$form->handle()}.urls") - ); + $rules = collect(Arr::get($this->rules, "forms.{$form->handle()}.urls")); + + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); + + Site::all()->each(function ($site) use ($rules) { + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl(Str::removeRight($site->url(), '/').Str::ensureLeft($rule, '/'))); + }); } - protected function invalidateAssetUrls($asset) + protected function invalidateAssetUrls($asset): void { - $this->cacher->invalidateUrls( - Arr::get($this->rules, "assets.{$asset->container()->handle()}.urls") - ); + $rules = collect(Arr::get($this->rules, "assets.{$asset->container()->handle()}.urls")); + + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); + + Site::all()->each(function ($site) use ($rules) { + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl(Str::removeRight($site->url(), '/').Str::ensureLeft($rule, '/'))); + }); } - protected function invalidateEntryUrls($entry) + protected function invalidateEntryUrls($entry): void { + $rules = collect(Arr::get($this->rules, "collections.{$entry->collectionHandle()}.urls")); + $entry->descendants()->merge([$entry])->each(function ($entry) { if (! $entry->isRedirect() && $url = $entry->absoluteUrl()) { $this->cacher->invalidateUrl(...$this->splitUrlAndDomain($url)); } }); + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); + $this->cacher->invalidateUrls( - Arr::get($this->rules, "collections.{$entry->collectionHandle()}.urls") + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($entry->site()->url(), '/').Str::ensureLeft($rule, '/'))->values()->all() ); } - protected function invalidateTermUrls($term) + protected function invalidateTermUrls($term): void { + $rules = collect(Arr::get($this->rules, "taxonomies.{$term->taxonomyHandle()}.urls")); + if ($url = $term->absoluteUrl()) { $this->cacher->invalidateUrl(...$this->splitUrlAndDomain($url)); @@ -84,37 +120,106 @@ protected function invalidateTermUrls($term) }); } + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); + $this->cacher->invalidateUrls( - Arr::get($this->rules, "taxonomies.{$term->taxonomyHandle()}.urls") + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($term->site()->url(), '/').Str::ensureLeft($rule, '/'))->values()->all() ); } - protected function invalidateNavUrls($nav) + protected function invalidateNavUrls($nav): void + { + $rules = collect(Arr::get($this->rules, "navigation.{$nav->handle()}.urls")); + + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); + + $nav->sites()->each(function (string $site) use ($rules) { + $this->cacher->invalidateUrls( + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight(Site::get($site)->url(), '/').Str::ensureLeft($rule, '/'))->values()->all() + ); + }); + } + + protected function invalidateNavTreeUrls($tree): void { + $rules = collect(Arr::get($this->rules, "navigation.{$tree->structure()->handle()}.urls")); + + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); + $this->cacher->invalidateUrls( - Arr::get($this->rules, "navigation.{$nav->handle()}.urls") + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($tree->site()->url(), '/').Str::ensureLeft($rule, '/'))->values()->all() ); } - protected function invalidateGlobalUrls($set) + protected function invalidateGlobalUrls($variables): void { + $rules = collect(Arr::get($this->rules, "globals.{$variables->globalSet()->handle()}.urls")); + + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); + $this->cacher->invalidateUrls( - Arr::get($this->rules, "globals.{$set->handle()}.urls") + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($variables->site()->url(), '/').Str::ensureLeft($rule, '/'))->values()->all() ); } - protected function invalidateCollectionUrls($collection) + protected function invalidateCollectionUrls($collection): void { - if ($url = $collection->absoluteUrl()) { - $this->cacher->invalidateUrl(...$this->splitUrlAndDomain($url)); - } + $rules = collect(Arr::get($this->rules, "collections.{$collection->handle()}.urls")); + + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); + + $collection->sites()->each(function (string $site) use (&$collection, $rules) { + if ($url = $collection->absoluteUrl($site)) { + $this->cacher->invalidateUrl(...$this->splitUrlAndDomain($url)); + } + + $this->cacher->invalidateUrls( + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight(Site::get($site)->url(), '/').Str::ensureLeft($rule, '/'))->values()->all() + ); + }); + } + + protected function invalidateCollectionTreeUrls($tree): void + { + $rules = collect(Arr::get($this->rules, "collections.{$tree->collection()->handle()}.urls")); + + $rules + ->filter(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->each(fn (string $rule) => $this->cacher->invalidateUrl($rule)); $this->cacher->invalidateUrls( - Arr::get($this->rules, "collections.{$collection->handle()}.urls") + $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($tree->site()->url(), '/').Str::ensureLeft($rule, '/'))->values()->all() ); } - private function splitUrlAndDomain(string $url) + private function isAbsoluteUrl(string $url): bool + { + return isset(parse_url($url)['scheme']); + } + + private function splitUrlAndDomain(string $url): array { $parsed = parse_url($url); diff --git a/src/StaticCaching/Invalidate.php b/src/StaticCaching/Invalidate.php index c22f4f6879..bcfa0f2588 100644 --- a/src/StaticCaching/Invalidate.php +++ b/src/StaticCaching/Invalidate.php @@ -13,14 +13,14 @@ use Statamic\Events\EntrySaved; use Statamic\Events\FormDeleted; use Statamic\Events\FormSaved; -use Statamic\Events\GlobalSetDeleted; -use Statamic\Events\GlobalSetSaved; +use Statamic\Events\GlobalVariablesDeleted; +use Statamic\Events\GlobalVariablesSaved; +use Statamic\Events\LocalizedTermDeleted; +use Statamic\Events\LocalizedTermSaved; use Statamic\Events\NavDeleted; use Statamic\Events\NavSaved; use Statamic\Events\NavTreeDeleted; use Statamic\Events\NavTreeSaved; -use Statamic\Events\TermDeleted; -use Statamic\Events\TermSaved; use Statamic\Facades\Form; class Invalidate implements ShouldQueue @@ -32,10 +32,10 @@ class Invalidate implements ShouldQueue AssetDeleted::class => 'invalidateAsset', EntrySaved::class => 'invalidateEntry', EntryDeleting::class => 'invalidateEntry', - TermSaved::class => 'invalidateTerm', - TermDeleted::class => 'invalidateTerm', - GlobalSetSaved::class => 'invalidateGlobalSet', - GlobalSetDeleted::class => 'invalidateGlobalSet', + LocalizedTermSaved::class => 'invalidateTerm', + LocalizedTermDeleted::class => 'invalidateTerm', + GlobalVariablesSaved::class => 'invalidateGlobalSet', + GlobalVariablesDeleted::class => 'invalidateGlobalSet', NavSaved::class => 'invalidateNav', NavDeleted::class => 'invalidateNav', FormSaved::class => 'invalidateForm', @@ -77,7 +77,7 @@ public function invalidateTerm($event) public function invalidateGlobalSet($event) { - $this->invalidator->invalidate($event->globals); + $this->invalidator->invalidate($event->variables); } public function invalidateNav($event) @@ -92,12 +92,12 @@ public function invalidateForm($event) public function invalidateCollectionByTree($event) { - $this->invalidator->invalidate($event->tree->collection()); + $this->invalidator->invalidate($event->tree); } public function invalidateNavByTree($event) { - $this->invalidator->invalidate($event->tree->structure()); + $this->invalidator->invalidate($event->tree); } public function invalidateByBlueprint($event) diff --git a/tests/StaticCaching/DefaultInvalidatorTest.php b/tests/StaticCaching/DefaultInvalidatorTest.php index 79571d41b9..39110aa5f5 100644 --- a/tests/StaticCaching/DefaultInvalidatorTest.php +++ b/tests/StaticCaching/DefaultInvalidatorTest.php @@ -13,16 +13,18 @@ use Statamic\Contracts\Structures\Nav; use Statamic\Contracts\Taxonomies\Taxonomy; use Statamic\Contracts\Taxonomies\Term; +use Statamic\Facades\Site; +use Statamic\Globals\Variables; use Statamic\StaticCaching\Cacher; use Statamic\StaticCaching\DefaultInvalidator as Invalidator; +use Statamic\Structures\CollectionTree; +use Statamic\Structures\NavTree; +use Statamic\Structures\Structure; +use Statamic\Taxonomies\LocalizedTerm; +use Tests\TestCase; -class DefaultInvalidatorTest extends \PHPUnit\Framework\TestCase +class DefaultInvalidatorTest extends TestCase { - public function tearDown(): void - { - Mockery::close(); - } - #[Test] public function specifying_all_as_invalidation_rule_will_just_flush_the_cache() { @@ -38,7 +40,50 @@ public function specifying_all_as_invalidation_rule_will_just_flush_the_cache() public function assets_can_trigger_url_invalidation() { $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { - $cacher->shouldReceive('invalidateUrls')->once()->with(['/page/one', '/page/two']); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/page/one')->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/page/two')->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/page/three')->once(); + }); + + $container = tap(Mockery::mock(AssetContainer::class), function ($m) { + $m->shouldReceive('handle')->andReturn('main'); + }); + + $asset = tap(Mockery::mock(Asset::class), function ($m) use ($container) { + $m->shouldReceive('container')->andReturn($container); + }); + + $invalidator = new Invalidator($cacher, [ + 'assets' => [ + 'main' => [ + 'urls' => [ + '/page/one', + '/page/two', + 'http://localhost/page/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($asset)); + } + + #[Test] + public function assets_can_trigger_url_invalidation_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + ]); + + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/page/one')->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/page/two')->once(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.fr/page/one')->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://test.fr/page/two')->once(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/page/three')->once(); }); $container = tap(Mockery::mock(AssetContainer::class), function ($m) { @@ -55,6 +100,7 @@ public function assets_can_trigger_url_invalidation() 'urls' => [ '/page/one', '/page/two', + 'http://test.com/page/three', ], ], ], @@ -66,14 +112,62 @@ public function assets_can_trigger_url_invalidation() #[Test] public function collection_urls_can_be_invalidated() { + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrl')->with('/my/test/collection', 'http://localhost')->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://localhost/blog/one', 'http://localhost/blog/two'])->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/blog/three')->once(); + }); + + $collection = tap(Mockery::mock(Collection::class), function ($m) { + $m->shouldReceive('handle')->andReturn('blog'); + $m->shouldReceive('sites')->andReturn(collect(['en'])); + $m->shouldReceive('absoluteUrl')->with('en')->andReturn('http://localhost/my/test/collection'); + }); + + $invalidator = new Invalidator($cacher, [ + 'collections' => [ + 'blog' => [ + 'urls' => [ + '/blog/one', + '/blog/two', + 'http://localhost/blog/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($collection)); + } + + #[Test] + public function collection_urls_can_be_invalidated_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + 'de' => ['url' => 'http://test.de', 'locale' => 'de_DE'], + ]); + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { $cacher->shouldReceive('invalidateUrl')->with('/my/test/collection', 'http://test.com')->once(); - $cacher->shouldReceive('invalidateUrls')->once()->with(['/blog/one', '/blog/two']); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.com/blog/one', 'http://test.com/blog/two'])->once(); + + $cacher->shouldReceive('invalidateUrl')->with('/my/test/collection', 'http://test.fr')->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.fr/blog/one', 'http://test.fr/blog/two'])->once(); + + $cacher->shouldReceive('invalidateUrl')->with('/my/test/collection', 'http://test.de')->never(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.de/blog/one', 'http://test.de/blog/two'])->never(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/blog/three')->once(); }); $collection = tap(Mockery::mock(Collection::class), function ($m) { - $m->shouldReceive('absoluteUrl')->andReturn('http://test.com/my/test/collection'); $m->shouldReceive('handle')->andReturn('blog'); + $m->shouldReceive('sites')->andReturn(collect(['en', 'fr'])); + + $m->shouldReceive('absoluteUrl')->with('en')->andReturn('http://test.com/my/test/collection')->once(); + $m->shouldReceive('absoluteUrl')->with('fr')->andReturn('http://test.fr/my/test/collection')->once(); + $m->shouldReceive('absoluteUrl')->with('de')->never(); }); $invalidator = new Invalidator($cacher, [ @@ -82,6 +176,7 @@ public function collection_urls_can_be_invalidated() 'urls' => [ '/blog/one', '/blog/two', + 'http://test.com/blog/three', ], ], ], @@ -90,12 +185,95 @@ public function collection_urls_can_be_invalidated() $this->assertNull($invalidator->invalidate($collection)); } + #[Test] + public function collection_urls_can_be_invalidated_by_a_tree() + { + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrls')->with(['http://localhost/blog/one', 'http://localhost/blog/two'])->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/blog/three')->once(); + }); + + $collection = tap(Mockery::mock(Collection::class), function ($m) { + $m->shouldReceive('handle')->andReturn('blog'); + $m->shouldReceive('sites')->andReturn(collect(['en'])); + }); + + $structure = tap(Mockery::mock(Structure::class), function ($m) use ($collection) { + $m->shouldReceive('collection')->andReturn($collection); + }); + + $tree = tap(Mockery::mock(CollectionTree::class), function ($m) use ($collection, $structure) { + $m->shouldReceive('structure')->andReturn($structure); + $m->shouldReceive('collection')->andReturn($collection); + $m->shouldReceive('site')->andReturn(Site::default()); + }); + + $invalidator = new Invalidator($cacher, [ + 'collections' => [ + 'blog' => [ + 'urls' => [ + '/blog/one', + '/blog/two', + 'http://localhost/blog/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($tree)); + } + + #[Test] + public function collection_urls_can_be_invalidated_by_a_tree_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + ]); + + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrls')->with(['http://test.fr/blog/one', 'http://test.fr/blog/two'])->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.com/blog/one', 'http://test.com/blog/two'])->never(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/blog/three')->once(); + }); + + $collection = tap(Mockery::mock(Collection::class), function ($m) { + $m->shouldReceive('handle')->andReturn('blog'); + $m->shouldReceive('sites')->andReturn(collect(['en'])); + }); + + $structure = tap(Mockery::mock(Structure::class), function ($m) use ($collection) { + $m->shouldReceive('collection')->andReturn($collection); + }); + + $tree = tap(Mockery::mock(CollectionTree::class), function ($m) use ($collection, $structure) { + $m->shouldReceive('structure')->andReturn($structure); + $m->shouldReceive('collection')->andReturn($collection); + $m->shouldReceive('site')->andReturn(Site::get('fr')); + }); + + $invalidator = new Invalidator($cacher, [ + 'collections' => [ + 'blog' => [ + 'urls' => [ + '/blog/one', + '/blog/two', + 'http://localhost/blog/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($tree)); + } + #[Test] public function collection_urls_can_be_invalidated_by_an_entry() { $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { $cacher->shouldReceive('invalidateUrl')->with('/my/test/entry', 'http://test.com')->once(); - $cacher->shouldReceive('invalidateUrls')->once()->with(['/blog/one', '/blog/two']); + $cacher->shouldReceive('invalidateUrls')->with(['http://localhost/blog/one', 'http://localhost/blog/two'])->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/blog/three')->once(); }); $entry = tap(Mockery::mock(Entry::class), function ($m) { @@ -103,6 +281,48 @@ public function collection_urls_can_be_invalidated_by_an_entry() $m->shouldReceive('absoluteUrl')->andReturn('http://test.com/my/test/entry'); $m->shouldReceive('collectionHandle')->andReturn('blog'); $m->shouldReceive('descendants')->andReturn(collect()); + $m->shouldReceive('site')->andReturn(Site::default()); + }); + + $invalidator = new Invalidator($cacher, [ + 'collections' => [ + 'blog' => [ + 'urls' => [ + '/blog/one', + '/blog/two', + 'http://localhost/blog/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($entry)); + } + + #[Test] + public function collection_urls_can_be_invalidated_by_an_entry_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + ]); + + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrl')->with('/my/test/entry', 'http://test.fr')->never(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.fr/blog/one', 'http://test.fr/blog/two'])->never(); + + $cacher->shouldReceive('invalidateUrl')->with('/my/test/entry', 'http://test.fr')->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.fr/blog/one', 'http://test.fr/blog/two'])->once(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/blog/three')->once(); + }); + + $entry = tap(Mockery::mock(Entry::class), function ($m) { + $m->shouldReceive('isRedirect')->andReturn(false); + $m->shouldReceive('absoluteUrl')->andReturn('http://test.fr/my/test/entry'); + $m->shouldReceive('collectionHandle')->andReturn('blog'); + $m->shouldReceive('descendants')->andReturn(collect()); + $m->shouldReceive('site')->andReturn(Site::get('fr')); }); $invalidator = new Invalidator($cacher, [ @@ -111,6 +331,7 @@ public function collection_urls_can_be_invalidated_by_an_entry() 'urls' => [ '/blog/one', '/blog/two', + 'http://test.com/blog/three', ], ], ], @@ -124,7 +345,7 @@ public function entry_urls_are_not_invalidated_by_an_entry_with_a_redirect() { $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { $cacher->shouldReceive('invalidateUrl')->never(); - $cacher->shouldReceive('invalidateUrls')->once()->with(['/blog/one', '/blog/two']); + $cacher->shouldReceive('invalidateUrls')->with(['http://localhost/blog/one', 'http://localhost/blog/two'])->once(); }); $entry = tap(Mockery::mock(Entry::class), function ($m) { @@ -132,6 +353,7 @@ public function entry_urls_are_not_invalidated_by_an_entry_with_a_redirect() $m->shouldReceive('absoluteUrl')->andReturn('http://test.com/my/test/entry'); $m->shouldReceive('collectionHandle')->andReturn('blog'); $m->shouldReceive('descendants')->andReturn(collect()); + $m->shouldReceive('site')->andReturn(Site::default()); }); $invalidator = new Invalidator($cacher, [ @@ -152,9 +374,63 @@ public function entry_urls_are_not_invalidated_by_an_entry_with_a_redirect() public function taxonomy_urls_can_be_invalidated() { $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { - $cacher->shouldReceive('invalidateUrl')->with('/my/test/term', 'http://test.com')->once(); - $cacher->shouldReceive('invalidateUrl')->with('/my/collection/tags/term', 'http://test.com')->once(); - $cacher->shouldReceive('invalidateUrls')->once()->with(['/tags/one', '/tags/two']); + $cacher->shouldReceive('invalidateUrl')->with('/my/test/term', 'http://localhost')->once(); + $cacher->shouldReceive('invalidateUrl')->with('/my/collection/tags/term', 'http://localhost')->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://localhost/tags/one', 'http://localhost/tags/two'])->once(); + + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/tags/three')->once(); + }); + + $collection = Mockery::mock(Collection::class); + + $taxonomy = tap(Mockery::mock(Taxonomy::class), function ($m) use ($collection) { + $m->shouldReceive('collections')->andReturn(collect([$collection])); + }); + + $term = Mockery::mock(Term::class); + + $localized = tap(Mockery::mock(LocalizedTerm::class), function ($m) use ($term, $taxonomy) { + $m->shouldReceive('term')->andReturn($term); + $m->shouldReceive('taxonomyHandle')->andReturn('tags'); + $m->shouldReceive('taxonomy')->andReturn($taxonomy); + $m->shouldReceive('collection')->andReturn($m); + $m->shouldReceive('site')->andReturn(Site::default()); + $m->shouldReceive('absoluteUrl')->andReturn('http://localhost/my/test/term', 'http://localhost/my/collection/tags/term'); + }); + + $invalidator = new Invalidator($cacher, [ + 'taxonomies' => [ + 'tags' => [ + 'urls' => [ + '/tags/one', + '/tags/two', + 'http://localhost/tags/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($localized)); + } + + #[Test] + public function taxonomy_urls_can_be_invalidated_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + ]); + + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrl')->with('/my/test/term', 'http://test.com')->never(); + $cacher->shouldReceive('invalidateUrl')->with('/my/collection/tags/term', 'http://test.com')->never(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.com/tags/one', 'http://test.com/tags/two'])->never(); + + $cacher->shouldReceive('invalidateUrl')->with('/my/test/term', 'http://test.fr')->once(); + $cacher->shouldReceive('invalidateUrl')->with('/my/collection/tags/term', 'http://test.fr')->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.fr/tags/one', 'http://test.fr/tags/two'])->once(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/tags/three')->once(); }); $collection = Mockery::mock(Collection::class); @@ -163,11 +439,15 @@ public function taxonomy_urls_can_be_invalidated() $m->shouldReceive('collections')->andReturn(collect([$collection])); }); - $term = tap(Mockery::mock(Term::class), function ($m) use ($taxonomy) { - $m->shouldReceive('absoluteUrl')->andReturn('http://test.com/my/test/term', 'http://test.com/my/collection/tags/term'); + $term = Mockery::mock(Term::class); + + $localized = tap(Mockery::mock(LocalizedTerm::class), function ($m) use ($term, $taxonomy) { + $m->shouldReceive('term')->andReturn($term); $m->shouldReceive('taxonomyHandle')->andReturn('tags'); $m->shouldReceive('taxonomy')->andReturn($taxonomy); $m->shouldReceive('collection')->andReturn($m); + $m->shouldReceive('site')->andReturn(Site::get('fr')); + $m->shouldReceive('absoluteUrl')->andReturn('http://test.fr/my/test/term', 'http://test.fr/my/collection/tags/term'); }); $invalidator = new Invalidator($cacher, [ @@ -176,23 +456,26 @@ public function taxonomy_urls_can_be_invalidated() 'urls' => [ '/tags/one', '/tags/two', + 'http://test.com/tags/three', ], ], ], ]); - $this->assertNull($invalidator->invalidate($term)); + $this->assertNull($invalidator->invalidate($localized)); } #[Test] public function navigation_urls_can_be_invalidated() { $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { - $cacher->shouldReceive('invalidateUrls')->once()->with(['/one', '/two']); + $cacher->shouldReceive('invalidateUrls')->with(['http://localhost/one', 'http://localhost/two'])->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/three')->once(); }); $nav = tap(Mockery::mock(Nav::class), function ($m) { $m->shouldReceive('handle')->andReturn('links'); + $m->shouldReceive('sites')->andReturn(collect(['en'])); }); $invalidator = new Invalidator($cacher, [ @@ -201,6 +484,7 @@ public function navigation_urls_can_be_invalidated() 'urls' => [ '/one', '/two', + 'http://localhost/three', ], ], ], @@ -209,36 +493,228 @@ public function navigation_urls_can_be_invalidated() $this->assertNull($invalidator->invalidate($nav)); } + #[Test] + public function navigation_urls_can_be_invalidated_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + 'de' => ['url' => 'http://test.de', 'locale' => 'de_DE'], + ]); + + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrls')->with(['http://test.com/one', 'http://test.com/two'])->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.fr/one', 'http://test.fr/two'])->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.de/one', 'http://test.de/two'])->never(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/three')->once(); + }); + + $nav = tap(Mockery::mock(Nav::class), function ($m) { + $m->shouldReceive('handle')->andReturn('links'); + $m->shouldReceive('sites')->andReturn(collect(['en', 'fr'])); + }); + + $invalidator = new Invalidator($cacher, [ + 'navigation' => [ + 'links' => [ + 'urls' => [ + '/one', + '/two', + 'http://test.com/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($nav)); + } + + #[Test] + public function navigation_urls_can_be_invalidated_by_a_tree() + { + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrls')->with(['http://localhost/one', 'http://localhost/two'])->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/three')->once(); + }); + + $nav = tap(Mockery::mock(Nav::class), function ($m) { + $m->shouldReceive('handle')->andReturn('links'); + }); + + $tree = tap(Mockery::mock(NavTree::class), function ($m) use ($nav) { + $m->shouldReceive('structure')->andReturn($nav); + $m->shouldReceive('site')->andReturn(Site::default()); + }); + + $invalidator = new Invalidator($cacher, [ + 'navigation' => [ + 'links' => [ + 'urls' => [ + '/one', + '/two', + 'http://localhost/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($tree)); + } + + #[Test] + public function navigation_urls_can_be_invalidated_by_a_tree_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + ]); + + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrls')->with(['http://test.fr/one', 'http://test.fr/two'])->once(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.com/one', 'http://test.com/two'])->never(); + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/three')->once(); + }); + + $nav = tap(Mockery::mock(Nav::class), function ($m) { + $m->shouldReceive('handle')->andReturn('links'); + }); + + $tree = tap(Mockery::mock(NavTree::class), function ($m) use ($nav) { + $m->shouldReceive('structure')->andReturn($nav); + $m->shouldReceive('site')->andReturn(Site::get('fr')); + }); + + $invalidator = new Invalidator($cacher, [ + 'navigation' => [ + 'links' => [ + 'urls' => [ + '/one', + '/two', + 'http://test.com/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($tree)); + } + #[Test] public function globals_urls_can_be_invalidated() { $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { - $cacher->shouldReceive('invalidateUrls')->once()->with(['/one', '/two']); + $cacher->shouldReceive('invalidateUrls')->with(['http://localhost/one', 'http://localhost/two'])->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/three')->once(); + }); + + $set = tap(Mockery::mock(GlobalSet::class), function ($m) { + $m->shouldReceive('handle')->andReturn('social'); + }); + + $variables = tap(Mockery::mock(Variables::class), function ($m) use ($set) { + $m->shouldReceive('globalSet')->andReturn($set); + $m->shouldReceive('site')->andReturn(Site::default()); + }); + + $invalidator = new Invalidator($cacher, [ + 'globals' => [ + 'social' => [ + 'urls' => [ + '/one', + '/two', + 'http://localhost/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($variables)); + } + + #[Test] + public function globals_urls_can_be_invalidated_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + ]); + + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrls')->with(['http://test.com/one', 'http://test.com/two'])->never(); + $cacher->shouldReceive('invalidateUrls')->with(['http://test.fr/one', 'http://test.fr/two'])->once(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/three')->once(); }); $set = tap(Mockery::mock(GlobalSet::class), function ($m) { $m->shouldReceive('handle')->andReturn('social'); }); + $variables = tap(Mockery::mock(Variables::class), function ($m) use ($set) { + $m->shouldReceive('globalSet')->andReturn($set); + $m->shouldReceive('site')->andReturn(Site::get('fr')); + }); + $invalidator = new Invalidator($cacher, [ 'globals' => [ 'social' => [ 'urls' => [ '/one', '/two', + 'http://test.com/three', ], ], ], ]); - $this->assertNull($invalidator->invalidate($set)); + $this->assertNull($invalidator->invalidate($variables)); } #[Test] public function form_urls_can_be_invalidated() { $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { - $cacher->shouldReceive('invalidateUrls')->once()->with(['/one', '/two']); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/one')->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/two')->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://localhost/three')->once(); + }); + + $form = tap(Mockery::mock(Form::class), function ($m) { + $m->shouldReceive('handle')->andReturn('newsletter'); + }); + + $invalidator = new Invalidator($cacher, [ + 'forms' => [ + 'newsletter' => [ + 'urls' => [ + '/one', + '/two', + 'http://localhost/three', + ], + ], + ], + ]); + + $this->assertNull($invalidator->invalidate($form)); + } + + #[Test] + public function form_urls_can_be_invalidated_in_a_multisite() + { + $this->setSites([ + 'en' => ['url' => 'http://test.com', 'locale' => 'en_US'], + 'fr' => ['url' => 'http://test.fr', 'locale' => 'fr_FR'], + ]); + + $cacher = tap(Mockery::mock(Cacher::class), function ($cacher) { + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/one')->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/two')->once(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.fr/one')->once(); + $cacher->shouldReceive('invalidateUrl')->with('http://test.fr/two')->once(); + + $cacher->shouldReceive('invalidateUrl')->with('http://test.com/three')->once(); }); $form = tap(Mockery::mock(Form::class), function ($m) { @@ -251,6 +727,7 @@ public function form_urls_can_be_invalidated() 'urls' => [ '/one', '/two', + 'http://test.com/three', ], ], ],