Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b25d6b
Fix invalidation for entries
duncanmcclean Aug 20, 2024
4d6e9fd
Listen to Variables events instead
duncanmcclean Aug 20, 2024
8be3573
Fix invalidation for globals
duncanmcclean Aug 20, 2024
6da4564
just make sure this never happens
duncanmcclean Aug 20, 2024
f97d01e
Fix styling
duncanmcclean Aug 20, 2024
bff8d03
Fix invalidation for collections
duncanmcclean Aug 21, 2024
abdb511
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Aug 22, 2024
8bf8f55
Listen to LocalizedTerm events instead
duncanmcclean Aug 21, 2024
90b4ac9
Add multisite test
duncanmcclean Aug 21, 2024
97d9d5a
Ensure the right URLs are being cleared
duncanmcclean Aug 21, 2024
5b739be
Fix styling
duncanmcclean Aug 22, 2024
19dac6c
Handle asset invalidation
duncanmcclean Aug 26, 2024
45b48ab
Handle form invalidation
duncanmcclean Aug 26, 2024
6dc9d94
Handle absolute URLs better when invalidating collections
duncanmcclean Aug 26, 2024
7b20816
Handle nav invalidation
duncanmcclean Aug 26, 2024
c6ab7ab
Refactor some things.
duncanmcclean Aug 26, 2024
3a1402c
Add separate tests to cover multisite.
duncanmcclean Aug 26, 2024
fbbaf44
Fix styling
duncanmcclean Aug 26, 2024
f3e3fa0
Handle collection tree invalidation.
duncanmcclean Aug 26, 2024
1cd62eb
Typehints.
duncanmcclean Aug 26, 2024
5056d2b
Fix styling
duncanmcclean Aug 26, 2024
006f60c
wip
duncanmcclean Aug 27, 2024
e74562d
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Mar 26, 2025
70ddd16
avoid return typehints
duncanmcclean Mar 31, 2025
26ef887
combine urls in a single array and call `invalidateUrls`
duncanmcclean Mar 31, 2025
e27fd5b
remove `splitUrlAndDomain`, it is no longer used
duncanmcclean Mar 31, 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
210 changes: 161 additions & 49 deletions src/StaticCaching/DefaultInvalidator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
{
Expand All@@ -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) {
Expand All@@ -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']);
}
}
22 changes: 11 additions & 11 deletions src/StaticCaching/Invalidate.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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',
Expand DownExpand Up@@ -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)
Expand All@@ -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)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[5.x] Fix static caching invalidation for multi-sites by duncanmcclean · Pull Request #10669 · statamic/cms · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b25d6b
Fix invalidation for entries
duncanmcclean Aug 20, 2024
4d6e9fd
Listen to Variables events instead
duncanmcclean Aug 20, 2024
8be3573
Fix invalidation for globals
duncanmcclean Aug 20, 2024
6da4564
just make sure this never happens
duncanmcclean Aug 20, 2024
f97d01e
Fix styling
duncanmcclean Aug 20, 2024
bff8d03
Fix invalidation for collections
duncanmcclean Aug 21, 2024
abdb511
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Aug 22, 2024
8bf8f55
Listen to LocalizedTerm events instead
duncanmcclean Aug 21, 2024
90b4ac9
Add multisite test
duncanmcclean Aug 21, 2024
97d9d5a
Ensure the right URLs are being cleared
duncanmcclean Aug 21, 2024
5b739be
Fix styling
duncanmcclean Aug 22, 2024
19dac6c
Handle asset invalidation
duncanmcclean Aug 26, 2024
45b48ab
Handle form invalidation
duncanmcclean Aug 26, 2024
6dc9d94
Handle absolute URLs better when invalidating collections
duncanmcclean Aug 26, 2024
7b20816
Handle nav invalidation
duncanmcclean Aug 26, 2024
c6ab7ab
Refactor some things.
duncanmcclean Aug 26, 2024
3a1402c
Add separate tests to cover multisite.
duncanmcclean Aug 26, 2024
fbbaf44
Fix styling
duncanmcclean Aug 26, 2024
f3e3fa0
Handle collection tree invalidation.
duncanmcclean Aug 26, 2024
1cd62eb
Typehints.
duncanmcclean Aug 26, 2024
5056d2b
Fix styling
duncanmcclean Aug 26, 2024
006f60c
wip
duncanmcclean Aug 27, 2024
e74562d
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Mar 26, 2025
70ddd16
avoid return typehints
duncanmcclean Mar 31, 2025
26ef887
combine urls in a single array and call `invalidateUrls`
duncanmcclean Mar 31, 2025
e27fd5b
remove `splitUrlAndDomain`, it is no longer used
duncanmcclean Mar 31, 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
210 changes: 161 additions & 49 deletions src/StaticCaching/DefaultInvalidator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
{
Expand All@@ -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) {
Expand All@@ -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']);
}
}
22 changes: 11 additions & 11 deletions src/StaticCaching/Invalidate.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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',
Expand DownExpand Up@@ -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)
Expand All@@ -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)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [5.x] Fix static caching invalidation for multi-sites by duncanmcclean · Pull Request #10669 · statamic/cms · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b25d6b
Fix invalidation for entries
duncanmcclean Aug 20, 2024
4d6e9fd
Listen to Variables events instead
duncanmcclean Aug 20, 2024
8be3573
Fix invalidation for globals
duncanmcclean Aug 20, 2024
6da4564
just make sure this never happens
duncanmcclean Aug 20, 2024
f97d01e
Fix styling
duncanmcclean Aug 20, 2024
bff8d03
Fix invalidation for collections
duncanmcclean Aug 21, 2024
abdb511
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Aug 22, 2024
8bf8f55
Listen to LocalizedTerm events instead
duncanmcclean Aug 21, 2024
90b4ac9
Add multisite test
duncanmcclean Aug 21, 2024
97d9d5a
Ensure the right URLs are being cleared
duncanmcclean Aug 21, 2024
5b739be
Fix styling
duncanmcclean Aug 22, 2024
19dac6c
Handle asset invalidation
duncanmcclean Aug 26, 2024
45b48ab
Handle form invalidation
duncanmcclean Aug 26, 2024
6dc9d94
Handle absolute URLs better when invalidating collections
duncanmcclean Aug 26, 2024
7b20816
Handle nav invalidation
duncanmcclean Aug 26, 2024
c6ab7ab
Refactor some things.
duncanmcclean Aug 26, 2024
3a1402c
Add separate tests to cover multisite.
duncanmcclean Aug 26, 2024
fbbaf44
Fix styling
duncanmcclean Aug 26, 2024
f3e3fa0
Handle collection tree invalidation.
duncanmcclean Aug 26, 2024
1cd62eb
Typehints.
duncanmcclean Aug 26, 2024
5056d2b
Fix styling
duncanmcclean Aug 26, 2024
006f60c
wip
duncanmcclean Aug 27, 2024
e74562d
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Mar 26, 2025
70ddd16
avoid return typehints
duncanmcclean Mar 31, 2025
26ef887
combine urls in a single array and call `invalidateUrls`
duncanmcclean Mar 31, 2025
e27fd5b
remove `splitUrlAndDomain`, it is no longer used
duncanmcclean Mar 31, 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
210 changes: 161 additions & 49 deletions src/StaticCaching/DefaultInvalidator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
{
Expand All@@ -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) {
Expand All@@ -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']);
}
}
22 changes: 11 additions & 11 deletions src/StaticCaching/Invalidate.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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',
Expand DownExpand Up@@ -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)
Expand All@@ -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)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [5.x] Fix static caching invalidation for multi-sites by duncanmcclean · Pull Request #10669 · statamic/cms · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b25d6b
Fix invalidation for entries
duncanmcclean Aug 20, 2024
4d6e9fd
Listen to Variables events instead
duncanmcclean Aug 20, 2024
8be3573
Fix invalidation for globals
duncanmcclean Aug 20, 2024
6da4564
just make sure this never happens
duncanmcclean Aug 20, 2024
f97d01e
Fix styling
duncanmcclean Aug 20, 2024
bff8d03
Fix invalidation for collections
duncanmcclean Aug 21, 2024
abdb511
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Aug 22, 2024
8bf8f55
Listen to LocalizedTerm events instead
duncanmcclean Aug 21, 2024
90b4ac9
Add multisite test
duncanmcclean Aug 21, 2024
97d9d5a
Ensure the right URLs are being cleared
duncanmcclean Aug 21, 2024
5b739be
Fix styling
duncanmcclean Aug 22, 2024
19dac6c
Handle asset invalidation
duncanmcclean Aug 26, 2024
45b48ab
Handle form invalidation
duncanmcclean Aug 26, 2024
6dc9d94
Handle absolute URLs better when invalidating collections
duncanmcclean Aug 26, 2024
7b20816
Handle nav invalidation
duncanmcclean Aug 26, 2024
c6ab7ab
Refactor some things.
duncanmcclean Aug 26, 2024
3a1402c
Add separate tests to cover multisite.
duncanmcclean Aug 26, 2024
fbbaf44
Fix styling
duncanmcclean Aug 26, 2024
f3e3fa0
Handle collection tree invalidation.
duncanmcclean Aug 26, 2024
1cd62eb
Typehints.
duncanmcclean Aug 26, 2024
5056d2b
Fix styling
duncanmcclean Aug 26, 2024
006f60c
wip
duncanmcclean Aug 27, 2024
e74562d
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Mar 26, 2025
70ddd16
avoid return typehints
duncanmcclean Mar 31, 2025
26ef887
combine urls in a single array and call `invalidateUrls`
duncanmcclean Mar 31, 2025
e27fd5b
remove `splitUrlAndDomain`, it is no longer used
duncanmcclean Mar 31, 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
210 changes: 161 additions & 49 deletions src/StaticCaching/DefaultInvalidator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
{
Expand All@@ -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) {
Expand All@@ -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']);
}
}
22 changes: 11 additions & 11 deletions src/StaticCaching/Invalidate.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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',
Expand DownExpand Up@@ -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)
Expand All@@ -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)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [5.x] Fix static caching invalidation for multi-sites by duncanmcclean · Pull Request #10669 · statamic/cms · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b25d6b
Fix invalidation for entries
duncanmcclean Aug 20, 2024
4d6e9fd
Listen to Variables events instead
duncanmcclean Aug 20, 2024
8be3573
Fix invalidation for globals
duncanmcclean Aug 20, 2024
6da4564
just make sure this never happens
duncanmcclean Aug 20, 2024
f97d01e
Fix styling
duncanmcclean Aug 20, 2024
bff8d03
Fix invalidation for collections
duncanmcclean Aug 21, 2024
abdb511
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Aug 22, 2024
8bf8f55
Listen to LocalizedTerm events instead
duncanmcclean Aug 21, 2024
90b4ac9
Add multisite test
duncanmcclean Aug 21, 2024
97d9d5a
Ensure the right URLs are being cleared
duncanmcclean Aug 21, 2024
5b739be
Fix styling
duncanmcclean Aug 22, 2024
19dac6c
Handle asset invalidation
duncanmcclean Aug 26, 2024
45b48ab
Handle form invalidation
duncanmcclean Aug 26, 2024
6dc9d94
Handle absolute URLs better when invalidating collections
duncanmcclean Aug 26, 2024
7b20816
Handle nav invalidation
duncanmcclean Aug 26, 2024
c6ab7ab
Refactor some things.
duncanmcclean Aug 26, 2024
3a1402c
Add separate tests to cover multisite.
duncanmcclean Aug 26, 2024
fbbaf44
Fix styling
duncanmcclean Aug 26, 2024
f3e3fa0
Handle collection tree invalidation.
duncanmcclean Aug 26, 2024
1cd62eb
Typehints.
duncanmcclean Aug 26, 2024
5056d2b
Fix styling
duncanmcclean Aug 26, 2024
006f60c
wip
duncanmcclean Aug 27, 2024
e74562d
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Mar 26, 2025
70ddd16
avoid return typehints
duncanmcclean Mar 31, 2025
26ef887
combine urls in a single array and call `invalidateUrls`
duncanmcclean Mar 31, 2025
e27fd5b
remove `splitUrlAndDomain`, it is no longer used
duncanmcclean Mar 31, 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
210 changes: 161 additions & 49 deletions src/StaticCaching/DefaultInvalidator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
{
Expand All@@ -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) {
Expand All@@ -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']);
}
}
22 changes: 11 additions & 11 deletions src/StaticCaching/Invalidate.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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',
Expand DownExpand Up@@ -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)
Expand All@@ -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)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [5.x] Fix static caching invalidation for multi-sites by duncanmcclean · Pull Request #10669 · statamic/cms · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b25d6b
Fix invalidation for entries
duncanmcclean Aug 20, 2024
4d6e9fd
Listen to Variables events instead
duncanmcclean Aug 20, 2024
8be3573
Fix invalidation for globals
duncanmcclean Aug 20, 2024
6da4564
just make sure this never happens
duncanmcclean Aug 20, 2024
f97d01e
Fix styling
duncanmcclean Aug 20, 2024
bff8d03
Fix invalidation for collections
duncanmcclean Aug 21, 2024
abdb511
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Aug 22, 2024
8bf8f55
Listen to LocalizedTerm events instead
duncanmcclean Aug 21, 2024
90b4ac9
Add multisite test
duncanmcclean Aug 21, 2024
97d9d5a
Ensure the right URLs are being cleared
duncanmcclean Aug 21, 2024
5b739be
Fix styling
duncanmcclean Aug 22, 2024
19dac6c
Handle asset invalidation
duncanmcclean Aug 26, 2024
45b48ab
Handle form invalidation
duncanmcclean Aug 26, 2024
6dc9d94
Handle absolute URLs better when invalidating collections
duncanmcclean Aug 26, 2024
7b20816
Handle nav invalidation
duncanmcclean Aug 26, 2024
c6ab7ab
Refactor some things.
duncanmcclean Aug 26, 2024
3a1402c
Add separate tests to cover multisite.
duncanmcclean Aug 26, 2024
fbbaf44
Fix styling
duncanmcclean Aug 26, 2024
f3e3fa0
Handle collection tree invalidation.
duncanmcclean Aug 26, 2024
1cd62eb
Typehints.
duncanmcclean Aug 26, 2024
5056d2b
Fix styling
duncanmcclean Aug 26, 2024
006f60c
wip
duncanmcclean Aug 27, 2024
e74562d
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Mar 26, 2025
70ddd16
avoid return typehints
duncanmcclean Mar 31, 2025
26ef887
combine urls in a single array and call `invalidateUrls`
duncanmcclean Mar 31, 2025
e27fd5b
remove `splitUrlAndDomain`, it is no longer used
duncanmcclean Mar 31, 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
210 changes: 161 additions & 49 deletions src/StaticCaching/DefaultInvalidator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
{
Expand All@@ -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) {
Expand All@@ -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']);
}
}
22 changes: 11 additions & 11 deletions src/StaticCaching/Invalidate.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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',
Expand DownExpand Up@@ -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)
Expand All@@ -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)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [5.x] Fix static caching invalidation for multi-sites by duncanmcclean · Pull Request #10669 · statamic/cms · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b25d6b
Fix invalidation for entries
duncanmcclean Aug 20, 2024
4d6e9fd
Listen to Variables events instead
duncanmcclean Aug 20, 2024
8be3573
Fix invalidation for globals
duncanmcclean Aug 20, 2024
6da4564
just make sure this never happens
duncanmcclean Aug 20, 2024
f97d01e
Fix styling
duncanmcclean Aug 20, 2024
bff8d03
Fix invalidation for collections
duncanmcclean Aug 21, 2024
abdb511
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Aug 22, 2024
8bf8f55
Listen to LocalizedTerm events instead
duncanmcclean Aug 21, 2024
90b4ac9
Add multisite test
duncanmcclean Aug 21, 2024
97d9d5a
Ensure the right URLs are being cleared
duncanmcclean Aug 21, 2024
5b739be
Fix styling
duncanmcclean Aug 22, 2024
19dac6c
Handle asset invalidation
duncanmcclean Aug 26, 2024
45b48ab
Handle form invalidation
duncanmcclean Aug 26, 2024
6dc9d94
Handle absolute URLs better when invalidating collections
duncanmcclean Aug 26, 2024
7b20816
Handle nav invalidation
duncanmcclean Aug 26, 2024
c6ab7ab
Refactor some things.
duncanmcclean Aug 26, 2024
3a1402c
Add separate tests to cover multisite.
duncanmcclean Aug 26, 2024
fbbaf44
Fix styling
duncanmcclean Aug 26, 2024
f3e3fa0
Handle collection tree invalidation.
duncanmcclean Aug 26, 2024
1cd62eb
Typehints.
duncanmcclean Aug 26, 2024
5056d2b
Fix styling
duncanmcclean Aug 26, 2024
006f60c
wip
duncanmcclean Aug 27, 2024
e74562d
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Mar 26, 2025
70ddd16
avoid return typehints
duncanmcclean Mar 31, 2025
26ef887
combine urls in a single array and call `invalidateUrls`
duncanmcclean Mar 31, 2025
e27fd5b
remove `splitUrlAndDomain`, it is no longer used
duncanmcclean Mar 31, 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
210 changes: 161 additions & 49 deletions src/StaticCaching/DefaultInvalidator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
{
Expand All@@ -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) {
Expand All@@ -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']);
}
}
22 changes: 11 additions & 11 deletions src/StaticCaching/Invalidate.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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',
Expand DownExpand Up@@ -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)
Expand All@@ -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)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [5.x] Fix static caching invalidation for multi-sites by duncanmcclean · Pull Request #10669 · statamic/cms · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4b25d6b
Fix invalidation for entries
duncanmcclean Aug 20, 2024
4d6e9fd
Listen to Variables events instead
duncanmcclean Aug 20, 2024
8be3573
Fix invalidation for globals
duncanmcclean Aug 20, 2024
6da4564
just make sure this never happens
duncanmcclean Aug 20, 2024
f97d01e
Fix styling
duncanmcclean Aug 20, 2024
bff8d03
Fix invalidation for collections
duncanmcclean Aug 21, 2024
abdb511
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Aug 22, 2024
8bf8f55
Listen to LocalizedTerm events instead
duncanmcclean Aug 21, 2024
90b4ac9
Add multisite test
duncanmcclean Aug 21, 2024
97d9d5a
Ensure the right URLs are being cleared
duncanmcclean Aug 21, 2024
5b739be
Fix styling
duncanmcclean Aug 22, 2024
19dac6c
Handle asset invalidation
duncanmcclean Aug 26, 2024
45b48ab
Handle form invalidation
duncanmcclean Aug 26, 2024
6dc9d94
Handle absolute URLs better when invalidating collections
duncanmcclean Aug 26, 2024
7b20816
Handle nav invalidation
duncanmcclean Aug 26, 2024
c6ab7ab
Refactor some things.
duncanmcclean Aug 26, 2024
3a1402c
Add separate tests to cover multisite.
duncanmcclean Aug 26, 2024
fbbaf44
Fix styling
duncanmcclean Aug 26, 2024
f3e3fa0
Handle collection tree invalidation.
duncanmcclean Aug 26, 2024
1cd62eb
Typehints.
duncanmcclean Aug 26, 2024
5056d2b
Fix styling
duncanmcclean Aug 26, 2024
006f60c
wip
duncanmcclean Aug 27, 2024
e74562d
Merge remote-tracking branch 'origin/5.x' into fix/static-caching-inv…
duncanmcclean Mar 26, 2025
70ddd16
avoid return typehints
duncanmcclean Mar 31, 2025
26ef887
combine urls in a single array and call `invalidateUrls`
duncanmcclean Mar 31, 2025
e27fd5b
remove `splitUrlAndDomain`, it is no longer used
duncanmcclean Mar 31, 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
210 changes: 161 additions & 49 deletions src/StaticCaching/DefaultInvalidator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
{
Expand All@@ -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) {
Expand All@@ -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']);
}
}
22 changes: 11 additions & 11 deletions src/StaticCaching/Invalidate.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand All@@ -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',
Expand DownExpand Up@@ -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)
Expand All@@ -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)
Expand Down
Loading