diff --git a/src/StaticCaching/DefaultInvalidator.php b/src/StaticCaching/DefaultInvalidator.php index 00ffdce6511..4425a88f3cb 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 { @@ -25,19 +29,25 @@ public function __construct(Cacher $cacher, $rules = []) public function invalidate($item) { 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) { @@ -47,80 +57,182 @@ public function invalidate($item) protected function invalidateFormUrls($form) { - $this->cacher->invalidateUrls( - Arr::get($this->rules, "forms.{$form->handle()}.urls") - ); + $rules = collect(Arr::get($this->rules, "forms.{$form->handle()}.urls")); + + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); + + $prefixedRelativeUrls = Site::all()->map(function ($site) use ($rules) { + return $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($site->url(), '/').Str::ensureLeft($rule, '/')); + })->flatten()->all(); + + $this->cacher->invalidateUrls([ + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); } protected function invalidateAssetUrls($asset) { - $this->cacher->invalidateUrls( - Arr::get($this->rules, "assets.{$asset->container()->handle()}.urls") - ); + $rules = collect(Arr::get($this->rules, "assets.{$asset->container()->handle()}.urls")); + + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); + + $prefixedRelativeUrls = Site::all()->map(function ($site) use ($rules) { + return $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($site->url(), '/').Str::ensureLeft($rule, '/')); + })->flatten()->all(); + + $this->cacher->invalidateUrls([ + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); } protected function invalidateEntryUrls($entry) { - $entry->descendants()->merge([$entry])->each(function ($entry) { - if (! $entry->isRedirect() && $url = $entry->absoluteUrl()) { - $this->cacher->invalidateUrl(...$this->splitUrlAndDomain($url)); - } - }); - - $this->cacher->invalidateUrls( - Arr::get($this->rules, "collections.{$entry->collectionHandle()}.urls") - ); + $rules = collect(Arr::get($this->rules, "collections.{$entry->collectionHandle()}.urls")); + + $urls = $entry->descendants() + ->merge([$entry]) + ->reject(fn ($entry) => $entry->isRedirect()) + ->map->absoluteUrl() + ->all(); + + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); + + $prefixedRelativeUrls = $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($entry->site()->url(), '/').Str::ensureLeft($rule, '/')) + ->all(); + + $this->cacher->invalidateUrls([ + ...$urls, + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); } protected function invalidateTermUrls($term) { - if ($url = $term->absoluteUrl()) { - $this->cacher->invalidateUrl(...$this->splitUrlAndDomain($url)); + $rules = collect(Arr::get($this->rules, "taxonomies.{$term->taxonomyHandle()}.urls")); - $term->taxonomy()->collections()->each(function ($collection) use ($term) { - if ($url = $term->collection($collection)->absoluteUrl()) { - $this->cacher->invalidateUrl(...$this->splitUrlAndDomain($url)); - } - }); + if ($url = $term->absoluteUrl()) { + $urls = $term->taxonomy()->collections() + ->map(fn ($collection) => $term->collection($collection)->absoluteUrl()) + ->filter() + ->prepend($url) + ->all(); } - $this->cacher->invalidateUrls( - Arr::get($this->rules, "taxonomies.{$term->taxonomyHandle()}.urls") - ); + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); + + $prefixedRelativeUrls = $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($term->site()->url(), '/').Str::ensureLeft($rule, '/')) + ->all(); + + $this->cacher->invalidateUrls([ + ...$urls ?? [], + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); } protected function invalidateNavUrls($nav) { - $this->cacher->invalidateUrls( - Arr::get($this->rules, "navigation.{$nav->handle()}.urls") - ); + $rules = collect(Arr::get($this->rules, "navigation.{$nav->handle()}.urls")); + + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); + + $prefixedRelativeUrls = $nav->sites()->map(function ($site) use ($rules) { + return $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight(Site::get($site)->url(), '/').Str::ensureLeft($rule, '/')); + })->flatten()->all(); + + $this->cacher->invalidateUrls([ + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); + } + + protected function invalidateNavTreeUrls($tree) + { + $rules = collect(Arr::get($this->rules, "navigation.{$tree->structure()->handle()}.urls")); + + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); + + $prefixedRelativeUrls = $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($tree->site()->url(), '/').Str::ensureLeft($rule, '/')) + ->all(); + + $this->cacher->invalidateUrls([ + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); } - protected function invalidateGlobalUrls($set) + protected function invalidateGlobalUrls($variables) { - $this->cacher->invalidateUrls( - Arr::get($this->rules, "globals.{$set->handle()}.urls") - ); + $rules = collect(Arr::get($this->rules, "globals.{$variables->globalSet()->handle()}.urls")); + + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); + + $prefixedRelativeUrls = $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($variables->site()->url(), '/').Str::ensureLeft($rule, '/')) + ->all(); + + $this->cacher->invalidateUrls([ + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); } protected function invalidateCollectionUrls($collection) { - if ($url = $collection->absoluteUrl()) { - $this->cacher->invalidateUrl(...$this->splitUrlAndDomain($url)); - } + $rules = collect(Arr::get($this->rules, "collections.{$collection->handle()}.urls")); - $this->cacher->invalidateUrls( - Arr::get($this->rules, "collections.{$collection->handle()}.urls") - ); + $urls = $collection->sites()->map(fn ($site) => $collection->absoluteUrl($site))->filter()->all(); + + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); + + $prefixedRelativeUrls = $collection->sites()->map(function ($site) use ($rules) { + return $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight(Site::get($site)->url(), '/').Str::ensureLeft($rule, '/')); + })->flatten()->all(); + + $this->cacher->invalidateUrls([ + ...$urls, + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); } - private function splitUrlAndDomain(string $url) + protected function invalidateCollectionTreeUrls($tree) { - $parsed = parse_url($url); + $rules = collect(Arr::get($this->rules, "collections.{$tree->collection()->handle()}.urls")); + + $absoluteUrls = $rules->filter(fn (string $rule) => $this->isAbsoluteUrl($rule))->all(); - return [ - Arr::get($parsed, 'path', '/'), - $parsed['scheme'].'://'.$parsed['host'], - ]; + $prefixedRelativeUrls = $rules + ->reject(fn (string $rule) => $this->isAbsoluteUrl($rule)) + ->map(fn (string $rule) => Str::removeRight($tree->site()->url(), '/').Str::ensureLeft($rule, '/')) + ->all(); + + $this->cacher->invalidateUrls([ + ...$absoluteUrls, + ...$prefixedRelativeUrls, + ]); + } + + private function isAbsoluteUrl(string $url) + { + return isset(parse_url($url)['scheme']); } } diff --git a/src/StaticCaching/Invalidate.php b/src/StaticCaching/Invalidate.php index c12ae792181..b1fc01177e1 100644 --- a/src/StaticCaching/Invalidate.php +++ b/src/StaticCaching/Invalidate.php @@ -14,14 +14,14 @@ use Statamic\Events\EntryScheduleReached; 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 @@ -34,10 +34,10 @@ class Invalidate implements ShouldQueue EntrySaved::class => 'invalidateEntry', EntryDeleting::class => 'invalidateEntry', EntryScheduleReached::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', @@ -79,7 +79,7 @@ public function invalidateTerm($event) public function invalidateGlobalSet($event) { - $this->invalidator->invalidate($event->globals); + $this->invalidator->invalidate($event->variables); } public function invalidateNav($event) @@ -94,12 +94,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 79571d41b97..5e794fbdd24 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,52 @@ 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('invalidateUrls')->with([ + 'http://localhost/page/three', + 'http://localhost/page/one', + 'http://localhost/page/two', + ])->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('invalidateUrls')->with([ + 'http://test.com/page/three', + 'http://test.com/page/one', + 'http://test.com/page/two', + 'http://test.fr/page/one', + 'http://test.fr/page/two', + ])->once(); }); $container = tap(Mockery::mock(AssetContainer::class), function ($m) { @@ -55,6 +102,7 @@ public function assets_can_trigger_url_invalidation() 'urls' => [ '/page/one', '/page/two', + 'http://test.com/page/three', ], ], ], @@ -67,13 +115,18 @@ public function assets_can_trigger_url_invalidation() public function collection_urls_can_be_invalidated() { $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://localhost/my/test/collection', + 'http://localhost/blog/three', + 'http://localhost/blog/one', + 'http://localhost/blog/two', + ])->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'])); + $m->shouldReceive('absoluteUrl')->with('en')->andReturn('http://localhost/my/test/collection'); }); $invalidator = new Invalidator($cacher, [ @@ -82,6 +135,7 @@ public function collection_urls_can_be_invalidated() 'urls' => [ '/blog/one', '/blog/two', + 'http://localhost/blog/three', ], ], ], @@ -90,12 +144,148 @@ public function collection_urls_can_be_invalidated() $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('invalidateUrls')->with([ + 'http://test.com/my/test/collection', + 'http://test.fr/my/test/collection', + 'http://test.com/blog/three', + 'http://test.com/blog/one', + 'http://test.com/blog/two', + 'http://test.fr/blog/one', + 'http://test.fr/blog/two', + ])->once(); + }); + + $collection = tap(Mockery::mock(Collection::class), function ($m) { + $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, [ + 'collections' => [ + 'blog' => [ + 'urls' => [ + '/blog/one', + '/blog/two', + 'http://test.com/blog/three', + ], + ], + ], + ]); + + $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/three', + 'http://localhost/blog/one', + 'http://localhost/blog/two', + ])->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://localhost/blog/three', + 'http://test.fr/blog/one', + 'http://test.fr/blog/two', + ])->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://test.com/my/test/entry', + 'http://localhost/blog/three', + 'http://localhost/blog/one', + 'http://localhost/blog/two', + ])->once(); }); $entry = tap(Mockery::mock(Entry::class), function ($m) { @@ -103,6 +293,7 @@ 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, [ @@ -111,6 +302,47 @@ public function collection_urls_can_be_invalidated_by_an_entry() '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('invalidateUrls')->with([ + 'http://test.fr/my/test/entry', + 'http://test.com/blog/three', + 'http://test.fr/blog/one', + 'http://test.fr/blog/two', + ])->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, [ + 'collections' => [ + 'blog' => [ + 'urls' => [ + '/blog/one', + '/blog/two', + 'http://test.com/blog/three', ], ], ], @@ -123,8 +355,10 @@ public function collection_urls_can_be_invalidated_by_an_entry() 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 +366,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 +387,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('invalidateUrls')->with([ + 'http://localhost/my/test/term', + 'http://localhost/my/collection/tags/term', + 'http://localhost/tags/three', + 'http://localhost/tags/one', + 'http://localhost/tags/two', + ])->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('invalidateUrls')->with([ + 'http://test.fr/my/test/term', + 'http://test.fr/my/collection/tags/term', + 'http://test.com/tags/three', + 'http://test.fr/tags/one', + 'http://test.fr/tags/two', + ])->once(); }); $collection = Mockery::mock(Collection::class); @@ -163,11 +452,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 +469,29 @@ 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/three', + 'http://localhost/one', + 'http://localhost/two', + ])->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 +500,7 @@ public function navigation_urls_can_be_invalidated() 'urls' => [ '/one', '/two', + 'http://localhost/three', ], ], ], @@ -209,36 +509,241 @@ 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/three', + 'http://test.com/one', + 'http://test.com/two', + 'http://test.fr/one', + 'http://test.fr/two', + ])->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/three', + 'http://localhost/one', + 'http://localhost/two', + ])->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.com/three', + 'http://test.fr/one', + 'http://test.fr/two', + ])->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/three', + 'http://localhost/one', + 'http://localhost/two', + ])->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($set)); + $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/three', + 'http://test.fr/one', + 'http://test.fr/two', + ])->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($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('invalidateUrls')->with([ + 'http://localhost/three', + 'http://localhost/one', + 'http://localhost/two', + ])->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('invalidateUrls')->with([ + 'http://test.com/three', + 'http://test.com/one', + 'http://test.com/two', + 'http://test.fr/one', + 'http://test.fr/two', + ])->once(); }); $form = tap(Mockery::mock(Form::class), function ($m) { @@ -251,6 +756,7 @@ public function form_urls_can_be_invalidated() 'urls' => [ '/one', '/two', + 'http://test.com/three', ], ], ],