2
2
/**
3
3
* Plugin Name: Next.js Revalidation
4
4
* Description: Revalidates Next.js site when WordPress content changes
5
- * Version: 1.0.3
5
+ * Version: 1.0.5
6
6
* Author: 9d8
7
7
* Author URI: https://9d8.dev
8
8
*/
15
15
class Next_Revalidation {
16
16
private $ options ;
17
17
private $ option_name = 'next_revalidation_settings ' ;
18
+ private $ last_revalidation = 0 ;
19
+ private $ revalidation_cooldown = 2 ; // seconds between revalidations
20
+ private $ history_option_name = 'next_revalidation_history ' ;
21
+ private $ max_history_items = 50 ;
18
22
19
23
public function __construct () {
20
24
// Initialize plugin
@@ -31,9 +35,12 @@ public function __construct() {
31
35
// Post save/update hooks - any post status change
32
36
add_action ('transition_post_status ' , array ($ this , 'on_post_status_change ' ), 10 , 3 );
33
37
add_action ('save_post ' , array ($ this , 'on_content_change ' ), 10 , 3 );
34
- add_action ('wp_trash_post ' , array ($ this , 'on_post_trash ' ), 10 );
35
- add_action ('untrash_post ' , array ($ this , 'on_post_untrash ' ), 10 );
36
- add_action ('before_delete_post ' , array ($ this , 'on_post_delete ' ), 10 );
38
+
39
+ // Trash and delete hooks with higher priority to ensure they run
40
+ add_action ('trashed_post ' , array ($ this , 'on_post_trash ' ), 5 );
41
+ add_action ('untrashed_post ' , array ($ this , 'on_post_untrash ' ), 5 );
42
+ add_action ('delete_post ' , array ($ this , 'on_post_delete ' ), 5 );
43
+ add_action ('after_delete_post ' , array ($ this , 'on_post_delete ' ), 5 );
37
44
38
45
// Term changes
39
46
add_action ('created_term ' , array ($ this , 'on_term_change ' ), 10 , 3 );
@@ -49,6 +56,11 @@ public function __construct() {
49
56
add_action ('add_attachment ' , array ($ this , 'on_media_change ' ), 10 );
50
57
add_action ('edit_attachment ' , array ($ this , 'on_media_change ' ), 10 );
51
58
add_action ('delete_attachment ' , array ($ this , 'on_media_change ' ), 10 );
59
+
60
+ // Menu changes
61
+ add_action ('wp_update_nav_menu ' , array ($ this , 'on_menu_change ' ), 10 );
62
+ add_action ('wp_create_nav_menu ' , array ($ this , 'on_menu_change ' ), 10 );
63
+ add_action ('wp_delete_nav_menu ' , array ($ this , 'on_menu_change ' ), 10 );
52
64
}
53
65
54
66
public function init () {
@@ -92,6 +104,14 @@ public function register_settings() {
92
104
'next-revalidation-settings ' ,
93
105
'next_revalidation_section '
94
106
);
107
+
108
+ add_settings_field (
109
+ 'revalidation_cooldown ' ,
110
+ 'Revalidation Cooldown ' ,
111
+ array ($ this , 'revalidation_cooldown_callback ' ),
112
+ 'next-revalidation-settings ' ,
113
+ 'next_revalidation_section '
114
+ );
95
115
}
96
116
97
117
public function sanitize_settings ($ input ) {
@@ -111,6 +131,11 @@ public function sanitize_settings($input) {
111
131
$ new_input ['enable_notifications ' ] = (bool )$ input ['enable_notifications ' ];
112
132
}
113
133
134
+ if (isset ($ input ['revalidation_cooldown ' ])) {
135
+ $ cooldown = intval ($ input ['revalidation_cooldown ' ]);
136
+ $ new_input ['revalidation_cooldown ' ] = max (0 , min (60 , $ cooldown )); // Between 0 and 60 seconds
137
+ }
138
+
114
139
return $ new_input ;
115
140
}
116
141
@@ -135,14 +160,40 @@ public function enable_notifications_callback() {
135
160
echo '<input type="checkbox" id="enable_notifications" name=" ' . $ this ->option_name . '[enable_notifications]" ' . checked ($ value , true , false ) . ' /> ' ;
136
161
echo '<label for="enable_notifications">Show admin notifications for revalidation events</label> ' ;
137
162
}
163
+
164
+ public function revalidation_cooldown_callback () {
165
+ $ value = isset ($ this ->options ['revalidation_cooldown ' ]) ? intval ($ this ->options ['revalidation_cooldown ' ]) : 2 ;
166
+ echo '<input type="number" min="0" max="60" id="revalidation_cooldown" name=" ' . $ this ->option_name . '[revalidation_cooldown]" value=" ' . $ value . '" class="small-text" /> ' ;
167
+ echo '<p class="description">Minimum seconds between revalidation requests (0-60). Use higher values for busy sites.</p> ' ;
168
+ }
138
169
139
170
public function add_admin_menu () {
140
- add_options_page (
141
- 'Next.js Revalidation ' ,
171
+ add_menu_page (
142
172
'Next.js Revalidation ' ,
173
+ 'Next.js ' ,
143
174
'manage_options ' ,
144
175
'next-revalidation-settings ' ,
145
- array ($ this , 'admin_page_content ' )
176
+ array ($ this , 'admin_page_content ' ),
177
+ 'dashicons-update ' , // WordPress dashicon
178
+ 100 // Position in menu
179
+ );
180
+
181
+ // Add submenu pages
182
+ add_submenu_page (
183
+ 'next-revalidation-settings ' ,
184
+ 'Settings ' ,
185
+ 'Settings ' ,
186
+ 'manage_options ' ,
187
+ 'next-revalidation-settings '
188
+ );
189
+
190
+ add_submenu_page (
191
+ 'next-revalidation-settings ' ,
192
+ 'Revalidation History ' ,
193
+ 'History ' ,
194
+ 'manage_options ' ,
195
+ 'next-revalidation-history ' ,
196
+ array ($ this , 'history_page_content ' )
146
197
);
147
198
}
148
199
@@ -194,6 +245,82 @@ public function admin_page_content() {
194
245
<?php
195
246
}
196
247
248
+ public function history_page_content () {
249
+ // Get revalidation history
250
+ $ history = get_option ($ this ->history_option_name , array ());
251
+
252
+ ?>
253
+ <div class="wrap">
254
+ <h1><?php echo esc_html (get_admin_page_title ()); ?> </h1>
255
+
256
+ <div class="tablenav top">
257
+ <div class="alignleft actions">
258
+ <form method="post" action="">
259
+ <?php wp_nonce_field ('clear_revalidation_history ' , 'clear_history_nonce ' ); ?>
260
+ <input type="hidden" name="action" value="clear_history">
261
+ <input type="submit" class="button" value="Clear History" onclick="return confirm('Are you sure you want to clear the revalidation history?');">
262
+ </form>
263
+ </div>
264
+ <br class="clear">
265
+ </div>
266
+
267
+ <table class="wp-list-table widefat fixed striped">
268
+ <thead>
269
+ <tr>
270
+ <th>Time</th>
271
+ <th>Content Type</th>
272
+ <th>Content ID</th>
273
+ <th>Status</th>
274
+ <th>Response</th>
275
+ </tr>
276
+ </thead>
277
+ <tbody>
278
+ <?php if (empty ($ history )): ?>
279
+ <tr>
280
+ <td colspan="5">No revalidation history found.</td>
281
+ </tr>
282
+ <?php else : ?>
283
+ <?php foreach ($ history as $ entry ): ?>
284
+ <tr>
285
+ <td><?php echo esc_html (date ('Y-m-d H:i:s ' , $ entry ['time ' ])); ?> </td>
286
+ <td><?php echo esc_html ($ entry ['content_type ' ]); ?> </td>
287
+ <td><?php echo isset ($ entry ['content_id ' ]) ? esc_html ($ entry ['content_id ' ]) : 'N/A ' ; ?> </td>
288
+ <td>
289
+ <?php if ($ entry ['success ' ]): ?>
290
+ <span style="color: green;">Success</span>
291
+ <?php else : ?>
292
+ <span style="color: red;">Failed</span>
293
+ <?php endif ; ?>
294
+ </td>
295
+ <td>
296
+ <?php
297
+ if (isset ($ entry ['response ' ])) {
298
+ echo esc_html (substr ($ entry ['response ' ], 0 , 50 ));
299
+ if (strlen ($ entry ['response ' ]) > 50 ) {
300
+ echo '... ' ;
301
+ }
302
+ } else {
303
+ echo 'N/A ' ;
304
+ }
305
+ ?>
306
+ </td>
307
+ </tr>
308
+ <?php endforeach ; ?>
309
+ <?php endif ; ?>
310
+ </tbody>
311
+ </table>
312
+ </div>
313
+ <?php
314
+
315
+ // Handle clear history action
316
+ if (isset ($ _POST ['action ' ]) && $ _POST ['action ' ] === 'clear_history ' ) {
317
+ if (check_admin_referer ('clear_revalidation_history ' , 'clear_history_nonce ' )) {
318
+ delete_option ($ this ->history_option_name );
319
+ echo '<script>window.location.reload();</script> ' ;
320
+ }
321
+ }
322
+ }
323
+
197
324
// AJAX action for manual revalidation
198
325
public function register_ajax_actions () {
199
326
add_action ('wp_ajax_manual_revalidation ' , array ($ this , 'handle_manual_revalidation ' ));
@@ -225,6 +352,7 @@ public function on_post_status_change($new_status, $old_status, $post) {
225
352
226
353
// If the status is changing, we should revalidate
227
354
if ($ new_status !== $ old_status ) {
355
+ error_log ("Next.js Revalidation: Post status changed from {$ old_status } to {$ new_status } for post {$ post ->ID }" );
228
356
$ this ->send_revalidation_request ($ post ->post_type , $ post ->ID );
229
357
}
230
358
}
@@ -240,22 +368,28 @@ public function on_content_change($post_id, $post = null, $update = null) {
240
368
$ post = get_post ($ post_id );
241
369
}
242
370
243
- // Revalidate regardless of post status
371
+ error_log ( " Next.js Revalidation: Content changed for post { $ post_id }" );
244
372
$ this ->send_revalidation_request ($ post ->post_type , $ post_id );
245
373
}
246
374
247
375
public function on_post_trash ($ post_id ) {
248
376
$ post_type = get_post_type ($ post_id );
377
+ error_log ("Next.js Revalidation: Post trashed {$ post_id } of type {$ post_type }" );
249
378
$ this ->send_revalidation_request ($ post_type , $ post_id );
250
379
}
251
380
252
381
public function on_post_untrash ($ post_id ) {
253
382
$ post_type = get_post_type ($ post_id );
383
+ error_log ("Next.js Revalidation: Post untrashed {$ post_id } of type {$ post_type }" );
254
384
$ this ->send_revalidation_request ($ post_type , $ post_id );
255
385
}
256
386
257
387
public function on_post_delete ($ post_id ) {
258
388
$ post_type = get_post_type ($ post_id );
389
+ if (!$ post_type ) {
390
+ $ post_type = 'unknown ' ; // Fallback if post type can't be determined
391
+ }
392
+ error_log ("Next.js Revalidation: Post deleted {$ post_id } of type {$ post_type }" );
259
393
$ this ->send_revalidation_request ($ post_type , $ post_id );
260
394
}
261
395
@@ -268,25 +402,67 @@ public function on_term_change($term_id, $tt_id, $taxonomy) {
268
402
$ content_type = 'tag ' ;
269
403
}
270
404
405
+ error_log ("Next.js Revalidation: Term changed {$ term_id } of type {$ content_type }" );
271
406
$ this ->send_revalidation_request ($ content_type , $ term_id );
272
407
}
273
408
274
409
public function on_user_change ($ user_id ) {
410
+ error_log ("Next.js Revalidation: User changed {$ user_id }" );
275
411
$ this ->send_revalidation_request ('author ' , $ user_id );
276
412
}
277
413
278
414
public function on_media_change ($ attachment_id ) {
415
+ error_log ("Next.js Revalidation: Media changed {$ attachment_id }" );
279
416
$ this ->send_revalidation_request ('media ' , $ attachment_id );
280
417
}
418
+
419
+ public function on_menu_change ($ menu_id ) {
420
+ error_log ("Next.js Revalidation: Menu changed {$ menu_id }" );
421
+ $ this ->send_revalidation_request ('menu ' , $ menu_id );
422
+ }
423
+
424
+ private function log_revalidation ($ content_type , $ content_id , $ success , $ response = '' ) {
425
+ $ history = get_option ($ this ->history_option_name , array ());
426
+
427
+ // Add new entry at the beginning
428
+ array_unshift ($ history , array (
429
+ 'time ' => time (),
430
+ 'content_type ' => $ content_type ,
431
+ 'content_id ' => $ content_id ,
432
+ 'success ' => $ success ,
433
+ 'response ' => $ response
434
+ ));
435
+
436
+ // Keep only the last X entries
437
+ if (count ($ history ) > $ this ->max_history_items ) {
438
+ $ history = array_slice ($ history , 0 , $ this ->max_history_items );
439
+ }
440
+
441
+ update_option ($ this ->history_option_name , $ history );
442
+ }
281
443
282
444
private function send_revalidation_request ($ content_type , $ content_id = null ) {
445
+ // Get cooldown from settings if available
446
+ $ cooldown = isset ($ this ->options ['revalidation_cooldown ' ]) ? intval ($ this ->options ['revalidation_cooldown ' ]) : $ this ->revalidation_cooldown ;
447
+
448
+ // Implement throttling to prevent too many requests
449
+ $ current_time = time ();
450
+ if ($ current_time - $ this ->last_revalidation < $ cooldown ) {
451
+ error_log ("Next.js Revalidation: Throttled request for {$ content_type } {$ content_id }" );
452
+ $ this ->log_revalidation ($ content_type , $ content_id , false , 'Throttled: Too many requests ' );
453
+ return false ;
454
+ }
455
+ $ this ->last_revalidation = $ current_time ;
456
+
283
457
// Check if we have the required settings
284
458
if (empty ($ this ->options ['next_url ' ]) || empty ($ this ->options ['webhook_secret ' ])) {
285
459
if (!empty ($ this ->options ['enable_notifications ' ])) {
286
460
add_action ('admin_notices ' , function () {
287
461
echo '<div class="notice notice-error is-dismissible"><p>Next.js revalidation failed: Missing URL or webhook secret. Please configure the plugin settings.</p></div> ' ;
288
462
});
289
463
}
464
+ error_log ("Next.js Revalidation: Missing URL or webhook secret " );
465
+ $ this ->log_revalidation ($ content_type , $ content_id , false , 'Missing URL or webhook secret ' );
290
466
return false ;
291
467
}
292
468
@@ -302,6 +478,8 @@ private function send_revalidation_request($content_type, $content_id = null) {
302
478
$ payload ['contentId ' ] = $ content_id ;
303
479
}
304
480
481
+ error_log ("Next.js Revalidation: Sending request to {$ endpoint } for {$ content_type } {$ content_id }" );
482
+
305
483
// Send revalidation request
306
484
$ response = wp_remote_post ($ endpoint , array (
307
485
'method ' => 'POST ' ,
@@ -318,33 +496,38 @@ private function send_revalidation_request($content_type, $content_id = null) {
318
496
319
497
// Check for success
320
498
if (is_wp_error ($ response )) {
321
- error_log ('Next.js revalidation error: ' . $ response ->get_error_message ());
499
+ $ error_message = $ response ->get_error_message ();
500
+ error_log ('Next.js revalidation error: ' . $ error_message );
322
501
if (!empty ($ this ->options ['enable_notifications ' ])) {
323
502
add_action ('admin_notices ' , function () use ($ response ) {
324
503
echo '<div class="notice notice-error is-dismissible"><p>Next.js revalidation failed: ' . esc_html ($ response ->get_error_message ()) . '</p></div> ' ;
325
504
});
326
505
}
506
+ $ this ->log_revalidation ($ content_type , $ content_id , false , $ error_message );
327
507
return false ;
328
508
}
329
509
330
510
$ status_code = wp_remote_retrieve_response_code ($ response );
511
+ $ body = wp_remote_retrieve_body ($ response );
331
512
$ success = $ status_code >= 200 && $ status_code < 300 ;
332
513
333
514
if ($ success ) {
515
+ error_log ("Next.js Revalidation: Success for {$ content_type } {$ content_id }" );
334
516
if (!empty ($ this ->options ['enable_notifications ' ])) {
335
517
add_action ('admin_notices ' , function () use ($ content_type , $ content_id ) {
336
518
echo '<div class="notice notice-success is-dismissible"><p>Next.js site revalidated successfully due to ' . esc_html ($ content_type ) . ' update ' . ($ content_id ? ' (ID: ' . esc_html ($ content_id ) . ') ' : '' ) . '</p></div> ' ;
337
519
});
338
520
}
521
+ $ this ->log_revalidation ($ content_type , $ content_id , true , $ body );
339
522
return true ;
340
523
} else {
341
- $ body = wp_remote_retrieve_body ($ response );
342
- error_log ('Next.js revalidation failed: ' . $ body );
524
+ error_log ("Next.js revalidation failed with status {$ status_code }: {$ body }" );
343
525
if (!empty ($ this ->options ['enable_notifications ' ])) {
344
526
add_action ('admin_notices ' , function () use ($ status_code , $ body ) {
345
527
echo '<div class="notice notice-error is-dismissible"><p>Next.js revalidation failed with status ' . esc_html ($ status_code ) . ': ' . esc_html ($ body ) . '</p></div> ' ;
346
528
});
347
529
}
530
+ $ this ->log_revalidation ($ content_type , $ content_id , false , "Status {$ status_code }: {$ body }" );
348
531
return false ;
349
532
}
350
533
}
0 commit comments