woocommerce-blocks
woocommerce-blocks copied to clipboard
Add to Cart with Options block > Update the block to rely on dedicated render methods instead of WooCommerce core templates.
https://github.com/woocommerce/woocommerce-blocks/assets/15730971/578d1366-349c-4c1d-8fc6-4879b1edcb35
With the removal of the global $product variable from this block on https://github.com/woocommerce/woocommerce-blocks/pull/9457 , follow-up changes are required to ensure it works as expected.
Initially, this block relied on the native WooCommerce's templates for rendering the add-to-cart form button. The problem is those templates all rely exclusively on the global $product variable for rendering content, and in the absence of it, a Fatal error is thrown:
Call to a member function is_purchasable() on string in /srv/htdocs/wp-content/plugins/woocommerce/templates/single-product/add-to-cart/simple.php:22
Based on the outcome of the discussion on #9457 , on this PR an altered and consolidated version of all add-to-cart form templates is used for rendering the Add to Cart with Options block.
The templates consolidated here include:
woocommerce/templates/single-product/add-to-cart/external.phpwoocommerce/templates/single-product/add-to-cart/grouped.phpwoocommerce/templates/single-product/add-to-cart/simple.phpwoocommerce/templates/single-product/add-to-cart/variable.phpwoocommerce/templates/single-product/add-to-cart/variation-add-to-cart-button.php
Important notes
- On this initial version, all filters were intentionally removed and all actions were preserved. The exception is
'woocommerce_single_variation', which was removed as it has thewoocommerce_single_variation_add_to_cart_buttonfunction hooked to it and invokingwc_get_template( 'single-product/add-to-cart/variation-add-to-cart-button.php' );, resulting in the same Fatal error aforementioned as a consequence). - Decision points: we need to decide which actions and filters we wanna officially support for this block. From my perspective, all actions should be preserved and all filters removed (as altering the content/styling should happen on the editor level when it comes to blocks). I'm opening a separate discussion for this so we can 🧠 ⚡ and avoid holding this PR for too long, as we need to clear out the fatal error sooner than later.
- Security: even though the majority of the changes here were ported over from the core of WooCommerce and follow the same security standards as the original templates, I'd like to have extra eyes here to make sure I didn't miss anything when it comes to safety, so I'm asking for more folks with a security background to take a look at these changes to make sure.
Fixes #9502
Other Checks
- [ ] This PR adds/removes a feature flag & I've updated this doc.
- [ ] This PR adds/removes an experimental interfaces and I've updated this doc.
- [x] I tagged two reviewers because this PR makes queries to the database or I think it might have some security impact.
Testing
User Facing Testing
- While having a block theme enabled such as Twenty-twenty Three, head over to your Dashboard, and on the sidebar, click on "Appearance > Editor".
- Select the Single Product template to customize it and click on edit.
- Make sure the Add to Cart with Options block is available for usage on the inserter (you can remove/add the block from the template), add it, and save.
- On the front end, ensure the
Add to Cart button with Optionsworks as expected and the product can be added to the cart. - Access all possible product types and ensure the
Add to Cart button with Optionsis properly displayed for all of them, including: simple, variable, grouped, and external. Make sure all of these products can be added to cart. - Make sure the debugger is enabled, and there are no errors on your PHP debug.log.
- Make sure the
Add to Cart button with Optionsblock also works as expected within theSingle Productblock.
Hooks Testing
- Add the following by the end of your
woocommerce-gutenberg-products-block.phpfile:
add_action( 'woocommerce_before_add_to_cart_form', function () {
echo "<div>Before add to cart form</div>";
} );
add_action( 'woocommerce_after_add_to_cart_form', function () {
echo "<div>After add to cart form</div>";
} );
add_action( 'woocommerce_before_add_to_cart_quantity', function () {
echo "<div>Before add to cart quantity</div>";
} );
add_action( 'woocommerce_after_add_to_cart_quantity', function () {
echo "<div>After add to cart quantity</div>";
} );
add_action( 'woocommerce_before_add_to_cart_button', function () {
echo "<div>Before add to cart button</div>";
} );
add_action( 'woocommerce_after_add_to_cart_button', function () {
echo "<div>After add to cart button</div>";
} );
add_action( 'woocommerce_before_variations_form', function () {
echo "<div>Before variations form</div>";
} );
add_action( 'woocommerce_after_variations_form', function () {
echo "<div>After variations form</div>";
} );
add_action( 'woocommerce_after_variations_table', function () {
echo "<div>After variations table</div>";
} );
add_action( 'woocommerce_before_single_variation', function () {
echo "<div>Before single variation</div>";
} );
add_action( 'woocommerce_after_single_variation', function () {
echo "<div>After single variation</div>";
} );
add_action( 'woocommerce_grouped_product_list_before', function () {
echo "<div>Before grouped product list</div>";
} );
add_action( 'woocommerce_grouped_product_list_after', function () {
echo "<div>After grouped product list</div>";
} );
- Access all possible single product types (simple, variable, grouped, and external) and ensure the hooked texts are correctly displayed on the front-end for all of them.
WooCommerce Visibility
- [x] WooCommerce Core
- [ ] Feature plugin
- [ ] Experimental
Changelog
Update the
Add to Cart with Optionsblock to rely on dedicated render methods instead of WooCommerce core templates.
The release ZIP for this PR is accessible via:
https://wcblocks.wpcomstaging.com/wp-content/uploads/woocommerce-gutenberg-products-block-9494.zip
Script Dependencies Report
The compare-assets action has detected some changed script dependencies between this branch and trunk. Please review and confirm the following are correct before merging.
| Script Handle | Added | Removed | |
|---|---|---|---|
reviews-frontend.js |
wc-settings, wp-a11y, wp-api-fetch, wp-compose, wp-element, wp-i18n, wp-is-shallow-equal, wp-polyfill |
⚠️ | |
active-filters-frontend.js |
wc-blocks-data-store, wc-price-format, wc-settings, wp-data, wp-element, wp-html-entities, wp-i18n, wp-is-shallow-equal, wp-polyfill, wp-primitives, wp-url |
⚠️ | |
all-products-frontend.js |
lodash, react, wc-blocks-checkout, wc-blocks-data-store, wc-blocks-registry, wc-blocks-shared-context, wc-blocks-shared-hocs, wc-price-format, wc-settings, wp-a11y, wp-api-fetch, wp-autop, wp-blocks, wp-compose, wp-data, wp-deprecated, wp-dom, wp-element, wp-hooks, wp-html-entities, wp-i18n, wp-is-shallow-equal, wp-polyfill, wp-primitives, wp-style-engine, wp-url, wp-warning, wp-wordcount |
⚠️ | |
cart-frontend.js |
lodash, react, wc-blocks-checkout, wc-blocks-data-store, wc-blocks-registry, wc-blocks-shared-context, wc-blocks-shared-hocs, wc-price-format, wc-settings, wp-a11y, wp-api-fetch, wp-autop, wp-blocks, wp-compose, wp-data, wp-deprecated, wp-dom, wp-element, wp-hooks, wp-html-entities, wp-i18n, wp-is-shallow-equal, wp-keycodes, wp-plugins, wp-polyfill, wp-primitives, wp-style-engine, wp-url, wp-warning, wp-wordcount |
⚠️ | |
checkout-frontend.js |
lodash, react, wc-blocks-checkout, wc-blocks-data-store, wc-blocks-registry, wc-blocks-shared-hocs, wc-price-format, wc-settings, wp-a11y, wp-api-fetch, wp-autop, wp-compose, wp-data, wp-deprecated, wp-dom, wp-element, wp-hooks, wp-html-entities, wp-i18n, wp-is-shallow-equal, wp-keycodes, wp-plugins, wp-polyfill, wp-primitives, wp-url, wp-warning, wp-wordcount |
⚠️ | |
filter-wrapper-frontend.js |
lodash, react, wc-blocks-checkout, wc-blocks-data-store, wc-blocks-registry, wc-price-format, wc-settings, wp-a11y, wp-compose, wp-data, wp-deprecated, wp-dom, wp-element, wp-html-entities, wp-i18n, wp-is-shallow-equal, wp-keycodes, wp-polyfill, wp-primitives, wp-style-engine, wp-url, wp-warning |
⚠️ | |
mini-cart-frontend.js |
wc-price-format, wp-i18n, wp-polyfill |
⚠️ | |
rating-filter-frontend.js |
lodash, react, wc-blocks-checkout, wc-blocks-data-store, wc-settings, wp-a11y, wp-compose, wp-data, wp-deprecated, wp-dom, wp-element, wp-i18n, wp-is-shallow-equal, wp-keycodes, wp-polyfill, wp-primitives, wp-url, wp-warning |
⚠️ | |
mini-cart-component-frontend.js |
lodash, react, wc-blocks-checkout, wc-blocks-data-store, wc-blocks-registry, wc-price-format, wc-settings, wp-a11y, wp-autop, wp-compose, wp-data, wp-deprecated, wp-dom, wp-element, wp-hooks, wp-html-entities, wp-i18n, wp-is-shallow-equal, wp-keycodes, wp-polyfill, wp-primitives, wp-style-engine, wp-url, wp-warning, wp-wordcount |
⚠️ |
This comment was automatically generated by the ./github/compare-assets action.
TypeScript Errors Report
- Files with errors: 473
- Total errors: 2268
🎉 🎉 This PR does not introduce new TS errors.
Size Change: 0 B
Total Size: 1.09 MB
ℹ️ View Unchanged
| Filename | Size |
|---|---|
build/active-filters-frontend.js |
8.57 kB |
build/active-filters-wrapper-frontend.js |
7.62 kB |
build/active-filters.js |
7.47 kB |
build/all-products-frontend.js |
11.9 kB |
build/all-products.js |
39.9 kB |
build/all-reviews.js |
7.77 kB |
build/attribute-filter-wrapper--stock-filter-wrapper-frontend.js |
4.04 kB |
build/attribute-filter-wrapper-frontend.js |
4.28 kB |
build/attribute-filter.js |
13.1 kB |
build/blocks-checkout.js |
35.1 kB |
build/breadcrumbs.js |
2.04 kB |
build/cart-blocks/cart-accepted-payment-methods-frontend.js |
1.39 kB |
build/cart-blocks/cart-cross-sells-frontend.js |
253 B |
build/cart-blocks/cart-cross-sells-products--product-price-frontend.js |
2.94 kB |
build/cart-blocks/cart-cross-sells-products-frontend.js |
3.73 kB |
build/cart-blocks/cart-express-payment--checkout-blocks/express-payment-frontend.js |
5.17 kB |
build/cart-blocks/cart-express-payment-frontend.js |
718 B |
build/cart-blocks/cart-items-frontend.js |
301 B |
build/cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend.js |
5.51 kB |
build/cart-blocks/cart-line-items-frontend.js |
1.06 kB |
build/cart-blocks/cart-order-summary-frontend.js |
1.28 kB |
build/cart-blocks/cart-totals-frontend.js |
308 B |
build/cart-blocks/empty-cart-frontend.js |
346 B |
build/cart-blocks/filled-cart-frontend.js |
656 B |
build/cart-blocks/order-summary-coupon-form-frontend.js |
1.63 kB |
build/cart-blocks/order-summary-discount-frontend.js |
2.13 kB |
build/cart-blocks/order-summary-fee-frontend.js |
272 B |
build/cart-blocks/order-summary-heading-frontend.js |
333 B |
build/cart-blocks/order-summary-shipping-frontend.js |
17 kB |
build/cart-blocks/order-summary-subtotal-frontend.js |
273 B |
build/cart-blocks/order-summary-taxes-frontend.js |
434 B |
build/cart-blocks/proceed-to-checkout-frontend.js |
1.38 kB |
build/cart-frontend.js |
29.7 kB |
build/cart.js |
45 kB |
build/catalog-sorting.js |
1.7 kB |
build/checkout-blocks/actions-frontend.js |
1.85 kB |
build/checkout-blocks/billing-address--checkout-blocks/shipping-address-frontend.js |
4.7 kB |
build/checkout-blocks/billing-address-frontend.js |
1.18 kB |
build/checkout-blocks/contact-information-frontend.js |
2.05 kB |
build/checkout-blocks/express-payment-frontend.js |
1.13 kB |
build/checkout-blocks/fields-frontend.js |
330 B |
build/checkout-blocks/order-note-frontend.js |
1.14 kB |
build/checkout-blocks/order-summary-cart-items-frontend.js |
3.69 kB |
build/checkout-blocks/order-summary-coupon-form-frontend.js |
1.79 kB |
build/checkout-blocks/order-summary-discount-frontend.js |
2.29 kB |
build/checkout-blocks/order-summary-fee-frontend.js |
275 B |
build/checkout-blocks/order-summary-frontend.js |
1.28 kB |
build/checkout-blocks/order-summary-shipping-frontend.js |
17 kB |
build/checkout-blocks/order-summary-subtotal-frontend.js |
273 B |
build/checkout-blocks/order-summary-taxes-frontend.js |
435 B |
build/checkout-blocks/payment-frontend.js |
8.29 kB |
build/checkout-blocks/pickup-options-frontend.js |
4.8 kB |
build/checkout-blocks/shipping-address-frontend.js |
1.17 kB |
build/checkout-blocks/shipping-method-frontend.js |
2.59 kB |
build/checkout-blocks/shipping-methods-frontend.js |
6.35 kB |
build/checkout-blocks/terms-frontend.js |
1.56 kB |
build/checkout-blocks/totals-frontend.js |
312 B |
build/checkout-frontend.js |
31.3 kB |
build/checkout.js |
46.3 kB |
build/customer-account.js |
3.18 kB |
build/featured-category.js |
15 kB |
build/featured-product.js |
15.2 kB |
build/filter-wrapper-frontend.js |
14.2 kB |
build/filter-wrapper.js |
2.39 kB |
build/general-style-rtl.css |
1.31 kB |
build/general-style.css |
1.31 kB |
build/handpicked-products.js |
8 kB |
build/legacy-template.js |
6.45 kB |
build/mini-cart-component-frontend.js |
28.4 kB |
build/mini-cart-contents-block/cart-button-frontend.js |
1.73 kB |
build/mini-cart-contents-block/checkout-button-frontend.js |
1.74 kB |
build/mini-cart-contents-block/empty-cart-frontend.js |
360 B |
build/mini-cart-contents-block/filled-cart-frontend.js |
267 B |
build/mini-cart-contents-block/footer-frontend.js |
4.1 kB |
build/mini-cart-contents-block/items-frontend.js |
237 B |
build/mini-cart-contents-block/products-table-frontend.js |
592 B |
build/mini-cart-contents-block/shopping-button-frontend.js |
528 B |
build/mini-cart-contents-block/title-frontend.js |
1.91 kB |
build/mini-cart-contents-block/title-items-counter-frontend.js |
1.6 kB |
build/mini-cart-contents-block/title-label-frontend.js |
1.54 kB |
build/mini-cart-contents.js |
18 kB |
build/mini-cart-frontend.js |
2.66 kB |
build/mini-cart.js |
4.35 kB |
build/price-filter-wrapper-frontend.js |
6.75 kB |
build/price-filter.js |
8.47 kB |
build/price-format.js |
1.19 kB |
build/product-add-to-cart--product-button--product-image--product-price--product-rating--product-sale-bad--49d3ecb2.js |
251 B |
build/product-add-to-cart--product-button--product-image--product-rating--product-title.js |
151 B |
build/product-add-to-cart-frontend.js |
6.51 kB |
build/product-add-to-cart.js |
8.87 kB |
build/product-best-sellers.js |
8.34 kB |
build/product-button--product-image--product-price--product-rating--product-sale-badge--product-sku--prod--5bce0384.js |
963 B |
build/product-button-frontend.js |
2.65 kB |
build/product-button.js |
4.01 kB |
build/product-categories.js |
2.37 kB |
build/product-category.js |
9.35 kB |
build/product-collection.js |
3.39 kB |
build/product-image-frontend.js |
2.63 kB |
build/product-image.js |
4.18 kB |
build/product-new.js |
8.34 kB |
build/product-on-sale.js |
8.68 kB |
build/product-price-frontend.js |
203 B |
build/product-price.js |
1.68 kB |
build/product-query.js |
11.7 kB |
build/product-rating-frontend.js |
2.31 kB |
build/product-rating.js |
999 B |
build/product-results-count.js |
1.66 kB |
build/product-sale-badge-frontend.js |
1.8 kB |
build/product-sale-badge.js |
666 B |
build/product-search.js |
2.63 kB |
build/product-sku-frontend.js |
1.84 kB |
build/product-sku.js |
535 B |
build/product-stock-indicator-frontend.js |
2.02 kB |
build/product-stock-indicator.js |
731 B |
build/product-summary-frontend.js |
2.19 kB |
build/product-summary.js |
904 B |
build/product-tag.js |
8.97 kB |
build/product-template.js |
3.27 kB |
build/product-title-frontend.js |
2.22 kB |
build/product-title.js |
3.69 kB |
build/product-top-rated.js |
8.58 kB |
build/products-by-attribute.js |
9.7 kB |
build/rating-filter-frontend.js |
21.3 kB |
build/rating-filter-wrapper-frontend.js |
6.21 kB |
build/rating-filter.js |
6.89 kB |
build/reviews-by-category.js |
12.1 kB |
build/reviews-by-product.js |
13.2 kB |
build/reviews-frontend.js |
7.11 kB |
build/single-product.js |
11.1 kB |
build/stock-filter-wrapper-frontend.js |
2.98 kB |
build/stock-filter.js |
7.61 kB |
build/store-notices.js |
1.69 kB |
build/vendors--attribute-filter-wrapper--cart-blocks/order-summary-coupon-form--cart-blocks/order-summary--48e1e4bb-frontend.js |
6.82 kB |
build/vendors--attribute-filter-wrapper--cart-blocks/order-summary-shipping--checkout-blocks/billing-addr--d9f38f9d-frontend.js |
4.21 kB |
build/vendors--attribute-filter-wrapper--stock-filter-wrapper-frontend.js |
5.11 kB |
build/vendors--cart-blocks/cart-cross-sells-products--cart-blocks/cart-line-items--cart-blocks/cart-order--3c5fe802-frontend.js |
5.26 kB |
build/vendors--cart-blocks/cart-line-items--checkout-blocks/order-summary-cart-items--mini-cart-contents---233ab542-frontend.js |
3.57 kB |
build/vendors--cart-blocks/order-summary-shipping--checkout-blocks/billing-address--checkout-blocks/order--decc3dc6-frontend.js |
19.4 kB |
build/vendors--checkout-blocks/pickup-options--checkout-blocks/shipping-methods-frontend.js |
8.25 kB |
build/vendors--checkout-blocks/shipping-method-frontend.js |
12.5 kB |
build/vendors--price-filter-wrapper-frontend.js |
2.2 kB |
build/vendors--product-add-to-cart-frontend.js |
7.26 kB |
build/vendors--rating-filter-wrapper-frontend.js |
5.11 kB |
build/wc-blocks-data.js |
22.5 kB |
build/wc-blocks-editor-style-rtl.css |
5.96 kB |
build/wc-blocks-editor-style.css |
5.96 kB |
build/wc-blocks-google-analytics.js |
1.56 kB |
build/wc-blocks-middleware.js |
933 B |
build/wc-blocks-registry.js |
3.15 kB |
build/wc-blocks-shared-context.js |
1.52 kB |
build/wc-blocks-shared-hocs.js |
1.75 kB |
build/wc-blocks-style-rtl.css |
27.8 kB |
build/wc-blocks-style.css |
27.8 kB |
build/wc-blocks-vendors-style-rtl.css |
1.96 kB |
build/wc-blocks-vendors-style.css |
1.96 kB |
build/wc-blocks-vendors.js |
65 kB |
build/wc-blocks.js |
3.7 kB |
build/wc-payment-method-bacs.js |
816 B |
build/wc-payment-method-cheque.js |
811 B |
build/wc-payment-method-cod.js |
909 B |
build/wc-payment-method-paypal.js |
837 B |
build/wc-settings.js |
2.6 kB |
build/wc-shipping-method-pickup-location.js |
30.3 kB |
build/woo-directives-runtime.js |
2.73 kB |
build/woo-directives-vendors.js |
7.91 kB |
Hey @nefeline, would you mind converting this pull request to a draft until it's ready for review? 🙂
Hey @nefeline, would you mind converting this pull request to a draft until it's ready for review? 🙂
Hey @imanish003 ! I'm so sorry: I forgot to create it as a draft, my bad =/ This is now ready for review.
The action hooks for grouped products seems off. Hooks are outputted in wrong locations.
Thanks, @danielwrobert and @roykho, for the reviews; much appreciated! 🥳
@roykho ref.
The action hooks for grouped products seems off. Hooks are outputted in wrong locations.
These actions match precisely what we have within the woocommerce/templates/single-product/add-to-cart/grouped.php template:
Output for the template for grouped products (Twenty-twenty theme):
Output for the Add to Cart with Options block (Twenty-twenty Three theme):
To avoid any confusion for folks relying on those hooks and wishing to migrate to blocks, I believe we should keep them in their original positions.
Hooks are outputted in wrong locations.
~@roykho where should those be output? That looks correct to me but perhaps I'm misunderstanding something.~
Never mind - I needed to refresh, I missed the additional response after from @nefeline.
@danielwrobert - Yeah for example add_action( 'woocommerce_grouped_product_list_after', function () { echo "<div>After grouped product list</div>"; } );
I am expecting that to come AFTER the grouped list of products not before it. But it seems as Patricia pointed out, this is how Woo core is doing it.
Thanks for the review @nerrad !
Regarding:
In general, I'm pretty wary about the number of PHPCS security ignore comments added here which is likely an artifact of splitting things up into separate methods while retaining much of the logic of the original template?
While many of the functions are protected, the risk is that in the future refactoring could call one of these functions with unescaped arguments and introduce a security vulnerability. I would prefer to see no instances of ignoring PHPCS security escaping and refactor to make sure everything is escaped right before output so that any future refactoring won't accidentally introduce a security vuln.
I made a series of changes to remove those phpcs:ignore occurrences: the vast majority of those were ported over from the native WooCommerce templates (so those have already been running on production sites for a while).
Regarding the one added within the add_to_cart_form protected method (which was explicitly introduced on this PR), instead of echoing the form, this method is now returning it. But there's a minor caveat: we are now repeating the 'woocommerce_before_add_to_cart_form' and 'woocommerce_after_add_to_cart_form' actions across all methods for each one of the product types since we are not buffering the output anymore.
With that said, unless folks have any urgent concerns, I'd like to propose addressing the pre-existing exceptions that are still pending for grouped products and the quantity input in separate PRs as those are all in production already (in the core of Woo) and we have some urgency on clearing out the Fatal error and unblocking release 10.3.0. cc: @mikejolley
Edit: See https://github.com/woocommerce/woocommerce-blocks/pull/9494#issuecomment-1560002579 for additional change made following up this msg.
Additional note regarding my previous comment over here:
I'd like to propose addressing the pre-existing exceptions that are still pending for grouped products
Regarding the pre-existing global variable override: it was ditched as I just noticed it's not a requirement for rendering the block.
Thanks @nerrad !
What implications are there. I'm assuming the actions will still only fire once depending on the product type?
Yes, they will still fire once per product type; the only downside is that we are breaking the DRY principle by repeating those actions across different methods, for each one of the product types.
Also, I go thinking, given product types are an extensible concept, one thing I'm wary about with this change is that any overrides that might exist for the templates in a theme (or injected via an extension) will no longer work.
Any refactor of this nature will need to also address the extensibility behaviour (beyond preserving actions) given that product types are an extensible concept. So while there's nothing really wrong with the code itself, I think it's premature to release it as is until we've figured out architecturally how things like the Add to Cart Block will be rendered on the server (will form elements be abstracted for instance) and the path to extensibility for that.
That's an excellent point; we definitely need a fallback for rendering custom product types and allowing extensibility, although it is not super clear to me what level of extensibility we should be providing as well: I'm thinking about a few possible paths, will share more details over here as soon as I have something more concrete to discuss.
Bumping it to the 10.5.0 release.
Thanks @nefeline, for your work! I will close this PR since that, at least for now, we don't see any major benefit with this approach.
Thanks @nefeline, for your work! I will close this PR since that, at least for now
Thanks @gigitux, but this PR is not ready to be closed.
we don't see any major benefit with this approach.
On the contrary: this is the basis for an upcoming discussion regarding block extensibility, and ultimately where we will be heading at some point in the near future.
This PR has been marked as stale because it has not seen any activity within the past 7 days. Our team uses this tool to help surface pull requests that have slipped through review.
If deemed still relevant, the pr can be kept active by ensuring it's up to date with the main branch and removing the stale label.
I'm removing the assigned milestone since before proceeding with this approach, it is necessary to discuss it
This PR has been marked as stale because it has not seen any activity within the past 7 days. Our team uses this tool to help surface pull requests that have slipped through review.