fix: Validate sold individually products in shortcode cart/checkout#62293
fix: Validate sold individually products in shortcode cart/checkout#62293opr merged 16 commits intowoocommerce:trunkfrom
Conversation
Testing GuidelinesHi , Apart from reviewing the code changes, please make sure to review the testing instructions (Guide) and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed. Reminder: PR reviewers are required to document testing performed. This includes:
|
📝 WalkthroughWalkthroughAdds enforcement for products marked "sold individually" by reducing any cart item quantity above 1 to 1 during cart validation, updates cart item data and totals, produces an error notice, and includes a unit test and changelog entry. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
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 |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php (1)
135-136:get_subtotal()does not accept a context parameter.Unlike
get_total($context), theget_subtotal()method has no parameters. The'edit'argument is silently ignored by PHP, so the test still works, but it's misleading. Consider removing the argument for clarity.- $initial_subtotal = WC()->cart->get_subtotal( 'edit' ); + $initial_subtotal = WC()->cart->get_subtotal();And at line 152:
- $final_subtotal = WC()->cart->get_subtotal( 'edit' ); + $final_subtotal = WC()->cart->get_subtotal();Also applies to: 152-152
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validation(1 hunks)plugins/woocommerce/includes/class-wc-cart.php(3 hunks)plugins/woocommerce/tests/php/includes/class-wc-cart-test.php(2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
plugins/woocommerce/**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
Backend PHP code must conform to WordPress coding standards
Files:
plugins/woocommerce/includes/class-wc-cart.phpplugins/woocommerce/tests/php/includes/class-wc-cart-test.php
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/avoid-regex.mdc)
**/*.{php,js,jsx,ts,tsx}: Avoid regular expressions in favor of more readable and maintainable alternatives, only using regex when no built-in alternative exists, performance is critical, the pattern is complex and well-documented, or maintaining legacy code
Document regular expressions extensively by explaining what the pattern matches, using named groups to improve readability, and adding comprehensive tests for edge cases and security issues
Validate regex input first and consider security implications such as ReDoS (Regular Expression Denial of Service) attacks and injection vulnerabilities when using regular expressions
Files:
plugins/woocommerce/includes/class-wc-cart.phpplugins/woocommerce/tests/php/includes/class-wc-cart-test.php
**/*.{php,js,jsx,tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,tsx,ts}: Guard all code against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Ensure code is readable and intuitive
Add unit or E2E tests where applicable
Files:
plugins/woocommerce/includes/class-wc-cart.phpplugins/woocommerce/tests/php/includes/class-wc-cart-test.php
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit configuration file
**/*.{php,js,ts,jsx,tsx}: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
When making any changes to code that deletes or modifies orders/products/customer data, make sure that there are
sufficient checks in place to prevent accidental data loss. As an example, if deleting a draft order, check that
the order status is indeeddraftorcheckout-draft. Also think about whether race conditions could occur and
delete orders that don't belong to the current customer. When in doubt, ask for clarification in the PR comments.
Files:
plugins/woocommerce/includes/class-wc-cart.phpplugins/woocommerce/tests/php/includes/class-wc-cart-test.php
plugins/woocommerce/tests/php/**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
Backend PHP tests must be located in tests/php/ directory mirroring the src/ or includes/ structure being tested
Files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
plugins/woocommerce/tests/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/woo-phpunit.mdc)
plugins/woocommerce/tests/**/*.php: All setUp() and tearDown() methods in WooCommerce PHPUnit test classes must be declared aspublic, notprotected
All test methods in WooCommerce PHPUnit tests must be declared aspublic, notprotected
Adddeclare( strict_types = 1 );at the top of WooCommerce PHPUnit test files
WooCommerce PHPUnit test classes should extendWC_Unit_Test_Caseand include proper class documentation
Files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
🧠 Learnings (18)
📓 Common learnings
Learnt from: Aljullu
Repo: woocommerce/woocommerce PR: 60641
File: plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts:410-480
Timestamp: 2025-08-29T14:38:18.023Z
Learning: The "sold individually" variation functionality (where the Product Quantity block should hide when a variation is sold individually) mentioned in issue #59443 objectives is not implemented in PR #60641 and will be addressed in a future PR.
Learnt from: Aljullu
Repo: woocommerce/woocommerce PR: 60598
File: plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts:286-319
Timestamp: 2025-08-26T15:16:32.848Z
Learning: In WooCommerce Add to Cart with Options blocks, the base store's actions can be overridden by specific product type stores. For example, validateQuantity() in frontend.ts is overridden by the Grouped Product Selector store in grouped-product-selector/frontend.ts, meaning grouped products use their own validation logic rather than the base store's validateQuantity implementation.
📚 Learning: 2025-11-24T16:12:58.709Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: packages/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:12:58.709Z
Learning: Applies to packages/js/**/*.{js,ts,tsx} : Add a changelog entry for every functional change to a WooCommerce JS package using `pnpm changelog add` or manual creation in `packages/js/[package-name]/changelog/[type-brief-description]`
Applied to files:
plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validation
📚 Learning: 2025-08-29T14:38:18.023Z
Learnt from: Aljullu
Repo: woocommerce/woocommerce PR: 60641
File: plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts:410-480
Timestamp: 2025-08-29T14:38:18.023Z
Learning: The "sold individually" variation functionality (where the Product Quantity block should hide when a variation is sold individually) mentioned in issue #59443 objectives is not implemented in PR #60641 and will be addressed in a future PR.
Applied to files:
plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validationplugins/woocommerce/includes/class-wc-cart.php
📚 Learning: 2025-06-16T11:06:45.637Z
Learnt from: Aljullu
Repo: woocommerce/woocommerce PR: 58845
File: plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/Utils.php:128-140
Timestamp: 2025-06-16T11:06:45.637Z
Learning: In WooCommerce, the `$product->is_in_stock()` method returns `true` when backorders are allowed, even if the actual stock quantity is 0 or negative. This means the method already considers backorder status when determining stock availability.
Applied to files:
plugins/woocommerce/includes/class-wc-cart.php
📚 Learning: 2025-09-25T13:09:51.143Z
Learnt from: louwie17
Repo: woocommerce/woocommerce PR: 61022
File: plugins/woocommerce/includes/wc-update-functions.php:3048-3048
Timestamp: 2025-09-25T13:09:51.143Z
Learning: In WooCommerce update functions, wc_get_order() can return false in edge cases such as when scheduled actions are backed up, orders have been deleted, or refunded orders have been reverted. Always check if the returned order object is valid before calling methods on it.
Applied to files:
plugins/woocommerce/includes/class-wc-cart.php
📚 Learning: 2025-06-19T11:58:57.484Z
Learnt from: mikejolley
Repo: woocommerce/woocommerce PR: 57961
File: plugins/woocommerce/includes/class-wc-session-handler.php:302-333
Timestamp: 2025-06-19T11:58:57.484Z
Learning: In WooCommerce's session handler, the cart merging behavior was revised to always merge guest and user carts when both contain items, rather than having the guest cart take precedence. The migrate_cart_data() method in class-wc-session-handler.php implements this by using array_merge() to combine both carts when neither is empty.
Applied to files:
plugins/woocommerce/includes/class-wc-cart.php
📚 Learning: 2025-11-24T16:14:26.363Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: plugins/woocommerce/tests/php/src/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:14:26.363Z
Learning: Applies to plugins/woocommerce/tests/php/src/tests/**/*Test.php : Write one purpose per test - multiple assertions are acceptable if testing the same behavior, but avoid testing multiple unrelated behaviors in a single test method
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-11T11:03:44.571Z
Learnt from: opr
Repo: woocommerce/woocommerce PR: 61507
File: plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Customers/class-wc-rest-customers-v4-controller-tests.php:638-638
Timestamp: 2025-11-11T11:03:44.571Z
Learning: In WooCommerce test files (plugins/woocommerce/tests/**/*.php), prefer explicit count assertions over scoping tests with parameters like `include` when testing pagination and default behaviors. Explicit counts test the specifics more reliably and help catch edge cases.
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:14:26.363Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: plugins/woocommerce/tests/php/src/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:14:26.363Z
Learning: Applies to plugins/woocommerce/tests/php/src/tests/**/*Test.php : Test behavior and interfaces, not implementation details, to ensure tests remain resilient when internal code changes
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:15:16.065Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: .cursor/rules/woo-phpunit.mdc:0-0
Timestamp: 2025-11-24T16:15:16.065Z
Learning: Applies to plugins/woocommerce/tests/**/*.php : Add `declare( strict_types = 1 );` at the top of WooCommerce PHPUnit test files
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:15:16.065Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: .cursor/rules/woo-phpunit.mdc:0-0
Timestamp: 2025-11-24T16:15:16.065Z
Learning: Applies to plugins/woocommerce/tests/**/*.php : WooCommerce PHPUnit test classes should extend `WC_Unit_Test_Case` and include proper class documentation
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:14:26.363Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: plugins/woocommerce/tests/php/src/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:14:26.363Z
Learning: Applies to plugins/woocommerce/tests/php/src/tests/**/*Test.php : Use targeted assertions instead of full equality checks - break down array comparisons into individual assertions using assertArrayHasKey() and specific value assertions
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:14:26.363Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: plugins/woocommerce/tests/php/src/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:14:26.363Z
Learning: Applies to plugins/woocommerce/tests/php/src/tests/**/*Test.php : Use unset() on unused closure parameters to avoid PHPCS parameter-not-used errors, particularly in mock callbacks, array_map/filter functions, and interface implementations
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:15:16.065Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: .cursor/rules/woo-phpunit.mdc:0-0
Timestamp: 2025-11-24T16:15:16.065Z
Learning: Applies to plugins/woocommerce/tests/**/*.php : All test methods in WooCommerce PHPUnit tests must be declared as `public`, not `protected`
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:14:26.363Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: plugins/woocommerce/tests/php/src/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:14:26.363Z
Learning: Applies to plugins/woocommerce/tests/php/src/tests/**/*Test.php : Name test methods as test_{feature}_{scenario}() or test_{feature}_{scenario}_{outcome}() to clearly describe what is being tested
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:14:26.363Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: plugins/woocommerce/tests/php/src/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:14:26.363Z
Learning: Applies to plugins/woocommerce/tests/php/src/tests/**/*Test.php : Use resilient assertions that won't break when adjacent code changes or new keys are added to data structures
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-11-24T16:14:26.363Z
Learnt from: CR
Repo: woocommerce/woocommerce PR: 0
File: plugins/woocommerce/tests/php/src/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:14:26.363Z
Learning: Applies to plugins/woocommerce/tests/php/src/tests/**/*Test.php : Use tearDown() to reset test state including deleted users, unset globals, and other cleanup to prevent test pollution
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
📚 Learning: 2025-06-13T23:52:46.221Z
Learnt from: mreishus
Repo: woocommerce/woocommerce PR: 58817
File: plugins/woocommerce/includes/wc-product-functions.php:140-140
Timestamp: 2025-06-13T23:52:46.221Z
Learning: In WooCommerce's wc_delete_product_transients function, the context check includes both $is_admin_page (for regular admin screens) and $is_privileged_ajax (for AJAX requests from users with edit_products capability), ensuring that legitimate admin AJAX operations like product imports/exports can still delete transients.
Applied to files:
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
🧬 Code graph analysis (1)
plugins/woocommerce/includes/class-wc-cart.php (2)
plugins/woocommerce/includes/wc-product-functions.php (1)
wc_get_product(69-79)plugins/woocommerce/includes/abstracts/abstract-wc-product.php (1)
is_sold_individually(1644-1646)
🔇 Additional comments (4)
plugins/woocommerce/includes/class-wc-cart.php (2)
748-753: LGTM!The integration follows the established pattern used for
check_cart_item_validity()andcheck_cart_item_stock(). Error handling is consistent.
799-830: Implementation is correct and addresses the bug.The method properly:
- Fetches a fresh product instance to get the current
sold_individuallystatus from the database- Updates the cached cart item data with the fresh product
- Reduces quantity to 1 without immediately recalculating totals (deferred to caller)
- Returns an informative error message
One observation: if multiple cart items violate the sold-individually constraint, only the last error message is returned. This matches the behavior of similar methods (
check_cart_item_validity,check_cart_item_stock) so it's consistent—users will see one error at a time.plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validation (1)
1-4: LGTM!Changelog entry is properly formatted with appropriate significance level (patch) and type (fix). The description clearly explains the behavior change.
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php (1)
115-173: Good test coverage for the new validation logic.The test effectively covers the core scenario from the linked issue:
- Product added with quantity > 1
- Product later marked as "Sold Individually"
check_cart_items()reduces quantity to 1 and shows appropriate noticeThe test also properly cleans up resources in the finally block (lines 170-172).
…b.com/faisuc/woocommerce into fix/cart-sold-individually-validation
|
@faisuc Sorry for not taking the time to look at it recently. Unfortunately, I am starting my AFK tomorrow and will be back Jan 5th. I will make sure to look at the PR at this time, unless someone else from the team finds the time to do it sooner. Sorry again! |
ralucaStan
left a comment
There was a problem hiding this comment.
@faisuc thank you so much for your contribution.
I tried replicating the bug on trunk, and I couldn't see it surfacing anymore. The total was correct, shown for 1 item.
Could you also test on trunk? If you also can't replicate, we can keep only the tests from this PR. Let me know.
Thank you for your valuable help.
|
Hi @ralucaStan, I tested this on trunk and the bug is still there. On trunk, Blocks and mini-cart work because they use the Store API, which validates sold individually via I ran a test on trunk: add product with qty 2, enable sold individually, then call This PR adds If you're not seeing it, are you testing on actual shortcode pages ([woocommerce_cart] or [woocommerce_checkout])? And did you add the product with qty > 1 before enabling sold individually? Thanks for the review, and let me know if I’m missing something. |
|
Thanks for confirming. I'll wait for @straku's review. |
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug where shortcode cart and checkout pages did not properly validate sold individually products. When a product was marked as "Sold Individually" after being added to the cart with a quantity greater than 1, the shortcode pages would display quantity as 1 but calculate totals based on the original quantity.
Changes:
- Added
check_cart_item_sold_individually()method to validate and automatically reduce quantity to 1 for sold individually products - Integrated the new validation into the existing
check_cart_items()workflow - Added comprehensive test coverage to verify the fix
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
plugins/woocommerce/includes/class-wc-cart.php |
Added new check_cart_item_sold_individually() method and integrated it into check_cart_items() to validate sold individually products and reduce quantities as needed |
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php |
Added test to verify sold individually validation correctly reduces quantity to 1 and recalculates totals |
plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validation |
Added changelog entry documenting the fix |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
opr
left a comment
There was a problem hiding this comment.
Hi thanks for the work so far, and for your patience while we review it! I suggested a few changes, let me know what you think 🙇🏼
|
@opr Thanks for the review. Added the |
Thanks a lot for the changes. Unfortunately I'm now AFK and due to capacity constraints it's not likely that we'll be able to review this until 23rd February at the earliest, so I appreciate your patience here. Thank you. |
be81e60 to
75cd511
Compare
opr
left a comment
There was a problem hiding this comment.
@faisuc thanks I pushed a lint change, hope you don't mind. Also some other comments added in line.
It would also be nice to have a test that verifies that a sold-individually product that already has quantity=1 doesn't trigger an error or get modified, just to be safe.
The direction of this PR is great and I think we can get it merged as soon as the points I mentioned are addressed.
…b.com/faisuc/woocommerce into fix/cart-sold-individually-validation
|
@opr Thanks for the review and the lint change. I’ve applied your test suggestions and added a test for sold-individually with qty=1. On the cart method, we do assume |
opr
left a comment
There was a problem hiding this comment.
@opr Thanks for the review and the lint change.
I’ve applied your test suggestions and added a test for sold-individually with qty=1. On the cart method, we do assume
$values['data']can be stale when the product is changed after it’s in the cart, so I kept the re-fetch and overwrite. The existing test would fail without it.
Thanks for clarifying!
Only thing I'd suggest in that case is a short inline comment on the overwrite line explaining why it's necessary. Something like:
// Re-fetch and overwrite to reflect product changes made after item was added to cart.Just to prevent a refactor cleaning this up
|
@opr Added the comment you suggested. |
…62293) * fix: Validate sold individually products in shortcode cart/checkout * Add changefile(s) from automation for the following project(s): woocommerce * Update class-wc-cart-test.php * Update class-wc-cart.php * fix: Validate sold individually products in shortcode cart/checkout * Update class-wc-cart-test.php * Add changefile(s) from automation for the following project(s): woocommerce * Update class-wc-cart.php * Lint fixes * Update class-wc-cart-test.php * Update class-wc-cart.php * Lint fix --------- Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
…62293) * fix: Validate sold individually products in shortcode cart/checkout * Add changefile(s) from automation for the following project(s): woocommerce * Update class-wc-cart-test.php * Update class-wc-cart.php * fix: Validate sold individually products in shortcode cart/checkout * Update class-wc-cart-test.php * Add changefile(s) from automation for the following project(s): woocommerce * Update class-wc-cart.php * Lint fixes * Update class-wc-cart-test.php * Update class-wc-cart.php * Lint fix --------- Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
Submission Review Guidelines:
Changes proposed in this Pull Request:
Fixed a bug where shortcode cart and checkout pages did not validate sold individually products. When a product was added to the cart with quantity greater than 1, and then marked as "Sold Individually" in the admin, the shortcode pages would display quantity as 1 but calculate totals for the original quantity.
Changes:
check_cart_item_sold_individually()method toWC_Cartclass to validate and fix sold individually productscheck_cart_items()method which runs on shortcode cart and checkout pagesThe fix ensures that when a product is marked as "Sold Individually" after being added to the cart, the quantity is automatically reduced to 1 and totals are recalculated correctly, matching the behavior of blocks cart and checkout pages.
Closes #62269
Changelog entry
Changelog Entry Details
Significance
Type
Message
Fixed sold individually validation on shortcode cart and checkout pages. Products marked as "Sold Individually" after being added to cart now have quantity automatically reduced to 1 with correct totals calculation.