From 1b86071ba68ef47c6a9480ef5130fbb04c111bba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:17:25 +0000 Subject: [PATCH 1/3] Initial plan From 43ef518fa8d55038fa5ee2b485c0e939e1cc5759 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:25:33 +0000 Subject: [PATCH 2/3] feat: add configurable statamic static cache invalidation support Co-authored-by: eminos <1682784+eminos@users.noreply.github.com> --- README.md | 6 ++ config/cloudflare-cache.php | 4 ++ src/CloudflareCacheServiceProvider.php | 8 +++ src/Listeners/PurgeCloudflareCache.php | 19 +++++ tests/Feature/PurgeCacheListenerTest.php | 91 ++++++++++++++++++++++++ 5 files changed, 128 insertions(+) diff --git a/README.md b/README.md index 8e3bfed..a0cac7c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Automatically purge your Cloudflare cache when content changes in Statamic. - Automatically purges Cloudflare cache when Statamic content changes. - **Multi-zone support** for Statamic multisite installations with different domains. - Configurable events that trigger cache purging. +- Optional support for Statamic `static_caching` invalidation rules via `UrlInvalidated`/`StaticCacheCleared` events. - Optional queuing of purge jobs for background processing. - CLI command for manual cache purging. - Simple configuration with backward compatibility. @@ -136,8 +137,13 @@ return [ 'nav_tree_saved' => true, 'global_set_saved' => true, 'global_set_deleted' => true, + 'url_invalidated' => true, + 'static_cache_cleared' => true, ], + // Use Statamic static cache invalidation events instead of direct content events + 'use_statamic_static_cache_invalidation' => env('CLOUDFLARE_CACHE_USE_STATAMIC_STATIC_CACHE_INVALIDATION', false), + // Dispatch purge jobs to the queue instead of running synchronously 'queue_purge' => env('CLOUDFLARE_CACHE_QUEUE_PURGE', false), diff --git a/config/cloudflare-cache.php b/config/cloudflare-cache.php index 3e58d8a..be47129 100644 --- a/config/cloudflare-cache.php +++ b/config/cloudflare-cache.php @@ -47,8 +47,12 @@ 'nav_tree_saved' => true, 'global_set_saved' => true, 'global_set_deleted' => true, + 'url_invalidated' => true, + 'static_cache_cleared' => true, ], + 'use_statamic_static_cache_invalidation' => env('CLOUDFLARE_CACHE_USE_STATAMIC_STATIC_CACHE_INVALIDATION', false), + 'queue_purge' => env('CLOUDFLARE_CACHE_QUEUE_PURGE', false), // Dispatch purge jobs to the queue /* diff --git a/src/CloudflareCacheServiceProvider.php b/src/CloudflareCacheServiceProvider.php index 3764d23..b58c765 100644 --- a/src/CloudflareCacheServiceProvider.php +++ b/src/CloudflareCacheServiceProvider.php @@ -15,6 +15,8 @@ use Statamic\Events\NavTreeSaved; use Statamic\Events\GlobalSetSaved; use Statamic\Events\GlobalSetDeleted; +use Statamic\Events\UrlInvalidated; +use Statamic\Events\StaticCacheCleared; class CloudflareCacheServiceProvider extends AddonServiceProvider { @@ -53,6 +55,12 @@ class CloudflareCacheServiceProvider extends AddonServiceProvider GlobalSetDeleted::class => [ PurgeCloudflareCache::class, ], + UrlInvalidated::class => [ + PurgeCloudflareCache::class, + ], + StaticCacheCleared::class => [ + PurgeCloudflareCache::class, + ], ]; /** diff --git a/src/Listeners/PurgeCloudflareCache.php b/src/Listeners/PurgeCloudflareCache.php index c3f1a8d..ac8faaf 100644 --- a/src/Listeners/PurgeCloudflareCache.php +++ b/src/Listeners/PurgeCloudflareCache.php @@ -16,6 +16,8 @@ use Statamic\Events\NavTreeSaved; use Statamic\Events\GlobalSetSaved; use Statamic\Events\GlobalSetDeleted; +use Statamic\Events\UrlInvalidated; +use Statamic\Events\StaticCacheCleared; use Statamic\Facades\URL; use Illuminate\Support\Facades\Log; // Already present, but good to confirm @@ -108,6 +110,17 @@ protected function purgeSynchronously(array $urls): void */ protected function shouldHandleEvent(Event $event): bool { + $useStatamicInvalidation = config('cloudflare-cache.use_statamic_static_cache_invalidation', false); + $isStaticCacheInvalidationEvent = $event instanceof UrlInvalidated || $event instanceof StaticCacheCleared; + + if ($useStatamicInvalidation && !$isStaticCacheInvalidationEvent) { + return false; + } + + if (!$useStatamicInvalidation && $isStaticCacheInvalidationEvent) { + return false; + } + $eventClass = get_class($event); $eventMap = [ 'Statamic\Events\EntrySaved' => 'entry_saved', @@ -120,6 +133,8 @@ protected function shouldHandleEvent(Event $event): bool 'Statamic\Events\NavTreeSaved' => 'nav_tree_saved', 'Statamic\Events\GlobalSetSaved' => 'global_set_saved', 'Statamic\Events\GlobalSetDeleted' => 'global_set_deleted', + 'Statamic\Events\UrlInvalidated' => 'url_invalidated', + 'Statamic\Events\StaticCacheCleared' => 'static_cache_cleared', ]; $configKey = $eventMap[$eventClass] ?? null; @@ -186,6 +201,10 @@ protected function getUrlsToPurge(Event $event): array // existing purge_everything_fallback logic (sync or queued). } + if ($event instanceof UrlInvalidated && $event->url) { + $urls[] = URL::makeAbsolute($event->url); + } + $urls = array_filter($urls); return array_unique($urls); diff --git a/tests/Feature/PurgeCacheListenerTest.php b/tests/Feature/PurgeCacheListenerTest.php index cb8a1ac..c370d6b 100644 --- a/tests/Feature/PurgeCacheListenerTest.php +++ b/tests/Feature/PurgeCacheListenerTest.php @@ -8,6 +8,8 @@ use Statamic\Events\EntrySaved; use Statamic\Events\GlobalSetSaved; use Statamic\Events\GlobalSetDeleted; +use Statamic\Events\UrlInvalidated; +use Statamic\Events\StaticCacheCleared; use Statamic\Contracts\Entries\Entry; use Statamic\Contracts\Entries\Collection; use Illuminate\Support\Facades\Event; @@ -355,4 +357,93 @@ public function it_fires_cache_purged_event_after_synchronous_purge_everything() return $e->purgedEverything && empty($e->urls); }); } + + #[Test] + public function it_ignores_legacy_events_when_statamic_static_cache_invalidation_is_enabled() + { + Event::fake([CachePurged::class]); + config([ + 'cloudflare-cache.use_statamic_static_cache_invalidation' => true, + 'cloudflare-cache.queue_purge' => false, + ]); + + $entry = $this->mockEntry(); + $event = new EntrySaved($entry); + + $clientMock = $this->mock(Client::class); + $clientMock->shouldNotReceive('purgeUrls'); + $clientMock->shouldNotReceive('purgeEverything'); + + $listener = $this->app->make(PurgeCloudflareCache::class); + $listener->handle($event); + + Queue::assertNothingPushed(); + Event::assertNotDispatched(CachePurged::class); + } + + #[Test] + public function it_purges_invalidated_urls_when_statamic_static_cache_invalidation_is_enabled() + { + Event::fake([CachePurged::class]); + config([ + 'cloudflare-cache.use_statamic_static_cache_invalidation' => true, + 'cloudflare-cache.queue_purge' => false, + ]); + + $event = new UrlInvalidated('/test-entry', 'https://example.com'); + + $clientMock = $this->mock(Client::class); + $clientMock->shouldReceive('purgeUrls')->once()->with(['https://example.com/test-entry']); + $clientMock->shouldNotReceive('purgeEverything'); + + $listener = $this->app->make(PurgeCloudflareCache::class); + $listener->handle($event); + + Queue::assertNothingPushed(); + Event::assertDispatched(CachePurged::class, function ($e) { + return !$e->purgedEverything && $e->urls === ['https://example.com/test-entry']; + }); + } + + #[Test] + public function it_purges_everything_when_static_cache_is_cleared_and_statamic_static_cache_invalidation_is_enabled() + { + config([ + 'cloudflare-cache.use_statamic_static_cache_invalidation' => true, + 'cloudflare-cache.queue_purge' => false, + 'cloudflare-cache.purge_everything_fallback' => true, + ]); + + $event = new StaticCacheCleared(); + + $clientMock = $this->mock(Client::class); + $clientMock->shouldReceive('purgeEverything')->once(); + $clientMock->shouldNotReceive('purgeUrls'); + + $listener = $this->app->make(PurgeCloudflareCache::class); + $listener->handle($event); + + Queue::assertNothingPushed(); + } + + #[Test] + public function it_does_not_purge_when_static_cache_is_cleared_and_fallback_is_disabled() + { + config([ + 'cloudflare-cache.use_statamic_static_cache_invalidation' => true, + 'cloudflare-cache.queue_purge' => false, + 'cloudflare-cache.purge_everything_fallback' => false, + ]); + + $event = new StaticCacheCleared(); + + $clientMock = $this->mock(Client::class); + $clientMock->shouldNotReceive('purgeEverything'); + $clientMock->shouldNotReceive('purgeUrls'); + + $listener = $this->app->make(PurgeCloudflareCache::class); + $listener->handle($event); + + Queue::assertNothingPushed(); + } } From 5d80754e35f2b0b9a6a901c27b5ff8987ba11941 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:03:21 +0000 Subject: [PATCH 3/3] docs: clarify static cache mode and add debug logging coverage Co-authored-by: eminos <1682784+eminos@users.noreply.github.com> --- README.md | 12 ++++-- config/cloudflare-cache.php | 6 ++- src/Listeners/PurgeCloudflareCache.php | 37 ++++++++++++++++- tests/Feature/PurgeCacheListenerTest.php | 52 ++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a0cac7c..211cf6a 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,11 @@ Once configured, the addon will automatically purge the Cloudflare cache when co You can configure which events trigger cache purging in the config file. +#### Event handling modes (either/or) + +- **Default (`use_statamic_static_cache_invalidation = false`)**: uses this addon’s own content events (`entry_*`, `term_*`, `asset_*`, etc.). +- **Statamic static cache mode (`use_statamic_static_cache_invalidation = true`)**: uses Statamic Static Cache events (`url_invalidated`, `static_cache_cleared`) and skips the addon’s legacy content events. + #### Queued Purging If you prefer to handle cache purging in the background to avoid potential delays during web requests, you can enable queued purging. Set the `CLOUDFLARE_CACHE_QUEUE_PURGE` environment variable to `true` or set `'queue_purge' => true` in the configuration file. @@ -137,11 +142,12 @@ return [ 'nav_tree_saved' => true, 'global_set_saved' => true, 'global_set_deleted' => true, - 'url_invalidated' => true, - 'static_cache_cleared' => true, + 'url_invalidated' => true, // Statamic Static Cache: UrlInvalidated event + 'static_cache_cleared' => true, // Statamic Static Cache: StaticCacheCleared event ], - // Use Statamic static cache invalidation events instead of direct content events + // When enabled, only Statamic Static Cache events are handled (UrlInvalidated/StaticCacheCleared). + // Legacy addon content events are skipped in this mode. 'use_statamic_static_cache_invalidation' => env('CLOUDFLARE_CACHE_USE_STATAMIC_STATIC_CACHE_INVALIDATION', false), // Dispatch purge jobs to the queue instead of running synchronously diff --git a/config/cloudflare-cache.php b/config/cloudflare-cache.php index be47129..12e8b59 100644 --- a/config/cloudflare-cache.php +++ b/config/cloudflare-cache.php @@ -47,10 +47,12 @@ 'nav_tree_saved' => true, 'global_set_saved' => true, 'global_set_deleted' => true, - 'url_invalidated' => true, - 'static_cache_cleared' => true, + 'url_invalidated' => true, // Statamic Static Cache: UrlInvalidated event + 'static_cache_cleared' => true, // Statamic Static Cache: StaticCacheCleared event ], + // When enabled, only Statamic Static Cache events are handled (UrlInvalidated/StaticCacheCleared). + // Legacy addon content events are skipped in this mode. 'use_statamic_static_cache_invalidation' => env('CLOUDFLARE_CACHE_USE_STATAMIC_STATIC_CACHE_INVALIDATION', false), 'queue_purge' => env('CLOUDFLARE_CACHE_QUEUE_PURGE', false), // Dispatch purge jobs to the queue diff --git a/src/Listeners/PurgeCloudflareCache.php b/src/Listeners/PurgeCloudflareCache.php index ac8faaf..c080faa 100644 --- a/src/Listeners/PurgeCloudflareCache.php +++ b/src/Listeners/PurgeCloudflareCache.php @@ -33,6 +33,11 @@ public function __construct(Client $client) public function handle(Event $event): void { if (!config('cloudflare-cache.enabled')) { + if (config('cloudflare-cache.debug')) { + Log::debug('[Cloudflare Cache] Skipping purge because addon is disabled.', [ + 'event' => get_class($event), + ]); + } return; } @@ -112,16 +117,26 @@ protected function shouldHandleEvent(Event $event): bool { $useStatamicInvalidation = config('cloudflare-cache.use_statamic_static_cache_invalidation', false); $isStaticCacheInvalidationEvent = $event instanceof UrlInvalidated || $event instanceof StaticCacheCleared; + $eventClass = get_class($event); if ($useStatamicInvalidation && !$isStaticCacheInvalidationEvent) { + if (config('cloudflare-cache.debug')) { + Log::debug('[Cloudflare Cache] Skipping event because Statamic static cache invalidation mode is enabled and this is a legacy addon event.', [ + 'event' => $eventClass, + ]); + } return false; } if (!$useStatamicInvalidation && $isStaticCacheInvalidationEvent) { + if (config('cloudflare-cache.debug')) { + Log::debug('[Cloudflare Cache] Skipping event because Statamic static cache invalidation mode is disabled.', [ + 'event' => $eventClass, + ]); + } return false; } - $eventClass = get_class($event); $eventMap = [ 'Statamic\Events\EntrySaved' => 'entry_saved', 'Statamic\Events\EntryDeleted' => 'entry_deleted', @@ -139,7 +154,25 @@ protected function shouldHandleEvent(Event $event): bool $configKey = $eventMap[$eventClass] ?? null; - return $configKey && config("cloudflare-cache.purge_on.{$configKey}"); + if (!$configKey) { + if (config('cloudflare-cache.debug')) { + Log::debug('[Cloudflare Cache] Skipping event because no purge_on mapping exists.', [ + 'event' => $eventClass, + ]); + } + return false; + } + + $shouldHandle = (bool) config("cloudflare-cache.purge_on.{$configKey}"); + + if (!$shouldHandle && config('cloudflare-cache.debug')) { + Log::debug('[Cloudflare Cache] Skipping event because purge_on setting is disabled.', [ + 'event' => $eventClass, + 'config_key' => "cloudflare-cache.purge_on.{$configKey}", + ]); + } + + return $shouldHandle; } /** diff --git a/tests/Feature/PurgeCacheListenerTest.php b/tests/Feature/PurgeCacheListenerTest.php index c370d6b..e1779be 100644 --- a/tests/Feature/PurgeCacheListenerTest.php +++ b/tests/Feature/PurgeCacheListenerTest.php @@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Queue; // Add Queue facade +use Illuminate\Support\Facades\Log; use Eminos\StatamicCloudflareCache\Events\CachePurged; use Eminos\StatamicCloudflareCache\Jobs\PurgeCloudflareCacheJob; use Mockery; @@ -446,4 +447,55 @@ public function it_does_not_purge_when_static_cache_is_cleared_and_fallback_is_d Queue::assertNothingPushed(); } + + #[Test] + public function it_logs_when_legacy_event_is_skipped_in_statamic_static_cache_invalidation_mode() + { + config([ + 'cloudflare-cache.use_statamic_static_cache_invalidation' => true, + 'cloudflare-cache.debug' => true, + ]); + + Log::shouldReceive('debug')->once()->withArgs(function ($message, $context = []) { + return str_contains($message, 'Statamic static cache invalidation mode is enabled') + && ($context['event'] ?? null) === EntrySaved::class; + }); + + $entry = $this->mockEntry(); + $event = new EntrySaved($entry); + + $clientMock = $this->mock(Client::class); + $clientMock->shouldNotReceive('purgeUrls'); + $clientMock->shouldNotReceive('purgeEverything'); + + $listener = $this->app->make(PurgeCloudflareCache::class); + $listener->handle($event); + + Queue::assertNothingPushed(); + } + + #[Test] + public function it_logs_when_static_cache_event_is_skipped_while_mode_is_disabled() + { + config([ + 'cloudflare-cache.use_statamic_static_cache_invalidation' => false, + 'cloudflare-cache.debug' => true, + ]); + + Log::shouldReceive('debug')->once()->withArgs(function ($message, $context = []) { + return str_contains($message, 'Statamic static cache invalidation mode is disabled') + && ($context['event'] ?? null) === UrlInvalidated::class; + }); + + $event = new UrlInvalidated('/test-entry', 'https://example.com'); + + $clientMock = $this->mock(Client::class); + $clientMock->shouldNotReceive('purgeUrls'); + $clientMock->shouldNotReceive('purgeEverything'); + + $listener = $this->app->make(PurgeCloudflareCache::class); + $listener->handle($event); + + Queue::assertNothingPushed(); + } }