diff --git a/includes/Activator.php b/includes/Activator.php
index 72b0bad..856ead6 100644
--- a/includes/Activator.php
+++ b/includes/Activator.php
@@ -48,6 +48,13 @@ class WP_Recache_Activator {
'cache_enabled' => true,
'separate_mobile_cache' => false,
'exclude_urls' => ['/cart', '/checkout', '/my-account'],
+ 'rejected_cookies' => [
+ 'woocommerce_items_in_cart',
+ 'wp_woocommerce_session_*',
+ 'wp-postpass',
+ 'comment_author_*',
+ 'wordpress_*',
+ ],
'minify_css' => false,
'minify_js' => false,
'lazyload_images' => false,
@@ -64,8 +71,20 @@ class WP_Recache_Activator {
/**
* Write the standalone cache server required by the advanced-cache.php
* drop-in. Must run without WordPress loaded.
+ *
+ * Public so Admin can regenerate the drop-in when options change.
*/
- private static function create_cache_server() {
+ public static function create_cache_server() {
+ $rejected_cookies = get_option('wp_recache_rejected_cookies', [
+ 'woocommerce_items_in_cart',
+ 'wp_woocommerce_session_*',
+ 'wp-postpass',
+ 'comment_author_*',
+ 'wordpress_*',
+ ]);
+
+ $cookies_php = var_export($rejected_cookies, true);
+
$content = ' $value) {
- if (strpos($name, \'wordpress_logged_in\') === 0) {
- return;
+$rejected_cookies = ' . $cookies_php . ';
+
+foreach ($rejected_cookies as $cookie) {
+ $cookie = trim($cookie);
+ if ($cookie === \'\') {
+ continue;
+ }
+
+ if (substr($cookie, -1) === \'*\') {
+ $prefix = substr($cookie, 0, -1);
+ foreach ($_COOKIE as $name => $value) {
+ if (strpos($name, $prefix) === 0) {
+ return;
+ }
+ }
+ } else {
+ if (isset($_COOKIE[$cookie])) {
+ return;
+ }
}
}
diff --git a/includes/Admin/Admin.php b/includes/Admin/Admin.php
index 55273ac..f06a94f 100644
--- a/includes/Admin/Admin.php
+++ b/includes/Admin/Admin.php
@@ -22,6 +22,10 @@ class WP_Recache_Admin {
add_action('admin_enqueue_scripts', 'wp_recache_enqueue_admin_bar_script');
// New exclusions must not leave stale files that the drop-in would still serve.
add_action('update_option_wp_recache_exclude_urls', 'wp_recache_delete_all_cache');
+ add_action('update_option_wp_recache_rejected_cookies', function () {
+ WP_Recache_Activator::create_cache_server();
+ wp_recache_delete_all_cache();
+ });
}
/**
@@ -59,6 +63,18 @@ class WP_Recache_Admin {
'default' => ['/cart', '/checkout', '/my-account'],
]);
+ register_setting('wp_recache_settings', 'wp_recache_rejected_cookies', [
+ 'type' => 'array',
+ 'sanitize_callback' => [$this, 'sanitize_rejected_cookies'],
+ 'default' => [
+ 'woocommerce_items_in_cart',
+ 'wp_woocommerce_session_*',
+ 'wp-postpass',
+ 'comment_author_*',
+ 'wordpress_*',
+ ],
+ ]);
+
register_setting('wp_recache_settings', 'wp_recache_minify_css', [
'type' => 'boolean',
'sanitize_callback' => 'rest_sanitize_boolean',
@@ -100,6 +116,22 @@ class WP_Recache_Admin {
return array_map('sanitize_text_field', $lines);
}
+ /**
+ * Sanitize rejected cookies.
+ *
+ * @param mixed $input Input value.
+ * @return array Sanitized cookie names.
+ */
+ public function sanitize_rejected_cookies($input) {
+ if (is_array($input)) {
+ $input = implode("\n", $input);
+ }
+
+ $lines = array_filter(array_map('trim', explode("\n", (string) $input)));
+
+ return array_map('sanitize_text_field', $lines);
+ }
+
/**
* Render settings page.
*/
diff --git a/includes/Admin/views/settings.php b/includes/Admin/views/settings.php
index 21e2b35..21f45c5 100644
--- a/includes/Admin/views/settings.php
+++ b/includes/Admin/views/settings.php
@@ -52,6 +52,19 @@ $stats = WP_Recache_Admin::get_cache_stats();
One URL per line. These pages will not be cached.
+
+ | Rejected Cookies |
+
+
+ One cookie name per line. Visitors with these cookies bypass the cache. Use * as a suffix to match a prefix (e.g. comment_author_*).
+ |
+
Cache Statistics
diff --git a/includes/Common/Helpers.php b/includes/Common/Helpers.php
index 0696fa4..f67520a 100644
--- a/includes/Common/Helpers.php
+++ b/includes/Common/Helpers.php
@@ -58,6 +58,10 @@ function wp_recache_should_exclude() {
return true;
}
+ if (wp_recache_has_rejected_cookie()) {
+ return true;
+ }
+
// Dynamic query-string requests are never cached (story 11).
if (!empty($_SERVER['QUERY_STRING'])) {
return true;
@@ -83,6 +87,47 @@ function wp_recache_should_exclude() {
return apply_filters('wp_recache_exclude', false);
}
+/**
+ * Check if a rejected cookie is present in the request.
+ *
+ * @return bool True if any rejected cookie is set.
+ */
+function wp_recache_has_rejected_cookie() {
+ if (empty($_COOKIE)) {
+ return false;
+ }
+
+ $rejected = wp_recache_get_option('rejected_cookies', [
+ 'woocommerce_items_in_cart',
+ 'wp_woocommerce_session_*',
+ 'wp-postpass',
+ 'comment_author_*',
+ 'wordpress_*',
+ ]);
+
+ foreach ($rejected as $cookie) {
+ $cookie = trim($cookie);
+ if ($cookie === '') {
+ continue;
+ }
+
+ if (substr($cookie, -1) === '*') {
+ $prefix = substr($cookie, 0, -1);
+ foreach ($_COOKIE as $name => $value) {
+ if (strpos($name, $prefix) === 0) {
+ return true;
+ }
+ }
+ } else {
+ if (isset($_COOKIE[$cookie])) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
/**
* Check if request is for a robot/crawler.
*/
diff --git a/tests/test-admin.php b/tests/test-admin.php
index 6593600..d640c7d 100644
--- a/tests/test-admin.php
+++ b/tests/test-admin.php
@@ -52,4 +52,38 @@ class WP_Recache_Admin_Test extends WP_UnitTestCase {
$this->assertGreaterThanOrEqual(2, $stats['files']);
}
+
+ public function test_sanitize_rejected_cookies_trims_whitespace() {
+ $admin = new WP_Recache_Admin();
+ $result = $admin->sanitize_rejected_cookies(" wp-postpass \n comment_author_* ");
+ $this->assertEquals(['wp-postpass', 'comment_author_*'], $result);
+ }
+
+ public function test_sanitize_rejected_cookies_filters_empty_lines() {
+ $admin = new WP_Recache_Admin();
+ $result = $admin->sanitize_rejected_cookies("wp-postpass\n\n\ncomment_author_*\n");
+ $this->assertEquals(['wp-postpass', 'comment_author_*'], $result);
+ }
+
+ public function test_sanitize_rejected_cookies_handles_array_input() {
+ $admin = new WP_Recache_Admin();
+ $result = $admin->sanitize_rejected_cookies(['wp-postpass', 'comment_author_*']);
+ $this->assertEquals(['wp-postpass', 'comment_author_*'], $result);
+ }
+
+ public function test_rejected_cookies_option_is_registered() {
+ $admin = new WP_Recache_Admin();
+ $admin->init();
+ do_action('admin_init');
+
+ $default = [
+ 'woocommerce_items_in_cart',
+ 'wp_woocommerce_session_*',
+ 'wp-postpass',
+ 'comment_author_*',
+ 'wordpress_*',
+ ];
+
+ $this->assertEquals($default, get_option('wp_recache_rejected_cookies', $default));
+ }
}
diff --git a/tests/test-helpers.php b/tests/test-helpers.php
index d3cea89..916d07a 100644
--- a/tests/test-helpers.php
+++ b/tests/test-helpers.php
@@ -88,6 +88,48 @@ class WP_Recache_Helpers_Test extends WP_UnitTestCase {
$this->assertTrue(wp_recache_should_exclude());
}
+ public function test_wp_recache_has_rejected_cookie_returns_true_for_exact_match() {
+ wp_set_current_user(0);
+ update_option('wp_recache_rejected_cookies', ['wp-postpass']);
+ $_COOKIE = ['wp-postpass' => 'abc123'];
+ $this->assertTrue(wp_recache_has_rejected_cookie());
+ $_COOKIE = [];
+ }
+
+ public function test_wp_recache_has_rejected_cookie_returns_true_for_prefix_match() {
+ wp_set_current_user(0);
+ update_option('wp_recache_rejected_cookies', ['comment_author_*']);
+ $_COOKIE = ['comment_author_12345' => 'test'];
+ $this->assertTrue(wp_recache_has_rejected_cookie());
+ $_COOKIE = [];
+ }
+
+ public function test_wp_recache_has_rejected_cookie_returns_false_when_no_match() {
+ wp_set_current_user(0);
+ update_option('wp_recache_rejected_cookies', ['wp-postpass']);
+ $_COOKIE = ['unrelated_cookie' => 'value'];
+ $this->assertFalse(wp_recache_has_rejected_cookie());
+ $_COOKIE = [];
+ }
+
+ public function test_wp_recache_has_rejected_cookie_returns_false_when_empty_cookies() {
+ wp_set_current_user(0);
+ update_option('wp_recache_rejected_cookies', ['wp-postpass']);
+ $_COOKIE = [];
+ $this->assertFalse(wp_recache_has_rejected_cookie());
+ }
+
+ public function test_wp_recache_should_exclude_returns_true_for_rejected_cookie() {
+ wp_set_current_user(0);
+ update_option('wp_recache_rejected_cookies', ['woocommerce_items_in_cart']);
+ $_COOKIE = ['woocommerce_items_in_cart' => 'yes'];
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ $_SERVER['REQUEST_URI'] = '/test-page/';
+ unset($_SERVER['HTTP_USER_AGENT']);
+ $this->assertTrue(wp_recache_should_exclude());
+ $_COOKIE = [];
+ }
+
public function test_wp_recache_delete_cache_removes_file() {
$url = 'https://example.com/test-page/';
$path = wp_recache_get_cache_path($url);
diff --git a/uninstall.php b/uninstall.php
index dbd2148..25893cf 100644
--- a/uninstall.php
+++ b/uninstall.php
@@ -12,6 +12,7 @@ $options = [
'wp_recache_cache_enabled',
'wp_recache_separate_mobile_cache',
'wp_recache_exclude_urls',
+ 'wp_recache_rejected_cookies',
'wp_recache_minify_css',
'wp_recache_minify_js',
'wp_recache_lazyload_images',