fix(checkout): prevent caching live checkout state#1354
Conversation
📝 WalkthroughWalkthroughThis PR implements deferred/lazy checkout rendering with cache safety. ChangesDeferred checkout with cache safety
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
inc/ui/class-checkout-element.php (1)
536-540: 💤 Low value
sanitize_key()lowercases header names, which changes the intended casing.
sanitize_key()converts strings to lowercase and removes non-alphanumeric characters except dashes and underscores. This means'Cache-Control'becomes'cache-control','X-Accel-Expires'becomes'x-accel-expires', etc. While HTTP/1.1 headers are case-insensitive, HTTP/2 requires lowercase headers. However, PHP'sheader()function typically handles this correctly regardless. The behavior is acceptable but worth noting for documentation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@inc/ui/class-checkout-element.php` around lines 536 - 540, The code is lowercasing header names by calling sanitize_key() before sending them; remove sanitize_key() and send header names as provided (after trimming CR/LF), or alternatively validate/whitelist header names without altering case to preserve original casing; update the foreach block that iterates $headers (and the header(...) call) to use the original $name (or a validated/whitelisted version) instead of sanitize_key($name) while still sanitizing $value and preventing CRLF injection.tests/WP_Ultimo/UI/Checkout_Element_Test.php (1)
87-99: 💤 Low valueSource-string assertions are brittle but serve as regression guardrails.
This test reads the source file to verify cache-safety patterns exist. While unconventional, it effectively prevents accidental removal of critical no-cache mechanisms. The path
dirname(__DIR__, 3) . '/inc/ui/class-checkout-element.php'resolves correctly fromtests/WP_Ultimo/UI/→ project root.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/WP_Ultimo/UI/Checkout_Element_Test.php` around lines 87 - 99, The test test_source_contains_live_checkout_cache_safeguards() intentionally reads the source file to assert the presence of cache-safety tokens; keep this test but add an inline explanation comment and ensure it uses the same path resolution (dirname(__DIR__, 3) . '/inc/ui/class-checkout-element.php'), retains the phpcs ignore, and preserves assertions for 'DONOTCACHEPAGE', 'nocache_headers()', 'wu_checkout_nocache_required', 'litespeed_control_set_nocache', 'X-Accel-Expires', 'CDN-Cache-Control', 'Surrogate-Control', and 'X-LiteSpeed-Cache-Control' so the regression guardrails remain in place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@inc/ui/class-checkout-element.php`:
- Around line 866-873: The viewport auto-load branch currently only runs when
'IntersectionObserver' exists, leaving older browsers without a fallback; update
the block that checks "else if ('viewport' === trigger && 'IntersectionObserver'
in window)" to handle the else case by attaching a safe fallback that calls
loadCheckout() when the document becomes visible (e.g., on DOMContentLoaded or
on first scroll/resize) and then removes its listener(s); reference the trigger
variable, loadCheckout(), root and the IntersectionObserver usage so you add a
fallback path that triggers loadCheckout() once and cleans up listeners just
like the observer.disconnect() path.
---
Nitpick comments:
In `@inc/ui/class-checkout-element.php`:
- Around line 536-540: The code is lowercasing header names by calling
sanitize_key() before sending them; remove sanitize_key() and send header names
as provided (after trimming CR/LF), or alternatively validate/whitelist header
names without altering case to preserve original casing; update the foreach
block that iterates $headers (and the header(...) call) to use the original
$name (or a validated/whitelisted version) instead of sanitize_key($name) while
still sanitizing $value and preventing CRLF injection.
In `@tests/WP_Ultimo/UI/Checkout_Element_Test.php`:
- Around line 87-99: The test
test_source_contains_live_checkout_cache_safeguards() intentionally reads the
source file to assert the presence of cache-safety tokens; keep this test but
add an inline explanation comment and ensure it uses the same path resolution
(dirname(__DIR__, 3) . '/inc/ui/class-checkout-element.php'), retains the phpcs
ignore, and preserves assertions for 'DONOTCACHEPAGE', 'nocache_headers()',
'wu_checkout_nocache_required', 'litespeed_control_set_nocache',
'X-Accel-Expires', 'CDN-Cache-Control', 'Surrogate-Control', and
'X-LiteSpeed-Cache-Control' so the regression guardrails remain in place.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a43df532-2d4f-4b7c-a493-9ff3ea163d51
📒 Files selected for processing (2)
inc/ui/class-checkout-element.phptests/WP_Ultimo/UI/Checkout_Element_Test.php
| } else if ('viewport' === trigger && 'IntersectionObserver' in window) { | ||
| new IntersectionObserver(function(entries, observer) { | ||
| if (entries.some(function(entry) { return entry.isIntersecting; })) { | ||
| observer.disconnect(); | ||
| loadCheckout(); | ||
| } | ||
| }).observe(root); | ||
| } |
There was a problem hiding this comment.
No fallback when IntersectionObserver is unavailable for viewport trigger.
When trigger === 'viewport' but IntersectionObserver is not supported (e.g., older browsers), the checkout won't auto-load. The button click and focusin handlers remain active, so users can still manually trigger checkout. Consider adding a fallback (e.g., load on DOMContentLoaded) for browsers without IntersectionObserver.
Proposed fix to add fallback for older browsers
} else if ('viewport' === trigger && 'IntersectionObserver' in window) {
new IntersectionObserver(function(entries, observer) {
if (entries.some(function(entry) { return entry.isIntersecting; })) {
observer.disconnect();
loadCheckout();
}
}).observe(root);
+ } else if ('viewport' === trigger) {
+ // Fallback for browsers without IntersectionObserver: load on DOMContentLoaded
+ if ('loading' === document.readyState) {
+ document.addEventListener('DOMContentLoaded', loadCheckout);
+ } else {
+ loadCheckout();
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } else if ('viewport' === trigger && 'IntersectionObserver' in window) { | |
| new IntersectionObserver(function(entries, observer) { | |
| if (entries.some(function(entry) { return entry.isIntersecting; })) { | |
| observer.disconnect(); | |
| loadCheckout(); | |
| } | |
| }).observe(root); | |
| } | |
| } else if ('viewport' === trigger && 'IntersectionObserver' in window) { | |
| new IntersectionObserver(function(entries, observer) { | |
| if (entries.some(function(entry) { return entry.isIntersecting; })) { | |
| observer.disconnect(); | |
| loadCheckout(); | |
| } | |
| }).observe(root); | |
| } else if ('viewport' === trigger) { | |
| // Fallback for browsers without IntersectionObserver: load on DOMContentLoaded | |
| if ('loading' === document.readyState) { | |
| document.addEventListener('DOMContentLoaded', loadCheckout); | |
| } else { | |
| loadCheckout(); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@inc/ui/class-checkout-element.php` around lines 866 - 873, The viewport
auto-load branch currently only runs when 'IntersectionObserver' exists, leaving
older browsers without a fallback; update the block that checks "else if
('viewport' === trigger && 'IntersectionObserver' in window)" to handle the else
case by attaching a safe fallback that calls loadCheckout() when the document
becomes visible (e.g., on DOMContentLoaded or on first scroll/resize) and then
removes its listener(s); reference the trigger variable, loadCheckout(), root
and the IntersectionObserver usage so you add a fallback path that triggers
loadCheckout() once and cleans up listeners just like the observer.disconnect()
path.
Summary
Research notesChecked public cache implementation patterns for:
Verification
aidevops.sh v3.20.36 plugin for OpenCode v1.16.2 with gpt-5.5 Merged via PR #1354 to main. |
Summary
Checkout_Element::setup()andCheckout_Element::output().defer/defer_trigger) so marketing pages can stay cacheable until visitor intent, then fetch fresh checkout markup via light-AJAX and setwu_checkout_intent.Research notes
Checked public cache implementation patterns for:
DONOTCACHEPAGEcompatibility used by WordPress page-cache plugins.nocache_headers()plus explicitCache-Control: no-storefor stateful responses.litespeed_control_set_nocachebypass action and cache-control header.X-Accel-Expires: 0.CDN-Cache-ControlandSurrogate-Controlno-store signals.Verification
vendor/bin/phpcs inc/ui/class-checkout-element.php tests/WP_Ultimo/UI/Checkout_Element_Test.phpvendor/bin/phpunit --filter Checkout_Element_Testvendor/bin/phpstan analyse inc/ui/class-checkout-element.php tests/WP_Ultimo/UI/Checkout_Element_Test.phpgit diff --checkResolves #1330
aidevops.sh v3.20.36 plugin for OpenCode v1.16.2 with gpt-5.5
Summary by CodeRabbit
New Features
Tests