Rejected-cookie cache exclusions (WooCommerce cart) #10

Open
opened 2026-09-13 11:50:22 +00:00 by kevin.bataille · 1 comment
Owner

Anonymous page caching is unsafe for visitors carrying session-state cookies: a WooCommerce customer with items in cart (woocommerce_items_in_cart, wp_woocommerce_session_*), password-protected post visitors (wp-postpass), or comment authors must always receive dynamic pages, not just logged-in users.

Problem: WP Recache only bypasses cache for wordpress_logged_in. A shopper with items in cart gets cached pages everywhere (stale mini-cart, wrong tax prices), breaking the intent of story 5 in #1.

Spec:

  • New rejected_cookies option (defaults: WooCommerce session/cart cookies, wp-postpass, comment author cookies), editable in settings
  • wp_recache_should_exclude() returns true when a rejected cookie is present
  • The standalone drop-in (wp-content/wp-recache-cache.php) applies the same check — cookie names are readable without WordPress
  • Updating the option purges the cache
Anonymous page caching is unsafe for visitors carrying session-state cookies: a WooCommerce customer with items in cart (woocommerce_items_in_cart, wp_woocommerce_session_*), password-protected post visitors (wp-postpass), or comment authors must always receive dynamic pages, not just logged-in users. **Problem:** WP Recache only bypasses cache for wordpress_logged_in. A shopper with items in cart gets cached pages everywhere (stale mini-cart, wrong tax prices), breaking the intent of story 5 in #1. **Spec:** - New rejected_cookies option (defaults: WooCommerce session/cart cookies, wp-postpass, comment author cookies), editable in settings - wp_recache_should_exclude() returns true when a rejected cookie is present - The standalone drop-in (wp-content/wp-recache-cache.php) applies the same check — cookie names are readable without WordPress - Updating the option purges the cache
kevin.bataille added the phase-1 label 2026-09-13 11:50:22 +00:00
Author
Owner

Spec: Rejected-cookie cache exclusions (WooCommerce cart)

Problem Statement

WP Recache only bypasses page caching for visitors carrying the wordpress_logged_in cookie. Anonymous visitors with session-state cookies — WooCommerce customers with items in cart (woocommerce_items_in_cart, wp_woocommerce_session_*), password-protected post visitors (wp-postpass), or comment authors (comment_author_*, wordpress_* comment cookies) — receive stale cached pages. This breaks dynamic elements like mini-carts showing wrong items, incorrect tax/pricing, and password-gate state.

Solution

Add a rejected_cookies option containing a list of cookie names (one per line) that trigger cache bypass. Defaults cover WooCommerce session/cart cookies, wp-postpass, and comment-author cookies. Both the WP-level exclusion check and the standalone drop-in apply the same list. Editing the option in settings purges all cache.

User Stories

  1. As a WooCommerce store owner, I want visitors with items in cart to bypass the page cache, so they always see accurate mini-cart contents and tax prices.
  2. As a site administrator, I want to see a "Rejected Cookies" textarea in WP Recache settings, so I can view and edit which cookies bypass the cache.
  3. As a site administrator, I want the rejected cookies list to ship with sensible WooCommerce defaults pre-filled, so the feature works out of the box without manual configuration.
  4. As a site administrator, I want to add custom cookie names to the rejected list, so I can handle plugins beyond WooCommerce that use session cookies.
  5. As a site administrator, I want to remove cookie names from the rejected list, so I can fine-tune cache bypass for my specific setup.
  6. As a site administrator, I want the cache to be purged automatically when I save changes to the rejected cookies list, so stale cached pages are not served with the old rules.
  7. As a WooCommerce customer, I want the mini-cart to show correct items and quantities on every page, so I can trust what I'm about to purchase.
  8. As a password-protected post visitor, I want to receive a dynamic page when I carry a wp-postpass cookie, so the content gate works correctly.
  9. As a comment author, I want to see my pending comment state reflected without requiring a page refresh, so I know my comment was submitted.
  10. As a site administrator, I want the drop-in (served before WordPress loads) to also check rejected cookies, so the cache bypass works even on fast-path requests.
  11. As a site administrator, I want the drop-in to read the same cookie list as the WP-level check, so there are no inconsistencies between the two cache-serving paths.
  12. As a developer, I want the rejected cookies option to use the same wp_recache_ prefix and option pattern as existing options, so the codebase stays consistent.
  13. As a site administrator, I want the rejected cookies setting to appear on the Cache tab alongside the existing "Exclude URLs" setting, so related exclusion controls are in one place.
  14. As a site administrator, I want each rejected cookie name on its own line in the textarea, so the list is easy to read and edit.
  15. As a site administrator, I want whitespace around cookie names to be trimmed automatically, so typos like accidental spaces don't cause missed cache bypasses.
  16. As a site administrator, I want empty lines in the rejected cookies textarea to be ignored, so the list is forgiving of formatting inconsistencies.
  17. As a site administrator, I want the default rejected cookies to include woocommerce_items_in_cart, wp_woocommerce_session_*, wp-postpass, and comment_author_*, so the most common session-state cookies are covered.
  18. As a site administrator, I want the * wildcard suffix on cookie names (like wp_woocommerce_session_* and comment_author_*) to match any cookie name starting with that prefix, so session cookies with dynamic suffixes are handled.
  19. As a developer, I want the drop-in to receive the rejected cookies list at activation time by writing it into the generated PHP file, so the drop-in can check cookies without loading WordPress or reading the database.
  20. As a developer, I want the drop-in to be regenerated when the rejected cookies option is updated, so the drop-in always reflects the current list.
  21. As a site administrator, I want the settings page to show a description explaining what rejected cookies do, so I understand the impact of my changes.
  22. As a site administrator, I want the rejected cookies feature to work independently of the "Enable Cache" toggle — the option is always stored, so re-enabling cache after disabling it retains my configuration.
  23. As a developer, I want wp_recache_should_exclude() to check rejected cookies after the logged-in check and before the URL exclusion check, so the check order is logical and consistent.
  24. As a site administrator, I want the rejected cookies list to support both exact cookie names and prefix patterns with *, so I can handle WordPress cookies that include dynamic user-specific suffixes.
  25. As a WooCommerce store owner, I want the default list to cover all standard WooCommerce session cookies, so I don't need to research cookie names manually.

Implementation Decisions

  • New option: wp_recache_rejected_cookies stored as an array of strings. Default value: ['woocommerce_items_in_cart', 'wp_woocommerce_session_*', 'wp-postpass', 'comment_author_*', 'wordpress_*']. The wordpress_* wildcard covers wordpress_logged_in_* as well, making the existing explicit logged-in check a redundant but safe fast-path.
  • Option registration: Register in Admin::register_settings() following the same pattern as wp_recache_exclude_urls: type array, sanitize callback splits on newlines, trims, filters empties, and applies sanitize_text_field.
  • Settings UI: Add a textarea row on the Cache tab, immediately below the "Exclude URLs" textarea. Same layout pattern: <textarea> with one cookie name per line, description paragraph.
  • WP-level check: In wp_recache_should_exclude(), after the logged-in check and before the URL exclusion check, iterate over the rejected cookies option. For each entry: if it ends with *, check strpos() against all $_COOKIE keys; otherwise check isset(). Return true on first match.
  • Drop-in generation: Activator::create_cache_server() bakes the rejected cookies list into the generated PHP file as a PHP array literal. The drop-in iterates this array using the same exact/prefix logic. No WordPress functions are called.
  • Drop-in regeneration on option change: Add an update_option_wp_recache_rejected_cookies hook in Admin::init() that calls Activator::create_cache_server() followed by wp_recache_delete_all_cache(). Same pattern as the existing update_option_wp_recache_exclude_urls hook.
  • Cache purge on save: Both update_option hooks purge all cache, ensuring stale pages are not served under new rules.
  • Wildcard convention: An asterisk at the end of a cookie name means "match any cookie whose name starts with this prefix." No other wildcard positions are supported. This is a simple strpos prefix check, not glob or regex.
  • Filter hook: The existing apply_filters('wp_recache_exclude', false) at the end of wp_recache_should_exclude() remains. The rejected cookies check runs before it, so filter-based exclusions still work as a fallback.

Testing Decisions

  • Unit tests in test-helpers.php: Add tests for wp_recache_should_exclude() returning true when a rejected cookie is present (exact match and prefix match), and false when no rejected cookies are present. Follow the existing pattern of setting $_COOKIE superglobal and asserting against the function return.
  • Unit tests in test-admin.php: Add tests for the sanitize callback: trimming, empty-line filtering, and array-vs-string input handling.
  • Manual tests (TESTING.md): Add a section covering: (a) visit with woocommerce_items_in_cart cookie set → MISS, no cache served; (b) visit with wp-postpass cookie → MISS; (c) edit rejected cookies in settings → cache purged; (d) drop-in serves dynamic page when rejected cookie is present.
  • Good test behavior: Tests assert external behavior (does the function return true/false? is the cache purged?) not implementation details (which line of code ran).

Out of Scope

  • No changes to cache TTL logic.
  • No new UI for per-URL cookie overrides.
  • No automatic detection of installed plugins to pre-fill cookie names.
  • No changes to the preload crawler.
  • No changes to mobile cache detection.
  • No changes to the DONOTCACHEPAGE constant handling.
  • No database schema changes beyond the new option row.

Further Notes

  • Issue #12 ("Bake settings into drop-in config file") is a related companion. If #12 lands first, the drop-in could read from a baked config file instead of regenerated PHP. This spec assumes the current approach (regenerated PHP) and is compatible with either direction.
  • The wordpress_* wildcard in defaults subsumes the existing explicit wordpress_logged_in check in the drop-in. The explicit check can remain as a fast-path without harm, or be removed once the wildcard is proven — a decision for implementation time.
  • The design doc (docs/plans/2026-09-13-wprecache-design.md) notes the drop-in cannot read WP options. This spec respects that constraint by baking the option value into the generated file.
## Spec: Rejected-cookie cache exclusions (WooCommerce cart) ### Problem Statement WP Recache only bypasses page caching for visitors carrying the `wordpress_logged_in` cookie. Anonymous visitors with session-state cookies — WooCommerce customers with items in cart (`woocommerce_items_in_cart`, `wp_woocommerce_session_*`), password-protected post visitors (`wp-postpass`), or comment authors (`comment_author_*`, `wordpress_*` comment cookies) — receive stale cached pages. This breaks dynamic elements like mini-carts showing wrong items, incorrect tax/pricing, and password-gate state. ### Solution Add a `rejected_cookies` option containing a list of cookie names (one per line) that trigger cache bypass. Defaults cover WooCommerce session/cart cookies, `wp-postpass`, and comment-author cookies. Both the WP-level exclusion check and the standalone drop-in apply the same list. Editing the option in settings purges all cache. ### User Stories 1. As a WooCommerce store owner, I want visitors with items in cart to bypass the page cache, so they always see accurate mini-cart contents and tax prices. 2. As a site administrator, I want to see a "Rejected Cookies" textarea in WP Recache settings, so I can view and edit which cookies bypass the cache. 3. As a site administrator, I want the rejected cookies list to ship with sensible WooCommerce defaults pre-filled, so the feature works out of the box without manual configuration. 4. As a site administrator, I want to add custom cookie names to the rejected list, so I can handle plugins beyond WooCommerce that use session cookies. 5. As a site administrator, I want to remove cookie names from the rejected list, so I can fine-tune cache bypass for my specific setup. 6. As a site administrator, I want the cache to be purged automatically when I save changes to the rejected cookies list, so stale cached pages are not served with the old rules. 7. As a WooCommerce customer, I want the mini-cart to show correct items and quantities on every page, so I can trust what I'm about to purchase. 8. As a password-protected post visitor, I want to receive a dynamic page when I carry a `wp-postpass` cookie, so the content gate works correctly. 9. As a comment author, I want to see my pending comment state reflected without requiring a page refresh, so I know my comment was submitted. 10. As a site administrator, I want the drop-in (served before WordPress loads) to also check rejected cookies, so the cache bypass works even on fast-path requests. 11. As a site administrator, I want the drop-in to read the same cookie list as the WP-level check, so there are no inconsistencies between the two cache-serving paths. 12. As a developer, I want the rejected cookies option to use the same `wp_recache_` prefix and option pattern as existing options, so the codebase stays consistent. 13. As a site administrator, I want the rejected cookies setting to appear on the Cache tab alongside the existing "Exclude URLs" setting, so related exclusion controls are in one place. 14. As a site administrator, I want each rejected cookie name on its own line in the textarea, so the list is easy to read and edit. 15. As a site administrator, I want whitespace around cookie names to be trimmed automatically, so typos like accidental spaces don't cause missed cache bypasses. 16. As a site administrator, I want empty lines in the rejected cookies textarea to be ignored, so the list is forgiving of formatting inconsistencies. 17. As a site administrator, I want the default rejected cookies to include `woocommerce_items_in_cart`, `wp_woocommerce_session_*`, `wp-postpass`, and `comment_author_*`, so the most common session-state cookies are covered. 18. As a site administrator, I want the `*` wildcard suffix on cookie names (like `wp_woocommerce_session_*` and `comment_author_*`) to match any cookie name starting with that prefix, so session cookies with dynamic suffixes are handled. 19. As a developer, I want the drop-in to receive the rejected cookies list at activation time by writing it into the generated PHP file, so the drop-in can check cookies without loading WordPress or reading the database. 20. As a developer, I want the drop-in to be regenerated when the rejected cookies option is updated, so the drop-in always reflects the current list. 21. As a site administrator, I want the settings page to show a description explaining what rejected cookies do, so I understand the impact of my changes. 22. As a site administrator, I want the rejected cookies feature to work independently of the "Enable Cache" toggle — the option is always stored, so re-enabling cache after disabling it retains my configuration. 23. As a developer, I want `wp_recache_should_exclude()` to check rejected cookies after the logged-in check and before the URL exclusion check, so the check order is logical and consistent. 24. As a site administrator, I want the rejected cookies list to support both exact cookie names and prefix patterns with `*`, so I can handle WordPress cookies that include dynamic user-specific suffixes. 25. As a WooCommerce store owner, I want the default list to cover all standard WooCommerce session cookies, so I don't need to research cookie names manually. ### Implementation Decisions - **New option:** `wp_recache_rejected_cookies` stored as an array of strings. Default value: `['woocommerce_items_in_cart', 'wp_woocommerce_session_*', 'wp-postpass', 'comment_author_*', 'wordpress_*']`. The `wordpress_*` wildcard covers `wordpress_logged_in_*` as well, making the existing explicit logged-in check a redundant but safe fast-path. - **Option registration:** Register in `Admin::register_settings()` following the same pattern as `wp_recache_exclude_urls`: type `array`, sanitize callback splits on newlines, trims, filters empties, and applies `sanitize_text_field`. - **Settings UI:** Add a textarea row on the Cache tab, immediately below the "Exclude URLs" textarea. Same layout pattern: `<textarea>` with one cookie name per line, description paragraph. - **WP-level check:** In `wp_recache_should_exclude()`, after the logged-in check and before the URL exclusion check, iterate over the rejected cookies option. For each entry: if it ends with `*`, check `strpos()` against all `$_COOKIE` keys; otherwise check `isset()`. Return `true` on first match. - **Drop-in generation:** `Activator::create_cache_server()` bakes the rejected cookies list into the generated PHP file as a PHP array literal. The drop-in iterates this array using the same exact/prefix logic. No WordPress functions are called. - **Drop-in regeneration on option change:** Add an `update_option_wp_recache_rejected_cookies` hook in `Admin::init()` that calls `Activator::create_cache_server()` followed by `wp_recache_delete_all_cache()`. Same pattern as the existing `update_option_wp_recache_exclude_urls` hook. - **Cache purge on save:** Both `update_option` hooks purge all cache, ensuring stale pages are not served under new rules. - **Wildcard convention:** An asterisk at the end of a cookie name means "match any cookie whose name starts with this prefix." No other wildcard positions are supported. This is a simple `strpos` prefix check, not glob or regex. - **Filter hook:** The existing `apply_filters('wp_recache_exclude', false)` at the end of `wp_recache_should_exclude()` remains. The rejected cookies check runs before it, so filter-based exclusions still work as a fallback. ### Testing Decisions - **Unit tests in `test-helpers.php`:** Add tests for `wp_recache_should_exclude()` returning `true` when a rejected cookie is present (exact match and prefix match), and `false` when no rejected cookies are present. Follow the existing pattern of setting `$_COOKIE` superglobal and asserting against the function return. - **Unit tests in `test-admin.php`:** Add tests for the sanitize callback: trimming, empty-line filtering, and array-vs-string input handling. - **Manual tests (TESTING.md):** Add a section covering: (a) visit with `woocommerce_items_in_cart` cookie set → MISS, no cache served; (b) visit with `wp-postpass` cookie → MISS; (c) edit rejected cookies in settings → cache purged; (d) drop-in serves dynamic page when rejected cookie is present. - **Good test behavior:** Tests assert external behavior (does the function return true/false? is the cache purged?) not implementation details (which line of code ran). ### Out of Scope - No changes to cache TTL logic. - No new UI for per-URL cookie overrides. - No automatic detection of installed plugins to pre-fill cookie names. - No changes to the preload crawler. - No changes to mobile cache detection. - No changes to the `DONOTCACHEPAGE` constant handling. - No database schema changes beyond the new option row. ### Further Notes - Issue #12 ("Bake settings into drop-in config file") is a related companion. If #12 lands first, the drop-in could read from a baked config file instead of regenerated PHP. This spec assumes the current approach (regenerated PHP) and is compatible with either direction. - The `wordpress_*` wildcard in defaults subsumes the existing explicit `wordpress_logged_in` check in the drop-in. The explicit check can remain as a fast-path without harm, or be removed once the wildcard is proven — a decision for implementation time. - The design doc (`docs/plans/2026-09-13-wprecache-design.md`) notes the drop-in cannot read WP options. This spec respects that constraint by baking the option value into the generated file.
kevin.bataille added the ready-for-agent label 2026-09-13 12:18:50 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kevin.bataille/wp-recache#10