Skip to content

fix: Validate sold individually products in shortcode cart/checkout#62293

Merged
opr merged 16 commits intowoocommerce:trunkfrom
faisuc:fix/cart-sold-individually-validation
Mar 2, 2026
Merged

fix: Validate sold individually products in shortcode cart/checkout#62293
opr merged 16 commits intowoocommerce:trunkfrom
faisuc:fix/cart-sold-individually-validation

Conversation

@faisuc
Copy link
Copy Markdown
Contributor

@faisuc faisuc commented Dec 6, 2025

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:

  • Added check_cart_item_sold_individually() method to WC_Cart class to validate and fix sold individually products
  • Integrated the validation into check_cart_items() method which runs on shortcode cart and checkout pages
  • Added test coverage to verify the fix works correctly

The 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

  • Automatically create a changelog entry from the details below.
Changelog Entry Details

Significance

  • Patch

Type

  • Fix - Fixes an existing bug

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.

@faisuc faisuc requested a review from a team as a code owner December 6, 2025 17:55
@faisuc faisuc requested review from straku and removed request for a team December 6, 2025 17:55
@github-actions github-actions Bot added type: community contribution plugin: woocommerce Issues related to the WooCommerce Core plugin. labels Dec 6, 2025
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 6, 2025

Testing Guidelines

Hi ,

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:

  • 🖼️ Screenshots or screen recordings.
  • 📝 List of functionality tested / steps followed.
  • 🌐 Site details (environment attributes such as hosting type, plugins, theme, store size, store age, and relevant settings).
  • 🔍 Any analysis performed, such as assessing potential impacts on environment attributes and other plugins, conducting performance profiling, or using LLM/AI-based analysis.

⚠️ Within the testing details you provide, please ensure that no sensitive information (such as API keys, passwords, user data, etc.) is included in this public issue.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 6, 2025

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Changelog Entry
plugins/woocommerce/changelog/62293-fix-cart-sold-individually-validation
Adds a patch changelog noting improved sold-individually validation on cart and checkout pages.
Cart Validation Implementation
plugins/woocommerce/includes/class-wc-cart.php
Adds public method check_cart_item_sold_individually() that iterates cart items, enforces quantity = 1 for sold-individually products (resets quantity, refreshes item data, returns WP_Error on violation), and integrates this check into check_cart_items() with error notice handling and cart totals recalculation.
Test Coverage
plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
Adds test_check_cart_items_reduces_sold_individually_quantity() which asserts quantity is reduced to 1, totals are recalculated, and an appropriate error notice is emitted.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Inspect check_cart_item_sold_individually() for correct mutation of cart item quantities and that item data refresh uses the right product snapshot.
  • Verify integration in check_cart_items() doesn't disrupt other validations and that WP_Error handling and notices are consistent with existing patterns.
  • Confirm cart totals recalculation is triggered and tests properly clean up cart state.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding validation for sold individually products on shortcode cart/checkout pages.
Description check ✅ Passed The description provides clear context about the bug, the changes made, and how they fix the issue, which aligns with the code changes.
Linked Issues check ✅ Passed The PR fully addresses issue #62269 by implementing validation that reduces sold-individually product quantities to 1 and recalculates totals correctly on shortcode pages.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the sold-individually validation issue: method addition, integration into validation flow, test coverage, and changelog entry are all directly related.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c678abf and 308238c.

📒 Files selected for processing (1)
  • plugins/woocommerce/tests/php/includes/class-wc-cart-test.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/woocommerce/tests/php/includes/class-wc-cart-test.php

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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), the get_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

📥 Commits

Reviewing files that changed from the base of the PR and between 5200c6a and c678abf.

📒 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.php
  • plugins/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.php
  • plugins/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.php
  • plugins/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 indeed draft or checkout-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.php
  • plugins/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 as public, not protected
All test methods in WooCommerce PHPUnit tests must be declared as public, not protected
Add declare( strict_types = 1 ); at the top of WooCommerce PHPUnit test files
WooCommerce PHPUnit test classes should extend WC_Unit_Test_Case and 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-validation
  • plugins/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() and check_cart_item_stock(). Error handling is consistent.


799-830: Implementation is correct and addresses the bug.

The method properly:

  1. Fetches a fresh product instance to get the current sold_individually status from the database
  2. Updates the cached cart item data with the fresh product
  3. Reduces quantity to 1 without immediately recalculating totals (deferred to caller)
  4. 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:

  1. Product added with quantity > 1
  2. Product later marked as "Sold Individually"
  3. check_cart_items() reduces quantity to 1 and shows appropriate notice

The test also properly cleans up resources in the finally block (lines 170-172).

@straku straku requested review from a team and ralucaStan and removed request for a team and straku December 18, 2025 16:02
@straku
Copy link
Copy Markdown
Contributor

straku commented Dec 18, 2025

@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!

Copy link
Copy Markdown
Contributor

@ralucaStan ralucaStan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@faisuc
Copy link
Copy Markdown
Contributor Author

faisuc commented Dec 29, 2025

Hi @ralucaStan,

I tested this on trunk and the bug is still there. On trunk, check_cart_items() only calls check_cart_item_validity() and check_cart_item_stock(). It doesn't check sold individually. So if you add a product with qty 2, then enable "Sold Individually" in admin, visiting shortcode cart or checkout pages leaves the quantity at 2 with incorrect totals.

Blocks and mini-cart work because they use the Store API, which validates sold individually via QuantityLimits (sets editable => false for sold individually products) and CartController::validate_cart_item() (throws an exception if qty > 1). Shortcode pages don't have this check.

I ran a test on trunk: add product with qty 2, enable sold individually, then call check_cart_items(). Quantity stays 2 and subtotal stays 20 (should be 10). This confirms the bug.

This PR adds check_cart_item_sold_individually() to check_cart_items(), following the same pattern as the other validation methods. It loads a fresh product instance to get the current sold individually setting (cart items can have cached product objects), reduces quantity to 1, and shows an error notice. All tests pass, including the new one covering this scenario.

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.

@ralucaStan
Copy link
Copy Markdown
Contributor

@faisuc I was able to replicate it in the cart shortcode. I'll have to handover this PR to @straku, and he'll continue with the review once he's back.

Thank you for your patience.

@faisuc
Copy link
Copy Markdown
Contributor Author

faisuc commented Dec 31, 2025

Thanks for confirming. I'll wait for @straku's review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

@opr opr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 🙇🏼

Comment thread plugins/woocommerce/includes/class-wc-cart.php
Comment thread plugins/woocommerce/includes/class-wc-cart.php Outdated
Comment thread plugins/woocommerce/includes/class-wc-cart.php Outdated
Comment thread plugins/woocommerce/includes/class-wc-cart.php Outdated
@faisuc
Copy link
Copy Markdown
Contributor Author

faisuc commented Feb 4, 2026

@opr Thanks for the review. Added the @since 10.7.0 tag, aligned $product_id, renamed $fresh_product to $product_to_check, and switched to a single WP_Error outside the loop with ->add() so we report all sold-individually items. Also updated check_cart_items() to loop over get_error_messages() so each product gets its own notice.

@opr
Copy link
Copy Markdown
Contributor

opr commented Feb 4, 2026

@opr Thanks for the review. Added the @since 10.7.0 tag, aligned $product_id, renamed $fresh_product to $product_to_check, and switched to a single WP_Error outside the loop with ->add() so we report all sold-individually items. Also updated check_cart_items() to loop over get_error_messages() so each product gets its own notice.

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.

@opr opr added this to the 10.7.0 milestone Feb 26, 2026
@opr opr force-pushed the fix/cart-sold-individually-validation branch from be81e60 to 75cd511 Compare February 26, 2026 17:17
Copy link
Copy Markdown
Contributor

@opr opr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Comment thread plugins/woocommerce/includes/class-wc-cart.php
Comment thread plugins/woocommerce/tests/php/includes/class-wc-cart-test.php Outdated
Comment thread plugins/woocommerce/tests/php/includes/class-wc-cart-test.php Outdated
Comment thread plugins/woocommerce/tests/php/includes/class-wc-cart-test.php Outdated
Comment thread plugins/woocommerce/tests/php/includes/class-wc-cart-test.php Outdated
@faisuc
Copy link
Copy Markdown
Contributor Author

faisuc commented Feb 27, 2026

@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.

Copy link
Copy Markdown
Contributor

@opr opr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

@faisuc
Copy link
Copy Markdown
Contributor Author

faisuc commented Feb 28, 2026

@opr Added the comment you suggested.

Copy link
Copy Markdown
Contributor

@opr opr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@faisuc Thank you for the PR and for your patience while we reviewed! I'm happy to approve this now! 😎

@opr opr merged commit 376e158 into woocommerce:trunk Mar 2, 2026
87 of 89 checks passed
samnajian pushed a commit that referenced this pull request Mar 11, 2026
…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>
jamesckemp pushed a commit that referenced this pull request Mar 11, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin: woocommerce Issues related to the WooCommerce Core plugin. type: community contribution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sold individually is not enforced in the shortcode cart and checkout pages

5 participants