66
77/**
88 * Handles Smart Cache events and consolidates them before triggering webhooks
9+ *
10+ * This is a lightweight handler that simply consolidates multiple cache purge
11+ * events into single webhook triggers to avoid webhook spam.
912 */
1013class SmartCacheEventHandler {
1114 /**
@@ -62,58 +65,13 @@ public function init() {
6265 * @param string $graphql_endpoint GraphQL endpoint URL
6366 */
6467 public function handle_graphql_purge ( $ key , $ event , $ graphql_endpoint ) {
65- $ parsed = $ this ->parse_event ( $ event );
66- if ( ! $ parsed ) {
67- return ;
68- }
69-
70- $ this ->buffer_event ( $ key , $ parsed ['post_type ' ], $ parsed ['action ' ], $ graphql_endpoint );
71- }
72-
73- /**
74- * Handle cache purge nodes event
75- *
76- * @param string $key Cache key
77- * @param array $nodes Nodes being purged
78- */
79- public function handle_cache_purge_nodes ( $ key , $ nodes ) {
80- $ payload = [
81- 'cache_key ' => $ key ,
82- 'nodes ' => $ nodes ,
83- 'nodes_count ' => count ( $ nodes ),
84- 'timestamp ' => current_time ( 'c ' ),
85- ];
86-
87- call_user_func ( $ this ->webhook_trigger_callback , 'smart_cache_nodes_purged ' , $ payload );
88- }
89-
90- /**
91- * Parse event string into components
92- *
93- * @param string $event Event string (e.g., post_UPDATE)
94- * @return array|null Array with 'post_type' and 'action' keys, or null if invalid
95- */
96- private function parse_event ( string $ event ): ?array {
9768 $ parts = explode ( '_ ' , $ event );
9869 if ( count ( $ parts ) !== 2 ) {
99- return null ;
70+ return ;
10071 }
10172
102- return [
103- 'post_type ' => $ parts [0 ],
104- 'action ' => strtolower ( $ parts [1 ] ),
105- ];
106- }
107-
108- /**
109- * Buffer an event for consolidated processing
110- *
111- * @param string $key Cache key
112- * @param string $post_type Post type
113- * @param string $action Action (create, update, delete)
114- * @param string $graphql_endpoint GraphQL endpoint URL
115- */
116- private function buffer_event ( string $ key , string $ post_type , string $ action , string $ graphql_endpoint ) {
73+ $ post_type = $ parts [0 ];
74+ $ action = strtolower ( $ parts [1 ] );
11775 $ buffer_key = "{$ post_type }_ {$ action }" ;
11876
11977 if ( ! isset ( $ this ->buffer [ $ buffer_key ] ) ) {
@@ -122,194 +80,34 @@ private function buffer_event( string $key, string $post_type, string $action, s
12280 'action ' => $ action ,
12381 'graphql_endpoint ' => $ graphql_endpoint ,
12482 'keys ' => [],
125- 'objects ' => [],
12683 ];
12784 }
12885
129- $ key_info = $ this ->analyze_cache_key ( $ key );
130-
131- // Extract object information if it's a Relay ID
132- if ( $ key_info ['type ' ] === 'relay_id ' && isset ( $ key_info ['decoded ' ] ) ) {
133- $ this ->add_object_to_buffer ( $ buffer_key , $ key_info ['decoded ' ], $ action );
134- }
135-
136- $ this ->buffer [ $ buffer_key ]['keys ' ][] = $ key_info ;
137- $ this ->schedule_processing ();
138- }
139-
140- /**
141- * Analyze a cache key and determine its type
142- *
143- * @param string $key Cache key
144- * @return array Key information with 'key', 'type', and optionally 'decoded'
145- */
146- private function analyze_cache_key ( string $ key ): array {
147- $ info = [
148- 'key ' => $ key ,
149- 'type ' => $ this ->classify_key_type ( $ key ),
150- ];
86+ // Just store the key - let webhook consumers decode if needed
87+ $ this ->buffer [ $ buffer_key ]['keys ' ][] = $ key ;
15188
152- // Try to decode Relay IDs
153- if ( $ info ['type ' ] === 'relay_id ' && class_exists ( Relay::class ) ) {
154- try {
155- $ decoded = Relay::fromGlobalId ( $ key );
156- if ( ! empty ( $ decoded ['type ' ] ) && ! empty ( $ decoded ['id ' ] ) ) {
157- $ info ['decoded ' ] = $ decoded ;
158- }
159- } catch ( \Exception $ e ) {
160- // Not a valid Relay ID after all
161- $ info ['type ' ] = 'unknown ' ;
162- }
89+ // Schedule processing if not already scheduled
90+ if ( $ this ->timer === false ) {
91+ $ this ->timer = wp_schedule_single_event ( time () + 1 , 'wpgraphql_webhooks_process_smart_cache ' );
92+ add_action ( 'wpgraphql_webhooks_process_smart_cache ' , [ $ this , 'process_buffer ' ] );
16393 }
164-
165- return $ info ;
16694 }
16795
16896 /**
169- * Classify the type of cache key
97+ * Handle cache purge nodes event - this fires immediately
17098 *
17199 * @param string $key Cache key
172- * @return string Key type: 'list', 'skipped', 'relay_id', or 'unknown'
173- */
174- private function classify_key_type ( string $ key ): string {
175- if ( strpos ( $ key , 'list: ' ) === 0 ) {
176- return 'list ' ;
177- }
178-
179- if ( strpos ( $ key , 'skipped: ' ) === 0 ) {
180- return 'skipped ' ;
181- }
182-
183- // Assume it might be a Relay ID if it looks like base64
184- if ( preg_match ( '/^[A-Za-z0-9+\/]+=*$/ ' , $ key ) ) {
185- return 'relay_id ' ;
186- }
187-
188- return 'unknown ' ;
189- }
190-
191- /**
192- * Add object data to buffer
193- *
194- * @param string $buffer_key Buffer key
195- * @param array $decoded Decoded Relay ID data
196- * @param string $action Action being performed
197- */
198- private function add_object_to_buffer ( string $ buffer_key , array $ decoded , string $ action ) {
199- $ object_key = "{$ decoded ['type ' ]}: {$ decoded ['id ' ]}" ;
200-
201- if ( isset ( $ this ->buffer [ $ buffer_key ]['objects ' ][ $ object_key ] ) ) {
202- return ; // Already added
203- }
204-
205- $ object_data = $ this ->fetch_object_data ( $ decoded ['type ' ], (int ) $ decoded ['id ' ], $ action );
206- if ( $ object_data ) {
207- $ this ->buffer [ $ buffer_key ]['objects ' ][ $ object_key ] = $ object_data ;
208- }
209- }
210-
211- /**
212- * Fetch object data based on type and ID
213- *
214- * @param string $type Object type
215- * @param int $id Object ID
216- * @param string $action The action being performed
217- * @return array|null Object data or null if not found
218- */
219- private function fetch_object_data ( string $ type , int $ id , string $ action ): ?array {
220- // For delete actions, just return minimal data
221- if ( $ action === 'delete ' ) {
222- return [
223- 'id ' => $ id ,
224- 'type ' => $ type ,
225- 'deleted ' => true ,
226- ];
227- }
228-
229- $ fetchers = [
230- 'post ' => [ $ this , 'fetch_post_data ' ],
231- 'term ' => [ $ this , 'fetch_term_data ' ],
232- 'user ' => [ $ this , 'fetch_user_data ' ],
233- ];
234-
235- if ( isset ( $ fetchers [ $ type ] ) ) {
236- return call_user_func ( $ fetchers [ $ type ], $ id );
237- }
238-
239- return null ;
240- }
241-
242- /**
243- * Fetch post data
244- *
245- * @param int $id Post ID
246- * @return array|null
247- */
248- private function fetch_post_data ( int $ id ): ?array {
249- $ post = get_post ( $ id );
250- if ( ! $ post ) {
251- return null ;
252- }
253-
254- return [
255- 'id ' => $ post ->ID ,
256- 'title ' => $ post ->post_title ,
257- 'status ' => $ post ->post_status ,
258- 'type ' => $ post ->post_type ,
259- 'url ' => get_permalink ( $ post ),
260- ];
261- }
262-
263- /**
264- * Fetch term data
265- *
266- * @param int $id Term ID
267- * @return array|null
268- */
269- private function fetch_term_data ( int $ id ): ?array {
270- $ term = get_term ( $ id );
271- if ( ! $ term || is_wp_error ( $ term ) ) {
272- return null ;
273- }
274-
275- return [
276- 'id ' => $ term ->term_id ,
277- 'name ' => $ term ->name ,
278- 'taxonomy ' => $ term ->taxonomy ,
279- 'url ' => get_term_link ( $ term ),
280- ];
281- }
282-
283- /**
284- * Fetch user data
285- *
286- * @param int $id User ID
287- * @return array|null
100+ * @param array $nodes Nodes being purged
288101 */
289- private function fetch_user_data ( int $ id ): ?array {
290- $ user = get_user_by ( 'id ' , $ id );
291- if ( ! $ user ) {
292- return null ;
293- }
294-
295- return [
296- 'id ' => $ user ->ID ,
297- 'login ' => $ user ->user_login ,
298- 'display_name ' => $ user ->display_name ,
299- 'url ' => get_author_posts_url ( $ user ->ID ),
102+ public function handle_cache_purge_nodes ( $ key , $ nodes ) {
103+ // This event provides the actual nodes being purged, so we can fire immediately
104+ $ payload = [
105+ 'cache_key ' => $ key ,
106+ 'nodes ' => $ nodes ,
107+ 'timestamp ' => current_time ( 'c ' ),
300108 ];
301- }
302109
303- /**
304- * Schedule buffer processing
305- */
306- private function schedule_processing () {
307- if ( $ this ->timer !== false ) {
308- return ; // Already scheduled
309- }
310-
311- $ this ->timer = wp_schedule_single_event ( time () + 1 , 'wpgraphql_webhooks_process_smart_cache ' );
312- add_action ( 'wpgraphql_webhooks_process_smart_cache ' , [ $ this , 'process_buffer ' ] );
110+ call_user_func ( $ this ->webhook_trigger_callback , 'smart_cache_nodes_purged ' , $ payload );
313111 }
314112
315113 /**
@@ -326,45 +124,23 @@ public function process_buffer() {
326124 continue ;
327125 }
328126
329- $ payload = $ this ->build_payload ( $ data );
127+ // Simple payload with just the essential information
128+ $ payload = [
129+ 'post_type ' => $ data ['post_type ' ],
130+ 'action ' => $ data ['action ' ],
131+ 'graphql_endpoint ' => $ data ['graphql_endpoint ' ],
132+ 'cache_keys ' => array_unique ( $ data ['keys ' ] ), // Remove duplicates
133+ 'cache_keys_count ' => count ( array_unique ( $ data ['keys ' ] ) ),
134+ 'timestamp ' => current_time ( 'c ' ),
135+ ];
136+
137+ // Let webhook consumers decode the keys if they need to
138+ // They already have access to WPGraphQL and can use Relay::fromGlobalId()
139+
330140 call_user_func ( $ this ->webhook_trigger_callback , $ webhook_event , $ payload );
331141 }
332142
333143 $ this ->buffer = [];
334144 $ this ->timer = false ;
335145 }
336-
337- /**
338- * Build webhook payload from buffered data
339- *
340- * @param array $data Buffered event data
341- * @return array
342- */
343- private function build_payload ( array $ data ): array {
344- return [
345- 'post_type ' => $ data ['post_type ' ],
346- 'action ' => $ data ['action ' ],
347- 'graphql_endpoint ' => $ data ['graphql_endpoint ' ],
348- 'timestamp ' => current_time ( 'c ' ),
349- 'cache_keys_purged ' => count ( $ data ['keys ' ] ),
350- 'objects_affected ' => array_values ( $ data ['objects ' ] ),
351- 'cache_key_summary ' => $ this ->summarize_keys ( $ data ['keys ' ] ),
352- ];
353- }
354-
355- /**
356- * Summarize cache keys by type
357- *
358- * @param array $keys Array of key information
359- * @return array Summary with counts by type
360- */
361- private function summarize_keys ( array $ keys ): array {
362- $ summary = array_fill_keys ( [ 'relay_id ' , 'list ' , 'skipped ' , 'unknown ' ], 0 );
363-
364- foreach ( $ keys as $ key_info ) {
365- $ summary [ $ key_info ['type ' ] ]++;
366- }
367-
368- return array_filter ( $ summary ); // Remove zeros
369- }
370146}
0 commit comments