diff --git a/.github/workflows/test-assistant-api-rest-change-tracker.yml b/.github/workflows/test-assistant-api-rest-change-tracker.yml index 6ead4960156..fcc2451d42c 100644 --- a/.github/workflows/test-assistant-api-rest-change-tracker.yml +++ b/.github/workflows/test-assistant-api-rest-change-tracker.yml @@ -59,8 +59,8 @@ jobs: <${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}> *Labels:* ${{ join(github.event.pull_request.labels.*.name, ', ') }} *Monthly Release Milestone:* <${{ github.event.pull_request.milestone.html_url }}|${{ github.event.pull_request.milestone.title }}> (Release Date: ${{ env.MILESTONE_DATE }}) - *WooAF (weekly) Timeline: this PR can be tested from:* ${{ env.TEST_DATE_MESSAGE }} - Please visit the <#${{ secrets.WOO_CORE_RELEASES_SLACK_CHANNEL }}> to obtain the latest WooAF build for testing. + Please check the Milestone above and test using the latest . + If a pre-release build for the stated Milestone does not exist, please use the Nightly build. slack-optional-unfurl_links: false slack-optional-unfurl_media: false diff --git a/.syncpackrc b/.syncpackrc index 14b98ade137..7a4664d5842 100644 --- a/.syncpackrc +++ b/.syncpackrc @@ -88,7 +88,8 @@ "webpack*" ], "packages": [ - "@woocommerce/block-library" + "@woocommerce/block-library", + "@woocommerce/storybook" ], "isIgnored": true }, @@ -199,7 +200,8 @@ "@wordpress/viewport", "@wordpress/interface", "@wordpress/router", - "@wordpress/edit-site" + "@wordpress/edit-site", + "@wordpress/private-apis" ], "packages": [ "@woocommerce/block-templates", diff --git a/changelog.txt b/changelog.txt index 8d70dfca5bc..e34b03260d4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,20 @@ == Changelog == += 9.2.3 2024-08-26 = + +**WooCommerce** + +* Fix - Ensure translation is fully loaded for certain parts of Checkout block. [#50892](https://github.com/woocommerce/woocommerce/pull/50892) + + += 9.2.2 2024-08-22 = + +**WooCommerce** + +* Fix - Revert PR#48731 to address possible issues with plugins using WC's bundled select2 package. [#50854](https://github.com/woocommerce/woocommerce/pull/50854) +* Fix - Partially revert PR#48709 as it could cause issues for some users of the REST API system_status endpoint. [#50881](https://github.com/woocommerce/woocommerce/pull/50881) + + = 9.2.1 2024-08-21 = **WooCommerce** diff --git a/docs/block-theme-development/theming-woo-blocks.md b/docs/block-theme-development/theming-woo-blocks.md index 24bfe9737f9..bba927c9873 100644 --- a/docs/block-theme-development/theming-woo-blocks.md +++ b/docs/block-theme-development/theming-woo-blocks.md @@ -43,7 +43,7 @@ WooCommerce also comes with two specific [block template parts](https://github.c - Mini-Cart (`mini-cart.html`): used inside the Mini-Cart block drawer. - Checkout header (`checkout-header.html`): used as the header in the Checkout template. -Similarly to the templates, they can be overriden by themes by adding a file with the same file name under the `/parts` folder. +Similarly to the templates, they can be overridden by themes by adding a file with the same file name under the `/parts` folder. ### Global styles diff --git a/plugins/woocommerce-blocks/docs/block-references/block-references.md b/docs/building-a-woo-store/block-references.md similarity index 93% rename from plugins/woocommerce-blocks/docs/block-references/block-references.md rename to docs/building-a-woo-store/block-references.md index 3e3f4d1c3db..0cf96318ebc 100644 --- a/plugins/woocommerce-blocks/docs/block-references/block-references.md +++ b/docs/building-a-woo-store/block-references.md @@ -1,9 +1,25 @@ +--- +post_title: Blocks reference +menu_title: Blocks Reference +--- + # Woo Blocks Reference This page lists the Woo blocks included in the package. (Incomplete as there are still blocks that are not using block.json definition). +## Add to Cart with Options - woocommerce/add-to-cart-form + +Display a button so the customer can add a product to their cart. Options will also be displayed depending on product type. e.g. quantity, variation. + +- **Name:** woocommerce/add-to-cart-form +- **Category:** woocommerce-product-elements +- **Ancestor:** +- **Parent:** +- **Supports:** +- **Attributes:** isDescendentOfSingleProductBlock + ## Product Average Rating (Beta) - woocommerce/product-average-rating Display the average rating of a product @@ -26,6 +42,17 @@ Display a call to action button which either adds the product to the cart, or li - **Supports:** align (full, wide), color (link, text, ~~background~~), interactivity, typography (fontSize, lineHeight), ~~html~~ - **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, productId, textAlign, width +## Product Image - woocommerce/product-image + +Display the main product image. + +- **Name:** woocommerce/product-image +- **Category:** woocommerce-product-elements +- **Ancestor:** +- **Parent:** +- **Supports:** +- **Attributes:** aspectRatio, height, imageSizing, isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, productId, saleBadgeAlign, scale, showProductLink, showSaleBadge, width + ## Product Details - woocommerce/product-details Display a product's description, attributes, and reviews. @@ -35,7 +62,7 @@ Display a product's description, attributes, and reviews. - **Ancestor:** - **Parent:** - **Supports:** align, spacing (margin) -- **Attributes:** +- **Attributes:** hideTabTitle ## Product Image Gallery - woocommerce/product-image-gallery @@ -114,6 +141,17 @@ Display related products. - **Supports:** align, ~~reusable~~ - **Attributes:** +## On-Sale Badge - woocommerce/product-sale-badge + +Displays an on-sale badge if the product is on-sale. + +- **Name:** woocommerce/product-sale-badge +- **Category:** woocommerce-product-elements +- **Ancestor:** +- **Parent:** +- **Supports:** +- **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductTemplate, productId + ## Active Filters Controls - woocommerce/active-filters Display the currently active filters. @@ -650,7 +688,7 @@ Renders classic WooCommerce shortcodes. - **Category:** woocommerce - **Ancestor:** - **Parent:** -- **Supports:** color (text, ~~background~~) +- **Supports:** color (text, ~~background~~), ~~inserter~~ - **Attributes:** color, storeOnly ## Customer account - woocommerce/customer-account @@ -717,7 +755,7 @@ Display a button for shoppers to quickly view their cart. - **Ancestor:** - **Parent:** - **Supports:** typography (fontSize), ~~html~~, ~~multiple~~ -- **Attributes:** addToCartBehaviour, cartAndCheckoutRenderStyle, hasHiddenPrice, iconColor, iconColorValue, isPreview, miniCartIcon, priceColor, priceColorValue, productCountColor, productCountColorValue +- **Attributes:** addToCartBehaviour, cartAndCheckoutRenderStyle, hasHiddenPrice, iconColor, iconColorValue, isPreview, miniCartIcon, priceColor, priceColorValue, productCountColor, productCountColorValue, productCountVisibility ## Empty Mini-Cart view - woocommerce/empty-mini-cart-contents-block @@ -1060,29 +1098,18 @@ The contents of this block will display when there are no products found. - **Supports:** align, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** -## Add to Cart with Options - woocommerce/add-to-cart-form - -Display a button so the customer can add a product to their cart. Options will also be displayed depending on product type. e.g. quantity, variation. - -- **Name:** woocommerce/add-to-cart-form -- **Category:** woocommerce-product-elements -- **Ancestor:** -- **Parent:** -- **Supports:** interactivity -- **Attributes:** isDescendentOfSingleProductBlock, quantitySelectorStyle - ## Product Filter (Experimental) - woocommerce/product-filter A block that adds product filters to the product collection. - **Name:** woocommerce/product-filter - **Category:** woocommerce -- **Ancestor:** +- **Ancestor:** woocommerce/product-filters - **Parent:** - **Supports:** ~~html~~, ~~inserter~~, ~~reusable~~ - **Attributes:** attributeId, filterType, heading, isPreview -## Product Filter: Active Filters (Experimental) - woocommerce/product-filter-active +## Filter Options - woocommerce/product-filter-active Display the currently active filters. @@ -1093,7 +1120,7 @@ Display the currently active filters. - **Supports:** color (text, ~~background~~), interactivity, ~~inserter~~ - **Attributes:** displayStyle -## Product Filter: Attribute (Experimental) - woocommerce/product-filter-attribute +## Filter Options - woocommerce/product-filter-attribute Enable customers to filter the product grid by selecting one or more attributes, such as color. @@ -1101,8 +1128,8 @@ Enable customers to filter the product grid by selecting one or more attributes, - **Category:** woocommerce - **Ancestor:** woocommerce/product-filter - **Parent:** -- **Supports:** color (text, ~~background~~), interactivity, ~~inserter~~ -- **Attributes:** attributeId, displayStyle, isPreview, queryType, selectType, showCounts +- **Supports:** color (text, ~~background~~), interactivity, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~inserter~~ +- **Attributes:** attributeId, clearButton, displayStyle, hideEmpty, isPreview, queryType, selectType, showCounts, sortOrder ## Clear (Experimental) - woocommerce/product-filter-clear-button @@ -1115,7 +1142,7 @@ Allows shoppers to reset this filter. - **Supports:** interactivity, ~~inserter~~ - **Attributes:** -## Product Filter: Price (Experimental) - woocommerce/product-filter-price +## Filter Options - woocommerce/product-filter-price Enable customers to filter the product collection by choosing a price range. @@ -1126,7 +1153,7 @@ Enable customers to filter the product collection by choosing a price range. - **Supports:** interactivity, ~~inserter~~ - **Attributes:** inlineInput, showInputFields -## Product Filter: Rating (Experimental) - woocommerce/product-filter-rating +## Filter Options - woocommerce/product-filter-rating Enable customers to filter the product collection by rating. @@ -1137,7 +1164,7 @@ Enable customers to filter the product collection by rating. - **Supports:** color (text, ~~background~~), interactivity, ~~inserter~~ - **Attributes:** className, displayStyle, isPreview, selectType, showCounts -## Product Filter: Stock Status (Experimental) - woocommerce/product-filter-stock-status +## Filter Options - woocommerce/product-filter-stock-status Enable customers to filter the product collection by stock status. @@ -1156,8 +1183,30 @@ Let shoppers filter products displayed on the page. - **Category:** woocommerce - **Ancestor:** - **Parent:** -- **Supports:** align, interactivity, ~~multiple~~ -- **Attributes:** +- **Supports:** align, color (background, text), interactivity, layout (allowJustification, allowOrientation, allowVerticalAlignment, default, ~~allowInheriting~~), spacing (blockGap), typography (fontSize, textAlign), ~~inserter~~, ~~multiple~~ +- **Attributes:** overlay, overlayButtonStyle, overlayIcon, overlayIconSize + +## Product Filters Overlay (Experimental) - woocommerce/product-filters-overlay + +Display product filters in an overlay on top of a page. + +- **Name:** woocommerce/product-filters-overlay +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align, color (background, text), dimensions (), layout (allowCustomContentAndWideSize), spacing (blockGap, padding), typography (), ~~inserter~~, ~~multiple~~ +- **Attributes:** overlayPosition, overlayStyle, style + +## Overlay Navigation (Experimental) - woocommerce/product-filters-overlay-navigation + +Display overlay navigation controls. + +- **Name:** woocommerce/product-filters-overlay-navigation +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filters-overlay,woocommerce/product-filters +- **Parent:** +- **Supports:** align (center, left, right), color (background, text), layout (default, ~~allowEditing~~), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~inserter~~ +- **Attributes:** align, buttonStyle, iconSize, navigationStyle, overlayMode, style, triggerType ## Product Gallery (Beta) - woocommerce/product-gallery diff --git a/docs/cart-and-checkout-blocks/hooks/migrated-hooks.md b/docs/cart-and-checkout-blocks/hooks/migrated-hooks.md index fb25b947531..f7fcc301050 100644 --- a/docs/cart-and-checkout-blocks/hooks/migrated-hooks.md +++ b/docs/cart-and-checkout-blocks/hooks/migrated-hooks.md @@ -10,41 +10,41 @@ Please note that the actions and filters here run on the server side. The client ## Legacy Filters -- [loop_shop_per_page](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#loop_shop_per_page) -- [wc_session_expiration](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#wc_session_expiration) -- [woocommerce_add_cart_item](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_cart_item) -- [woocommerce_add_cart_item_data](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_cart_item_data) -- [woocommerce_add_to_cart_quantity](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_quantity) -- [woocommerce_add_to_cart_sold_individually_quantity](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_sold_individually_quantity) -- [woocommerce_add_to_cart_validation](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_validation) -- [woocommerce_adjust_non_base_location_prices](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_adjust_non_base_location_prices) -- [woocommerce_apply_base_tax_for_local_pickup](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_base_tax_for_local_pickup) -- [woocommerce_apply_individual_use_coupon](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_individual_use_coupon) -- [woocommerce_apply_with_individual_use_coupon](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_with_individual_use_coupon) -- [woocommerce_cart_contents_changed](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_cart_contents_changed) -- [woocommerce_cart_item_permalink](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_cart_item_permalink) -- [woocommerce_get_item_data](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_get_item_data) -- [woocommerce_loop_add_to_cart_args](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_loop_add_to_cart_args) -- [woocommerce_loop_add_to_cart_link](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_loop_add_to_cart_link) -- [woocommerce_new_customer_data](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_new_customer_data) -- [woocommerce_pay_order_product_has_enough_stock](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_pay_order_product_has_enough_stock) -- [woocommerce_pay_order_product_in_stock](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_pay_order_product_in_stock) -- [woocommerce_registration_errors](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_registration_errors) -- [woocommerce_shipping_package_name](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_shipping_package_name) -- [woocommerce_show_page_title](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_show_page_title) -- [woocommerce_single_product_image_thumbnail_html](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_single_product_image_thumbnail_html) +- [loop_shop_per_page](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#loop_shop_per_page) +- [wc_session_expiration](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#wc_session_expiration) +- [woocommerce_add_cart_item](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_cart_item) +- [woocommerce_add_cart_item_data](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_cart_item_data) +- [woocommerce_add_to_cart_quantity](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_quantity) +- [woocommerce_add_to_cart_sold_individually_quantity](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_sold_individually_quantity) +- [woocommerce_add_to_cart_validation](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_validation) +- [woocommerce_adjust_non_base_location_prices](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_adjust_non_base_location_prices) +- [woocommerce_apply_base_tax_for_local_pickup](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_base_tax_for_local_pickup) +- [woocommerce_apply_individual_use_coupon](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_individual_use_coupon) +- [woocommerce_apply_with_individual_use_coupon](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_with_individual_use_coupon) +- [woocommerce_cart_contents_changed](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_cart_contents_changed) +- [woocommerce_cart_item_permalink](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_cart_item_permalink) +- [woocommerce_get_item_data](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_get_item_data) +- [woocommerce_loop_add_to_cart_args](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_loop_add_to_cart_args) +- [woocommerce_loop_add_to_cart_link](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_loop_add_to_cart_link) +- [woocommerce_new_customer_data](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_new_customer_data) +- [woocommerce_pay_order_product_has_enough_stock](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_pay_order_product_has_enough_stock) +- [woocommerce_pay_order_product_in_stock](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_pay_order_product_in_stock) +- [woocommerce_registration_errors](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_registration_errors) +- [woocommerce_shipping_package_name](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_shipping_package_name) +- [woocommerce_show_page_title](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_show_page_title) +- [woocommerce_single_product_image_thumbnail_html](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_single_product_image_thumbnail_html) ## Legacy Actions -- [woocommerce_add_to_cart](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_add_to_cart) -- [woocommerce_after_main_content](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_after_main_content) -- [woocommerce_after_shop_loop](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_after_shop_loop) -- [woocommerce_applied_coupon](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_applied_coupon) -- [woocommerce_archive_description](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_archive_description) -- [woocommerce_before_main_content](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_before_main_content) -- [woocommerce_before_shop_loop](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_before_shop_loop) -- [woocommerce_check_cart_items](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_check_cart_items) -- [woocommerce_created_customer](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_created_customer) -- [woocommerce_no_products_found](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_no_products_found) -- [woocommerce_register_post](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_register_post) -- [woocommerce_shop_loop](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_shop_loop) +- [woocommerce_add_to_cart](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_add_to_cart) +- [woocommerce_after_main_content](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_after_main_content) +- [woocommerce_after_shop_loop](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_after_shop_loop) +- [woocommerce_applied_coupon](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_applied_coupon) +- [woocommerce_archive_description](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_archive_description) +- [woocommerce_before_main_content](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_before_main_content) +- [woocommerce_before_shop_loop](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_before_shop_loop) +- [woocommerce_check_cart_items](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_check_cart_items) +- [woocommerce_created_customer](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_created_customer) +- [woocommerce_no_products_found](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_no_products_found) +- [woocommerce_register_post](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_register_post) +- [woocommerce_shop_loop](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_shop_loop) diff --git a/docs/docs-manifest.json b/docs/docs-manifest.json index 9537ea57ba4..6e8e25bf2e6 100644 --- a/docs/docs-manifest.json +++ b/docs/docs-manifest.json @@ -23,7 +23,7 @@ "menu_title": "Theming for Woo Blocks", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/block-theme-development/theming-woo-blocks.md", - "hash": "1ce763e8afcc7dfdd8c5eca4da799add21dfac48279c08fc7b614071edb67a7d", + "hash": "cec80a34a38b7286be676a35624e2e441f5ccbb1aa318def6afe56a5a2bb6558", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/block-theme-development/theming-woo-blocks.md", "id": "90b16f4143d6db728d5ed6dce2ee2c60bdcfdbf6" }, @@ -70,6 +70,14 @@ "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/building-a-woo-store/configuring-caching-plugins.md", "id": "9f484f8db1111fa6c1b6108d40939c967eea7f47" }, + { + "post_title": "Blocks reference", + "menu_title": "Blocks Reference", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/building-a-woo-store/block-references.md", + "hash": "a33fe5766283aaa70154077692a180319110e133ad430bf8dda3032455bad45c", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/building-a-woo-store/block-references.md", + "id": "1fbe91d7fa4fafaf35f0297e4cee1e7958756aed" + }, { "post_title": "How to add a custom field to simple and variable products", "menu_title": "Add Custom Fields to Products", @@ -259,7 +267,7 @@ "menu_title": "Legacy Hooks", "tags": "reference", "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/cart-and-checkout-blocks/hooks/migrated-hooks.md", - "hash": "025731fc8884e13e09ecc857bee7d669a091f11640e023e40c08ffc521f38964", + "hash": "be4cf6862932c6696568b210e5f6ae4bd5313dacfb66724e23fab830baeb94e1", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/cart-and-checkout-blocks/hooks/migrated-hooks.md", "id": "5264fa45d393327b2c9cffb038c4d3670879d911" } @@ -1119,7 +1127,7 @@ ] }, { - "content": "\nEnsuring the quality of your WooCommerce projects is essential. This section will delve into quality exoectations, best practices, coding standards, and other methodologies to ensure your projects stand out in terms of reliability, efficiency, user experience, and more. \n", + "content": "\nEnsuring the quality of your WooCommerce projects is essential. This section will delve into quality expectations, best practices, coding standards, and other methodologies to ensure your projects stand out in terms of reliability, efficiency, user experience, and more. \n", "category_slug": "quality-and-best-practices", "category_title": "Quality And Best Practices", "posts": [ @@ -1244,7 +1252,72 @@ "id": "b09a572b8a452b6cd795e0985daa85f06e5889fb" } ], - "categories": [] + "categories": [ + { + "content": "\nReviews are an integral part of the online shopping experience, and people installing software pay attention to them. Prospective users of your extensions will likely consider average ratings when making software choices.\n\nMany of today's most popular online review platforms - from Yelp business reviews, to Amazon product reviews - have a range of opinion that can be polarized, with many extremely positive and/or negative reviews, and fewer moderate opinions. This creates a \"J-shaped\" distribution of reviews that isn't as accurate or as helpful as could be.\n\nWooCommerce.com and WordPress.org both feature reviews heavily, and competing extensions having a higher rating likely have the edge in user choice. \n\n## Primary considerations around reviews\n\nRequesting more reviews for a extension with major issues will not generate good reviews, and analyzing existing reviews will help surface areas to address before soliciting reviews.\n\nIt is extremely rare for users of WordPress plugins to leave reviews organically (.2% of users for WordPress.org leave reviews), which means that there's an untapped market of 99.8% of users of the average plugin.\n\nThese plugins are competing with other plugins on the same search terms in the WordPress.org plugin directory, and ratings are a large factor in the ranking algorithm. This is not usually a factor to the same extent on the WooCommerce Marketplace. For instance, WooCommerce's PayPal extension directly competes on all possible keywords with other PayPal extensions on the WordPress.org repository, while it does not compete with other PayPal payments extensions on the Marketplace.\n", + "category_slug": "review-guidelines", + "category_title": "Review Guidelines", + "posts": [ + { + "post_title": "When to request WooCommerce extension reviews", + "menu_title": "When to request reviews", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/quality-and-best-practices/review-guidelines/when-to-request-reviews.md", + "hash": "7a14ceb03ac0a98d84c0d380e44534129198f8ee682e84824cbb6c793da71be8", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/quality-and-best-practices/review-guidelines/when-to-request-reviews.md", + "id": "d40eb30ff49c89545a5918f5b8c08b82e6f8f45a" + }, + { + "post_title": "Utilizing your support team to respond to feedback", + "menu_title": "Utilizing your support team", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/quality-and-best-practices/review-guidelines/utilizing-your-support-team.md", + "hash": "3cadfdd506c2c279c16d411b4037f6ea6cd2fa2dcfad6ffe895e89e61b0e54c0", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/quality-and-best-practices/review-guidelines/utilizing-your-support-team.md", + "id": "f7029c8549d5ed86dd8f9cc27d7a62da539b9ba7" + }, + { + "post_title": "Utilizing WooCommerce extension feature requests", + "menu_title": "Utilizing feature requests", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/quality-and-best-practices/review-guidelines/utilizing-feature-requests.md", + "hash": "28af9f05e0c843a932ba1320d16097c029e71bdeae0dbe02ff95a96eb9d8c1c0", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/quality-and-best-practices/review-guidelines/utilizing-feature-requests.md", + "id": "4efca02b0f14b13a1471f5beec4ef15365f98b17" + }, + { + "post_title": "How to respond to negative WooCommerce extension reviews", + "menu_title": "Responding to negative reviews", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/quality-and-best-practices/review-guidelines/responding-to-negative-reviews.md", + "hash": "306df15c394c6867657e7c68282f16dab5d08e821bbb7e27514abca764262b24", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/quality-and-best-practices/review-guidelines/responding-to-negative-reviews.md", + "id": "7cb825a3830e392aeae853b441a91237f48c3ae5" + }, + { + "post_title": "Notifying users about bug fixes and feature requests", + "menu_title": "Notifying users about bug fixes and feature requests", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/quality-and-best-practices/review-guidelines/notifying-users-about-important-events.md", + "hash": "30b6241ebb32204910980a5dea2399a8fa2e02fa37ea22449edc549f3f14034b", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/quality-and-best-practices/review-guidelines/notifying-users-about-important-events.md", + "id": "9ccc4bf579cc624002478fe8706241f4640d92b8" + }, + { + "post_title": "Miscellaneous guidelines and advice", + "menu_title": "Miscellaneous guidelines", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/quality-and-best-practices/review-guidelines/misc-guidelines.md", + "hash": "c5515db05195cebbe1ed0595dbf2a09623c3b85e1b5b6e1c3628a79b957f8884", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/quality-and-best-practices/review-guidelines/misc-guidelines.md", + "id": "003ece0250c6a0c248019a095f75f3cfedbc290e" + }, + { + "post_title": "How to request WooCommerce extension reviews", + "menu_title": "Requesting reviews", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/quality-and-best-practices/review-guidelines/how-to-request-reviews.md", + "hash": "dfdf5add075777636eb628d25484e93268251437dec0253766c12d80ac82573b", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/quality-and-best-practices/review-guidelines/how-to-request-reviews.md", + "id": "3d0c8bf7339a71198737d19eec7e6d71697b3727" + } + ], + "categories": [] + } + ] }, { "content": "\nUnderstand Woo's reporting capabilities. Learn to generate, understand, and optimize reports to make informed decisions about your WooCommerce projects.\n", @@ -1731,5 +1804,5 @@ "categories": [] } ], - "hash": "b6fab4eae1266824ee3e876c8fa5fd0342f59b4a0e5978f1460afc67d82e6d94" + "hash": "0b0ae9b9ed454ab234a5f053f6efb37bafb3e90f1c98f6488263c019f552697b" } \ No newline at end of file diff --git a/docs/quality-and-best-practices/README.md b/docs/quality-and-best-practices/README.md index ff7323fc62c..f68726dbcc5 100644 --- a/docs/quality-and-best-practices/README.md +++ b/docs/quality-and-best-practices/README.md @@ -4,4 +4,4 @@ category_slug: quality-and-best-practices post_title: Quality and best practices --- -Ensuring the quality of your WooCommerce projects is essential. This section will delve into quality exoectations, best practices, coding standards, and other methodologies to ensure your projects stand out in terms of reliability, efficiency, user experience, and more. +Ensuring the quality of your WooCommerce projects is essential. This section will delve into quality expectations, best practices, coding standards, and other methodologies to ensure your projects stand out in terms of reliability, efficiency, user experience, and more. diff --git a/docs/quality-and-best-practices/review-guidelines/README.md b/docs/quality-and-best-practices/review-guidelines/README.md new file mode 100644 index 00000000000..8e4b57bd566 --- /dev/null +++ b/docs/quality-and-best-practices/review-guidelines/README.md @@ -0,0 +1,19 @@ +--- +category_title: Review Guidelines +category_slug: review-guidelines +post_title: Review Guidelines +--- + +Reviews are an integral part of the online shopping experience, and people installing software pay attention to them. Prospective users of your extensions will likely consider average ratings when making software choices. + +Many of today's most popular online review platforms - from Yelp business reviews, to Amazon product reviews - have a range of opinion that can be polarized, with many extremely positive and/or negative reviews, and fewer moderate opinions. This creates a "J-shaped" distribution of reviews that isn't as accurate or as helpful as could be. + +WooCommerce.com and WordPress.org both feature reviews heavily, and competing extensions having a higher rating likely have the edge in user choice. + +## Primary considerations around reviews + +Requesting more reviews for a extension with major issues will not generate good reviews, and analyzing existing reviews will help surface areas to address before soliciting reviews. + +It is extremely rare for users of WordPress plugins to leave reviews organically (.2% of users for WordPress.org leave reviews), which means that there's an untapped market of 99.8% of users of the average plugin. + +These plugins are competing with other plugins on the same search terms in the WordPress.org plugin directory, and ratings are a large factor in the ranking algorithm. This is not usually a factor to the same extent on the WooCommerce Marketplace. For instance, WooCommerce's PayPal extension directly competes on all possible keywords with other PayPal extensions on the WordPress.org repository, while it does not compete with other PayPal payments extensions on the Marketplace. diff --git a/docs/quality-and-best-practices/review-guidelines/how-to-request-reviews.md b/docs/quality-and-best-practices/review-guidelines/how-to-request-reviews.md new file mode 100644 index 00000000000..a28cc93c503 --- /dev/null +++ b/docs/quality-and-best-practices/review-guidelines/how-to-request-reviews.md @@ -0,0 +1,39 @@ +--- +post_title: How to request WooCommerce extension reviews +menu_title: Requesting reviews +--- + +## Methods of requesting reviews + +### Admin notices + +Admin notices are an industry standard method of requesting reviews, but bombarding the admin dashboard with admin notices is not effective. We recommend using restraint in the design of a notice, as well as limiting to a single notice at a time. It's very easy to overwhelm merchants with too many notices, or too intrusive notices. + +#### Recommendations + +* A good place for an admin notice to review an extension would be to show on the `Plugins` page and extension's settings pages. +* Include a snooze option (or multiple) on your notices with a clear expectation of when the notice will reappear. +* Admin notices should always be always be completely dismissable. They cannot only have a snooze option. +* The options presented in the notice must be phrased carefully to avoid manipulative language. +* Use consistently designed notices so the request for reviews feels like a part of your extension, and looks consistent with WooCommerce's design. + +### Direct contact + +#### Recommendations + +* The most direct route to requesting reviews with the highest chance of being positive is to contact the customer when they are the happiest with the product. +* This can be milestone or time based, following the timing guidelines below. +* The best method for this is either an email or other direct exchange (support chat, call, etc.). This has the highest conversion rate, especially when timed properly so that the customer is happiest. +* This is also extremely effective when you are able to request feedback from specifically qualified merchants, such as merchants that have processed a certain amount with your platform, or who have shipped their first 100 orders using your fulfillment extension, or similar. +* Direct outreach is most likely to be successful if you have ways of targeting users for review requests (merchant account / usage info, etc.), as well as ways to gather the reviews, like sales or marketing teams able to email/call/chat with merchants. + +## Messaging for requesting reviews + +One method of requesting feedback that we recommend is using the NPS style of review solicitation. This can allow for an increase positive reviews as well as providing the opportunity to assist merchants that are struggling. + +NPS-style reviews first ask the user how they rate the product (out of 5 stars), then route them based on their response: + +* If they click 4 or 5 stars, ask them to leave a review. +* If they click 1, 2 or 3, tell them we're here to help & ask them to submit a support ticket. + +Merchants are significantly more likely to leave a review after a positive support interaction with a support rep who explicitly asked for a review. The language "the best way to thank me is to leave a 5 star review that mentions me in it" or similar tends to work very well - people are more willing to help a person than a produc or company. diff --git a/docs/quality-and-best-practices/review-guidelines/misc-guidelines.md b/docs/quality-and-best-practices/review-guidelines/misc-guidelines.md new file mode 100644 index 00000000000..dde408b1400 --- /dev/null +++ b/docs/quality-and-best-practices/review-guidelines/misc-guidelines.md @@ -0,0 +1,14 @@ +--- +post_title: Miscellaneous guidelines and advice +menu_title: Miscellaneous guidelines +--- + +Contributors' names matching search terms directly will rank extremely highly on the WordPress.org plugin repo, which means that having a WordPress.org user named after your business (if that's a search term for your plugin) could tilt the scales over a competing plugin. + +Constant nags and overwhelming the admin dashboard with unnecessary alerts detract from your user experience. + +You can request to have reviews that are not actually feedback removed. Where this might be applicable is if the request is a simple support request, where it's obviously in the wrong spot. 1 star reviews, even if aggressive or angry, are not usually removed. + +Reply to reviews! Thank the giver for offering feedback, acknowledge the issue if needed, ask for more specific feedback, or provide an update when that feedback is addressed! Your reviews are a great window into what your extension's users are actually thinking. + +Having folks close to the extension's development (think developers and project managers) looking at reviews on a regular basis is a good way to ensure the customers voice is heard. 1 star reviews are among the most useful, as long as the issue is understood and addressed (if needed). diff --git a/docs/quality-and-best-practices/review-guidelines/notifying-users-about-important-events.md b/docs/quality-and-best-practices/review-guidelines/notifying-users-about-important-events.md new file mode 100644 index 00000000000..7e2698fd5ec --- /dev/null +++ b/docs/quality-and-best-practices/review-guidelines/notifying-users-about-important-events.md @@ -0,0 +1,18 @@ +--- +post_title: Notifying users about bug fixes and feature requests +menu_title: Notifying users about bug fixes and feature requests +--- + +A bug or a missing feature can be a showstopper for merchants. Bugs that pile up or popular features that are not implemented can lead to negative reviews and/or merchants churning and looking at a competitive solution. + +Bugs are usually reported via support or GitHub, or you can discover them yourselves in testing. + +When a critical bug is found, resolve it within a couple of days (most critical bugs should be resolved within 24 hours) and release a new plugin version promptly. Part of your release process should be to notify all stakeholders (the support team and the merchant affected) about the upcoming release. + +Even though a critical bug is a great source of stress for merchants, a quick resolution makes merchants feel heard and supported - having a reliable business partner, who is keen to help in the most difficult situation, helps build a stronger relationship. Therefore, we usually ask merchants for a 5* review when we deliver a fast solution. + +When you implement a new feature request and ship a new plugin version, you can follow a similar approach to bugs: + +* Notify all stakeholders. +* Update the relevant request in the Feature Requests board, by sharing a public update and marking it as 'Completed'. +* For breaking releases: communicating with your marketing/relations teams to publish updates/newsletters before the release. diff --git a/docs/quality-and-best-practices/review-guidelines/responding-to-negative-reviews.md b/docs/quality-and-best-practices/review-guidelines/responding-to-negative-reviews.md new file mode 100644 index 00000000000..1bac36640da --- /dev/null +++ b/docs/quality-and-best-practices/review-guidelines/responding-to-negative-reviews.md @@ -0,0 +1,33 @@ +--- +post_title: How to respond to negative WooCommerce extension reviews +menu_title: Responding to negative reviews +--- + +An unpleasant event in the merchant's journey can lead them to leave a public, negative review. These events usually are: + +* a problem with the product, +* a missing product feature, +* an unhelpful reply, +* long wait times to receive a reply, or; +* combinations of the above. + +When receiving a negative review, your goal should always be to turn this review around - this sounds tough, but it is really rewarding. + +In the majority of cases, merchants who leave a negative review have first tried contacting support for help. This is useful knowledge, as we can read through the conversation history, understand the issue the merchant experienced and share more details with them when we reach out, even from our first reply. + +The process we have seen work well is: + +* Create a new response (via email, or on a public review) with subject: Regarding your recent review for xxx. +* Start by introducing yourself, for example: "Hey there, This is Andrew from the team that develops xxx". +* Use empathetic language and make it clear that this negative review had an impact on you. For example, "I read your recent review for xxx and I am worried to hear that an issue is preventing you from using this plugin as you had in mind. I'd be happy to help you resolve this!". + +Compare the above sentence with: "I am sorry to hear that you experienced an issue with xxx". "I am sorry to" indicates that you are saddened by an event, but don't necessarily plan to do something about it. In comparison, in "I am worried to hear", worry indicates action. Additionally, "That you experienced an issue" can be interpreted as if the problem is mainly the merchant's fault, whereas language like "an issue is preventing you from using this plugin as you had in mind. I'd be happy to help you resolve this!" implies you and the merchant are on the same team. + +* Share more details, a solution, an idea, a suggestion or an explanation. +* Urge the merchant to update the review, by highlighting how important reviews are for our team and for other merchants. Example language to do this is "We would appreciate it if you could take a couple of minutes to update your review and describe your experience with our product and support. Honest reviews are extremely helpful for other merchants who try to decide if a plugin is a right fit for them. Thank you for your contribution!". +* Include a direct link to the reviews section, so merchants can easily navigate there and change their review. +* On a follow-up communication, if the merchant has changed the review, consider saying something like: "I shared this with the rest of the team and it made everyone's day". + +If the above things are true, sharing some of your procedures with merchants (highlighting how your team emphasizes and thrives on feedback) helps ensure merchants feel like you are part of their team and builds a strong relationship with them. + +Even a merchant that doesn't change their review can offer a mutually beneficial discussion by learning more about their setup and offering some suggestions. These conversations help grow merchants' trust. diff --git a/docs/quality-and-best-practices/review-guidelines/utilizing-feature-requests.md b/docs/quality-and-best-practices/review-guidelines/utilizing-feature-requests.md new file mode 100644 index 00000000000..4a10042be0f --- /dev/null +++ b/docs/quality-and-best-practices/review-guidelines/utilizing-feature-requests.md @@ -0,0 +1,31 @@ +--- +post_title: Utilizing WooCommerce extension feature requests +menu_title: Utilizing feature requests +--- + +It is important to keep track of all feature requests, and have some sort of system of record where anyone can see what kind of feedback the product is receiving over time. + +We recommend a daily or bi-daily check-in, where you: + +* triage new feature requests, +* celebrate positive reviews and; +* act upon negative reviews. + +Carefully maintaining feature request boards (or similar system) is key, as the average board contains a lot of duplicate/spam content, requests about features that have been implemented and requests about features that will likely never be implemented. Poorly maintained boards make merchants feel unheard/neglected. + +This results in more negative reviews on the premise that the product teams were not reading/listening to their feedback. + +We've seen good results with the following procedures: + +Starting with the most affected products, go through all open requests, reply to most/all of them and categorize them as: + +* "Open", for requests that we still want more feedback, +* "Planned", for requests that we plan to implement, +* "Completed", for requests that have already been implemented and; +* "Closed", for requests that we do not plan to implement, as they are not a good fit for the product, for duplicate/spam requests and for requests that were actually support questions. + +Replying to all "Open" requests is the goal, but if that's not attainable currently, make sure to reply to 100% of the requests that are closed. + +For new open requests that arrive as a feature request, discuss/triage them, reply promptly, and assign a status to avoid having the board become unmanaged, and ensure merchants feel (and are) heard. + +In addition to the effect a tidy board has on merchants, it also helps product teams better understand which requests are most wanted and most impactful and then plan work accordingly. diff --git a/docs/quality-and-best-practices/review-guidelines/utilizing-your-support-team.md b/docs/quality-and-best-practices/review-guidelines/utilizing-your-support-team.md new file mode 100644 index 00000000000..80eecb20207 --- /dev/null +++ b/docs/quality-and-best-practices/review-guidelines/utilizing-your-support-team.md @@ -0,0 +1,18 @@ +--- +post_title: Utilizing your support team to respond to feedback +menu_title: Utilizing your support team +--- + +Your support team is usually the primary contact point of merchants when they contact you. Tickets and chats are the best tools we have to converse with merchants, understand pain-points about our software, listen to their feedback and analyze their feature requests. Collectively, support teams have a great understanding of the products and how people use them. This information is essential to be transferred over to product and engineering teams. + +We recommend that you take the following steps to best utilize your support team: + +* Create a strict internal SLA where support team requests are answered by product or engineering teams. +* Ensure you have a way for your support team to effectively report bugs to your product and engineering teams. +* When responding to your support team, avoid super-short answers, and try to explain the answer simply and concisely. This will allow the support agent to copy/paste your answer to the merchant. +* Avoid replying with statements like "no, this is not possible" or "no, this feature will not be implemented" without providing additional context about technical or product limitations. +* Regularly dedicate additional time to implement a short custom code snippets or to provide in-depth technical details about how a custom project would be implemented so that merchants can reach a solution faster if they decide to hire a dedicated WooCommerce developer. A small effort can go a long way to amaze merchants and reveal an opportunity to request a 5 star review. +* Keep support in the loop when they report a bug or request a new feature. When you release a new product version, we always consider the impact it can have on support. +* Work closely with your support team. For example, consider having a feedback hangout call every month where you can discuss product feedback and planned improvements. + +With these kinds of practices in place, support teams are more willing to share feedback, issues, concerns, and questions with us. This helps maintain a closer relationship with merchants and identify pain-points early, before they become a reason for them to churn. diff --git a/docs/quality-and-best-practices/review-guidelines/when-to-request-reviews.md b/docs/quality-and-best-practices/review-guidelines/when-to-request-reviews.md new file mode 100644 index 00000000000..b4cdb2e01d2 --- /dev/null +++ b/docs/quality-and-best-practices/review-guidelines/when-to-request-reviews.md @@ -0,0 +1,24 @@ +--- +post_title: When to request WooCommerce extension reviews +menu_title: When to request reviews +--- + +The best approach to increasing our top-star reviews is to identify key moments in the merchant's journey, when they are more likely to leave a review and actively request for it. + +The most distinct moments for most of our use cases are: + +* When merchants feel helpless, lost, frustrated and we are able to help, +* When merchants find a bug in our code and we quickly ship a fix, +* When merchants need a feature and we notify them when it is shipped, +* When merchants feel alone and we make them feel heard and; +* When merchants contact with a question and we go out of our way to provide them with top-notch support, even if this means slightly stepping outside the official boundaries of a support policy. + +Think about who is seeing the review request, and what they are doing at that time. Showing a request to a fulfillment worker just trying to ship an order isn't likely going to work well. + +Outreach after a milestone works really well. Some language we've used before is "Congratulations on your xxth sale! We're delighted that WooPayments facilitated this milestone. Would you consider sharing your experience and encouraging others by reviewing our extension?". + +Another way to optimally time a review request would be to setup a prompt that aligns with use patterns. For instance, if you know that most of your merchants use your extension daily, you would likely send a review request sooner than a extension that most merchants interact very sparingly with. + +SaaS/Connector extensions need to be particularly careful about requesting ratings correctly, as they are the most likely to be overlooked unless there is an issue, leading to skewed ratings not representative of the actual extension. + +Consider requesting feedback at the end of every single support interaction, especially in the WordPress.org support forums. One of the largest barriers to leaving a review is the requirement of a user being logged into WooCommerce.com (or WordPress.org), and the WordPress.org support forums present a good opportunity to gather these reviews. By being highly responsive in the public support forum and solving issues there, users are already logged in and able to immediately leave a review (after being requested to!). diff --git a/plugins/woocommerce/changelog/50354-fix-survey_ces b/packages/js/components/changelog/50993-fix-some-comment-typos similarity index 50% rename from plugins/woocommerce/changelog/50354-fix-survey_ces rename to packages/js/components/changelog/50993-fix-some-comment-typos index 36fc17908b2..3dce9f8d32b 100644 --- a/plugins/woocommerce/changelog/50354-fix-survey_ces +++ b/packages/js/components/changelog/50993-fix-some-comment-typos @@ -1,4 +1,4 @@ Significance: patch Type: update -CYS: Improve tracking survey \ No newline at end of file +Comment: Fix some comment typos. diff --git a/packages/js/components/changelog/add-onclick-plugins b/packages/js/components/changelog/add-onclick-plugins new file mode 100644 index 00000000000..9be31ae01f2 --- /dev/null +++ b/packages/js/components/changelog/add-onclick-plugins @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Add optional onclick to Plugins component diff --git a/packages/js/components/src/plugins/index.tsx b/packages/js/components/src/plugins/index.tsx index fc3971f0346..df2561ea28f 100644 --- a/packages/js/components/src/plugins/index.tsx +++ b/packages/js/components/src/plugins/index.tsx @@ -19,6 +19,7 @@ type PluginsProps = { response: InstallPluginsResponse ) => void; onError: ( errors: unknown, response: InstallPluginsResponse ) => void; + onClick?: () => void; onSkip?: () => void; skipText?: string; autoInstall?: boolean; @@ -37,6 +38,7 @@ export const Plugins = ( { onAbort, onComplete, onError = () => null, + onClick = () => null, pluginSlugs = [ 'woocommerce-services' ], onSkip, installText = __( 'Install & enable', 'woocommerce' ), @@ -159,6 +161,7 @@ export const Plugins = ( { } disabled={ isRequesting && hasBeenClicked } onClick={ () => { + onClick(); setHasBeenClicked( true ); installAndActivate(); } } diff --git a/packages/js/components/src/tree-select-control/index.scss b/packages/js/components/src/tree-select-control/index.scss index bec8900acfa..a2574fd39d9 100644 --- a/packages/js/components/src/tree-select-control/index.scss +++ b/packages/js/components/src/tree-select-control/index.scss @@ -215,7 +215,7 @@ $muriel-box-shadow-8dp: 0 5px 5px -3px rgb(0 0 0 / 20%), } // At the time of this comment, it was discovered that this component has - // the same classnames as the WP Components Checkbox, without it being a code depedency. + // the same class names as the WP Components Checkbox, without it being a code dependency. // This caused some visual breakages when changes happened in WP 6.6, and // the rules have been copied over from WP Components styles of 6.5.1. // https://github.com/WordPress/gutenberg/blob/403b4b8d014ef7f6edc15c822e455e109bf49c6d/packages/components/src/checkbox-control/style.scss#L4 diff --git a/packages/js/data/changelog/add-react-main-payments-settings-screen b/packages/js/data/changelog/add-react-main-payments-settings-screen new file mode 100644 index 00000000000..0aa0b00a417 --- /dev/null +++ b/packages/js/data/changelog/add-react-main-payments-settings-screen @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix payment store selector type diff --git a/packages/js/data/changelog/add-stripe-tax-to task b/packages/js/data/changelog/add-stripe-tax-to task new file mode 100644 index 00000000000..44d5916dff9 --- /dev/null +++ b/packages/js/data/changelog/add-stripe-tax-to task @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Add stripe tax status to task type diff --git a/packages/js/data/src/onboarding/types.ts b/packages/js/data/src/onboarding/types.ts index 1e62f7036ca..a39fa627e63 100644 --- a/packages/js/data/src/onboarding/types.ts +++ b/packages/js/data/src/onboarding/types.ts @@ -29,8 +29,10 @@ export type TaskType = { badge?: string; additionalData?: { woocommerceTaxCountries?: string[]; + stripeTaxCountries?: string[]; taxJarActivated?: boolean; avalaraActivated?: boolean; + stripeTaxActivated?: boolean; woocommerceTaxActivated?: boolean; woocommerceShippingActivated?: boolean; }; diff --git a/packages/js/data/src/payment-gateways/index.ts b/packages/js/data/src/payment-gateways/index.ts index b9c74e18f19..afd3e78c926 100644 --- a/packages/js/data/src/payment-gateways/index.ts +++ b/packages/js/data/src/payment-gateways/index.ts @@ -13,7 +13,7 @@ import * as resolvers from './resolvers'; import * as selectors from './selectors'; import reducer from './reducer'; import { STORE_KEY } from './constants'; -import { WPDataActions } from '../types'; +import { WPDataSelectors } from '../types'; import { PromiseifySelectors } from '../types/promiseify-selectors'; export const PAYMENT_GATEWAYS_STORE_NAME = STORE_KEY; @@ -33,7 +33,7 @@ declare module '@wordpress/data' { ): DispatchFromMap< typeof actions >; function select( key: typeof STORE_KEY - ): SelectFromMap< typeof selectors > & WPDataActions; + ): SelectFromMap< typeof selectors > & WPDataSelectors; function resolveSelect( key: typeof STORE_KEY ): PromiseifySelectors< SelectFromMap< typeof selectors > >; diff --git a/packages/js/date/changelog/50047-fix-typos b/packages/js/date/changelog/50047-fix-typos new file mode 100644 index 00000000000..e5d2f2b3db2 --- /dev/null +++ b/packages/js/date/changelog/50047-fix-typos @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Comment: Fix comment typos across various files. diff --git a/packages/js/date/src/test/index.ts b/packages/js/date/src/test/index.ts index 7c47f6a2182..87f2a653107 100644 --- a/packages/js/date/src/test/index.ts +++ b/packages/js/date/src/test/index.ts @@ -1052,7 +1052,7 @@ describe( 'getStoreTimeZoneMoment', () => { expect( utcOffset ).not.toHaveBeenCalled(); } ); - it( 'should use the utc offest when it is set', () => { + it( 'should use the utc offset when it is set', () => { global.window.wcSettings = { timeZone: '+06:00', }; diff --git a/packages/js/expression-evaluation/changelog/50047-fix-typos b/packages/js/expression-evaluation/changelog/50047-fix-typos new file mode 100644 index 00000000000..e5d2f2b3db2 --- /dev/null +++ b/packages/js/expression-evaluation/changelog/50047-fix-typos @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Comment: Fix comment typos across various files. diff --git a/packages/js/expression-evaluation/package.json b/packages/js/expression-evaluation/package.json index 0361729ee1c..7ebe4e2fb67 100644 --- a/packages/js/expression-evaluation/package.json +++ b/packages/js/expression-evaluation/package.json @@ -8,7 +8,7 @@ "wordpress", "woocommerce", "expression", - "evalution" + "evaluation" ], "engines": { "node": "^20.11.1", diff --git a/packages/js/number/changelog/50047-fix-typos b/packages/js/number/changelog/50047-fix-typos new file mode 100644 index 00000000000..e5d2f2b3db2 --- /dev/null +++ b/packages/js/number/changelog/50047-fix-typos @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Comment: Fix comment typos across various files. diff --git a/packages/js/number/changelog/fix-misspelling-in-parse-number-tests b/packages/js/number/changelog/fix-misspelling-in-parse-number-tests new file mode 100644 index 00000000000..25ded53e90c --- /dev/null +++ b/packages/js/number/changelog/fix-misspelling-in-parse-number-tests @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Fix typos in parse number tests diff --git a/packages/js/number/src/test/index.ts b/packages/js/number/src/test/index.ts index b5d2ac089e3..f2db08195dc 100644 --- a/packages/js/number/src/test/index.ts +++ b/packages/js/number/src/test/index.ts @@ -50,7 +50,7 @@ describe( 'numberFormat', () => { } ); describe( 'parseNumber', () => { - it( 'should remove thousand seperator before parsing number', () => { + it( 'should remove thousand separator before parsing number', () => { const config = { decimalSeparator: ',', thousandSeparator: '.', @@ -59,7 +59,7 @@ describe( 'parseNumber', () => { expect( parseNumber( config, '12.345,679' ) ).toBe( '12345.679' ); } ); - it( 'supports empty string as the thousandSeperator', () => { + it( 'supports empty string as the thousandSeparator', () => { const config = { decimalSeparator: ',', thousandSeparator: '', @@ -68,7 +68,7 @@ describe( 'parseNumber', () => { expect( parseNumber( config, '12345,679' ) ).toBe( '12345.679' ); } ); - it( 'supports empty string as the decimalSeperator', () => { + it( 'supports empty string as the decimalSeparator', () => { const config = { decimalSeparator: '', thousandSeparator: ',', diff --git a/plugins/woocommerce/changelog/50196-add-improve_tracking_survey b/packages/js/product-editor/changelog/50993-fix-some-comment-typos similarity index 50% rename from plugins/woocommerce/changelog/50196-add-improve_tracking_survey rename to packages/js/product-editor/changelog/50993-fix-some-comment-typos index 36fc17908b2..3dce9f8d32b 100644 --- a/plugins/woocommerce/changelog/50196-add-improve_tracking_survey +++ b/packages/js/product-editor/changelog/50993-fix-some-comment-typos @@ -1,4 +1,4 @@ Significance: patch Type: update -CYS: Improve tracking survey \ No newline at end of file +Comment: Fix some comment typos. diff --git a/packages/js/product-editor/changelog/add-initial_product_data_views_screen b/packages/js/product-editor/changelog/add-initial_product_data_views_screen new file mode 100644 index 00000000000..9ad9bd3724f --- /dev/null +++ b/packages/js/product-editor/changelog/add-initial_product_data_views_screen @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add new product data views component for use on new product data views page. diff --git a/packages/js/product-editor/changelog/fix-misspelling-in-AligmentToolbarButton-func-name b/packages/js/product-editor/changelog/fix-misspelling-in-AligmentToolbarButton-func-name new file mode 100644 index 00000000000..3cc637e471c --- /dev/null +++ b/packages/js/product-editor/changelog/fix-misspelling-in-AligmentToolbarButton-func-name @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Fix typo in AligmentToolbarButton function name diff --git a/packages/js/product-editor/changelog/fix-misspelling-in-handleAdviceCardDismiss-func-name b/packages/js/product-editor/changelog/fix-misspelling-in-handleAdviceCardDismiss-func-name new file mode 100644 index 00000000000..2f54b48b2f0 --- /dev/null +++ b/packages/js/product-editor/changelog/fix-misspelling-in-handleAdviceCardDismiss-func-name @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Fix typo in handleAdviceCardDissmiss function name diff --git a/packages/js/product-editor/changelog/fix-misspelling-in-noticeDimissed-ref b/packages/js/product-editor/changelog/fix-misspelling-in-noticeDimissed-ref new file mode 100644 index 00000000000..f9fda3b1ea1 --- /dev/null +++ b/packages/js/product-editor/changelog/fix-misspelling-in-noticeDimissed-ref @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Fix typo in noticeDismissed ref diff --git a/plugins/woocommerce/changelog/tweak-add-gtin-ld-json b/packages/js/product-editor/changelog/fix-toggle-misspelling-in-class-names similarity index 51% rename from plugins/woocommerce/changelog/tweak-add-gtin-ld-json rename to packages/js/product-editor/changelog/fix-toggle-misspelling-in-class-names index da32ee9c08e..cdef9bd7270 100644 --- a/plugins/woocommerce/changelog/tweak-add-gtin-ld-json +++ b/packages/js/product-editor/changelog/fix-toggle-misspelling-in-class-names @@ -1,4 +1,4 @@ Significance: minor Type: tweak -Add GTIN in structured data +Fix toogle typo in class names diff --git a/packages/js/product-editor/package.json b/packages/js/product-editor/package.json index 1496fa6de08..ba2ec86f116 100644 --- a/packages/js/product-editor/package.json +++ b/packages/js/product-editor/package.json @@ -71,6 +71,7 @@ "@wordpress/media-utils": "wp-6.0", "@wordpress/plugins": "wp-6.0", "@wordpress/preferences": "wp-6.0", + "@wordpress/private-apis": "^1.6.0", "@wordpress/url": "wp-6.0", "classnames": "^2.3.2", "dompurify": "^2.4.7", diff --git a/packages/js/product-editor/src/blocks/generic/linked-product-list/edit.tsx b/packages/js/product-editor/src/blocks/generic/linked-product-list/edit.tsx index 96a3767afa1..12636655cd8 100644 --- a/packages/js/product-editor/src/blocks/generic/linked-product-list/edit.tsx +++ b/packages/js/product-editor/src/blocks/generic/linked-product-list/edit.tsx @@ -259,7 +259,7 @@ export function LinkedProductListBlockEdit( { setLinkedProductIds( newLinkedProducts ); } - function handleAdviceCardDissmiss() { + function handleAdviceCardDismiss() { recordEvent( 'linked_products_placeholder_dismiss', { source: TRACKS_SOURCE, field: property, @@ -297,7 +297,7 @@ export function LinkedProductListBlockEdit( { tip={ emptyState.tip } dismissPreferenceId={ `woocommerce-product-${ property }-advice-card-dismissed` } isDismissible={ emptyState.isDismissible } - onDismiss={ handleAdviceCardDissmiss } + onDismiss={ handleAdviceCardDismiss } > diff --git a/packages/js/product-editor/src/blocks/generic/text-area/edit.tsx b/packages/js/product-editor/src/blocks/generic/text-area/edit.tsx index 163c9fcb927..7c0430ef478 100644 --- a/packages/js/product-editor/src/blocks/generic/text-area/edit.tsx +++ b/packages/js/product-editor/src/blocks/generic/text-area/edit.tsx @@ -18,7 +18,7 @@ import type { TextAreaBlockEditAttributes, TextAreaBlockEditProps, } from './types'; -import AligmentToolbarButton from './toolbar/toolbar-button-alignment'; +import AlignmentToolbarButton from './toolbar/toolbar-button-alignment'; import { useClearSelectedBlockOnBlur } from '../../../hooks/use-clear-selected-block-on-blur'; import useProductEntityProp from '../../../hooks/use-product-entity-prop'; import { Label } from '../../../components/label/label'; @@ -100,7 +100,7 @@ export function TextAreaBlockEdit( {
{ isRichTextMode && ( - diff --git a/packages/js/product-editor/src/blocks/generic/text-area/toolbar/toolbar-button-alignment/index.tsx b/packages/js/product-editor/src/blocks/generic/text-area/toolbar/toolbar-button-alignment/index.tsx index 9b0e48d6c94..5ca9d283d31 100644 --- a/packages/js/product-editor/src/blocks/generic/text-area/toolbar/toolbar-button-alignment/index.tsx +++ b/packages/js/product-editor/src/blocks/generic/text-area/toolbar/toolbar-button-alignment/index.tsx @@ -38,7 +38,7 @@ export const ALIGNMENT_CONTROLS = [ }, ]; -export default function AligmentToolbarButton( { +export default function AlignmentToolbarButton( { align, setAlignment, }: AlignmentControl ) { diff --git a/packages/js/product-editor/src/blocks/product-fields/downloads/downloads-menu/downloads-menu.tsx b/packages/js/product-editor/src/blocks/product-fields/downloads/downloads-menu/downloads-menu.tsx index 3bf0ff3f2d5..e90a3921cf7 100644 --- a/packages/js/product-editor/src/blocks/product-fields/downloads/downloads-menu/downloads-menu.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/downloads/downloads-menu/downloads-menu.tsx @@ -33,7 +33,7 @@ export function DownloadsMenu( { icon={ isOpen ? chevronUp : chevronDown } variant="secondary" onClick={ onToggle } - className="woocommerce-downloads-menu__toogle" + className="woocommerce-downloads-menu__toggle" > { __( 'Add new', 'woocommerce' ) } diff --git a/packages/js/product-editor/src/blocks/product-fields/downloads/downloads-menu/style.scss b/packages/js/product-editor/src/blocks/product-fields/downloads/downloads-menu/style.scss index fe56bd2c248..0cec19b4642 100644 --- a/packages/js/product-editor/src/blocks/product-fields/downloads/downloads-menu/style.scss +++ b/packages/js/product-editor/src/blocks/product-fields/downloads/downloads-menu/style.scss @@ -1,5 +1,5 @@ .woocommerce-downloads-menu { - &__toogle { + &__toggle { flex-direction: row-reverse; > span { diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx index df6fb636d48..7e8b0d4d138 100644 --- a/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx @@ -34,7 +34,7 @@ export function Edit( { attributes, context: { isInSelectedTab }, }: ProductEditorBlockEditProps< VariationOptionsBlockAttributes > ) { - const noticeDimissed = useRef( false ); + const noticeDismissed = useRef( false ); const { invalidateResolution } = useDispatch( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME ); @@ -107,7 +107,7 @@ export function Edit( { */ if ( totalCountWithoutPrice > 0 && - ! noticeDimissed.current && + ! noticeDismissed.current && productStatus !== 'publish' && // New status. newData?.status === 'publish' @@ -198,7 +198,7 @@ export function Edit( { ref={ variationTableRef as React.Ref< HTMLDivElement > } noticeText={ noticeText } onNoticeDismiss={ () => { - noticeDimissed.current = true; + noticeDismissed.current = true; updateUserPreferences( { variable_items_without_price_notice_dismissed: { ...( itemsWithoutPriceNoticeDismissed || {} ), diff --git a/packages/js/product-editor/src/components/custom-fields/custom-field-name-control/custom-field-name-control.tsx b/packages/js/product-editor/src/components/custom-fields/custom-field-name-control/custom-field-name-control.tsx index 44eb7fd03c2..94818f40ae3 100644 --- a/packages/js/product-editor/src/components/custom-fields/custom-field-name-control/custom-field-name-control.tsx +++ b/packages/js/product-editor/src/components/custom-fields/custom-field-name-control/custom-field-name-control.tsx @@ -26,7 +26,7 @@ import type { CustomFieldNameControlProps } from './types'; * the arbitrary value into an option so it can be selected as * a valid value * - * @param search The seraching criteria. + * @param search The search criteria. * @return The list of filtered custom field names as a Promise. */ async function searchCustomFieldNames( search?: string ) { diff --git a/packages/js/product-editor/src/components/variations-table/styles.scss b/packages/js/product-editor/src/components/variations-table/styles.scss index 9e78670f16a..a58f35e4c3f 100644 --- a/packages/js/product-editor/src/components/variations-table/styles.scss +++ b/packages/js/product-editor/src/components/variations-table/styles.scss @@ -162,7 +162,7 @@ gap: $gap-smaller; margin-right: $gap-smallest; - .variations-actions-menu__toogle:disabled { + .variations-actions-menu__toggle:disabled { cursor: not-allowed; } diff --git a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/multiple-update-menu.tsx b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/multiple-update-menu.tsx index 8c5f6e74fa7..12c3eb0b0a4 100644 --- a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/multiple-update-menu.tsx +++ b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/multiple-update-menu.tsx @@ -35,7 +35,7 @@ export function MultipleUpdateMenu( { icon={ isOpen ? chevronUp : chevronDown } variant="secondary" onClick={ onToggle } - className="variations-actions-menu__toogle" + className="variations-actions-menu__toggle" > { __( 'Quick update', 'woocommerce' ) } diff --git a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/styles.scss b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/styles.scss index 99143614edc..7956e681575 100644 --- a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/styles.scss +++ b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/styles.scss @@ -1,5 +1,5 @@ .variations-actions-menu { - &__toogle { + &__toggle { flex-direction: row-reverse; > span { diff --git a/packages/js/product-editor/src/index.ts b/packages/js/product-editor/src/index.ts index be6145a3587..6b4862e1408 100644 --- a/packages/js/product-editor/src/index.ts +++ b/packages/js/product-editor/src/index.ts @@ -35,6 +35,11 @@ export * from './contexts/validation-context/types'; export { EditorLoadingContext as __experimentalEditorLoadingContext } from './contexts/editor-loading-context'; export { PostTypeContext } from './contexts/post-type-context'; +/** + * Product data views page. + */ +export * from './products'; + // Init the store registerProductEditorUiStore(); diff --git a/packages/js/product-editor/src/lock-unlock.ts b/packages/js/product-editor/src/lock-unlock.ts new file mode 100644 index 00000000000..8c585c29833 --- /dev/null +++ b/packages/js/product-editor/src/lock-unlock.ts @@ -0,0 +1,10 @@ +/** + * External dependencies + */ +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; + +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.', + '@wordpress/edit-site' + ); diff --git a/packages/js/product-editor/src/products-app/index.tsx b/packages/js/product-editor/src/products-app/index.tsx new file mode 100644 index 00000000000..69bef7fb2af --- /dev/null +++ b/packages/js/product-editor/src/products-app/index.tsx @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { + UnsavedChangesWarning, + // @ts-expect-error No types for this exist yet. + privateApis as editorPrivateApis, +} from '@wordpress/editor'; + +/** + * Internal dependencies + */ +import { unlock } from '../lock-unlock'; + +const { RouterProvider } = unlock( routerPrivateApis ); +const { GlobalStylesProvider } = unlock( editorPrivateApis ); + +function ProductsLayout() { + return
Initial Products Layout
; +} + +export function ProductsApp() { + return ( + + + + + + + ); +} diff --git a/packages/js/product-editor/src/products.scss b/packages/js/product-editor/src/products.scss new file mode 100644 index 00000000000..9a1199c1c33 --- /dev/null +++ b/packages/js/product-editor/src/products.scss @@ -0,0 +1,40 @@ +@include wordpress-admin-schemes(); + +.woocommerce_page_woocommerce-products-dashboard #wpadminbar, +.woocommerce_page_woocommerce-products-dashboard #adminmenumain { + display: none; +} +.woocommerce_page_woocommerce-products-dashboard #wpcontent { + margin-left: 0; +} +body.woocommerce_page_woocommerce-products-dashboard + #woocommerce-products-dashboard { + @include wp-admin-reset("#woocommerce-products-dashboard"); + @include reset; + display: block !important; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + min-height: 100vh; +} + +body.js.is-fullscreen-mode { + @include break-medium { + // Reset the html.wp-topbar padding. + // Because this uses negative margins, we have to compensate for the height. + margin-top: -$admin-bar-height; + height: calc(100% + #{$admin-bar-height}); + + #adminmenumain, + #wpadminbar { + display: none; + } + + #wpcontent, + #wpfooter { + margin-left: 0; + } + } +} diff --git a/packages/js/product-editor/src/products.tsx b/packages/js/product-editor/src/products.tsx new file mode 100644 index 00000000000..016ba882661 --- /dev/null +++ b/packages/js/product-editor/src/products.tsx @@ -0,0 +1,53 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + StrictMode, + Suspense, + createElement, + // @ts-expect-error createRoot is available. + createRoot, + lazy, +} from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { getGutenbergVersion } from './utils/get-gutenberg-version'; + +const ProductsApp = lazy( () => + import( './products-app' ).then( ( module ) => ( { + default: module.ProductsApp, + } ) ) +); + +/** + * Initializes the "Products Dashboard". + * + * @param {string} id DOM element id. + */ +export function initializeProductsDashboard( id: string ) { + const target = document.getElementById( id ); + const root = createRoot( target ); + const isGutenbergEnabled = getGutenbergVersion() > 0; + + root.render( + + { isGutenbergEnabled ? ( + + + + ) : ( +
+ { __( + 'Please enabled Gutenberg for this feature', + 'woocommerce' + ) } +
+ ) } +
+ ); + + return root; +} diff --git a/packages/js/product-editor/src/style.scss b/packages/js/product-editor/src/style.scss index ce54a3a1c3a..e8dde242c74 100644 --- a/packages/js/product-editor/src/style.scss +++ b/packages/js/product-editor/src/style.scss @@ -60,3 +60,5 @@ /* Hooks */ @import "hooks/use-draggable/styles.scss"; + +@import "products.scss"; diff --git a/packages/js/product-editor/typings/index.d.ts b/packages/js/product-editor/typings/index.d.ts index daf2d188e73..92aeddf3d87 100644 --- a/packages/js/product-editor/typings/index.d.ts +++ b/packages/js/product-editor/typings/index.d.ts @@ -25,9 +25,21 @@ declare module '@wordpress/core-data' { name: string, id: number | string, options?: { enabled: boolean } - ): { record: T, editedRecord: T, isResolving: boolean, hasResolved: boolean }; + ): { + record: T; + editedRecord: T; + isResolving: boolean; + hasResolved: boolean; + }; } declare module '@wordpress/keyboard-shortcuts' { - function useShortcut(name: string, callback: (event: KeyboardEvent) => void): void; + function useShortcut( + name: string, + callback: ( event: KeyboardEvent ) => void + ): void; const store; } + +declare module '@wordpress/router' { + const privateApis; +} diff --git a/packages/js/remote-logging/README.md b/packages/js/remote-logging/README.md index 46a3b9aa45f..49dac108054 100644 --- a/packages/js/remote-logging/README.md +++ b/packages/js/remote-logging/README.md @@ -2,6 +2,12 @@ A remote logging package for Automattic based projects. This package provides error tracking and logging capabilities, with support for rate limiting, stack trace formatting, and customizable error filtering. +## Installation + +```bash +npm install @woocommerce/remote-logging --save +``` + ## Description The WooCommerce Remote Logging package offers the following features: @@ -44,16 +50,42 @@ The WooCommerce Remote Logging package offers the following features: } ``` +## Remote Logging Conditions + +Remote logging is subject to the following conditions: + +1. **Remote Logging Enabled**: The package checks `window.wcSettings.isRemoteLoggingEnabled` to determine if the feature should be enabled. The value is set via PHP and passed to JS as a boolean. It requires tracks to be enabled and a few other conditions internally. Please see the [RemoteLogger.php](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Internal/Logging/RemoteLogger.php) for more details. + +2. **Non-Development Environment**: It also checks `process.env.NODE_ENV` to ensure logging only occurs in non-development environments. + +If either of these conditions are not met (Tracks is not enabled or the environment is development), no logs will be transmitted to the remote server. + +## API Reference + +- `init(config: RemoteLoggerConfig): void`: Initializes the remote logger with the given configuration. +- `log(severity: LogSeverity, message: string, extraData?: object): Promise`: Logs a message with the specified severity and optional extra data. +- `captureException(error: Error, extraData?: object): void`: Captures an error and sends it to the remote API. + +For more detailed information about types and interfaces, refer to the source code and inline documentation. + ## Customization -You can customize the behavior of the remote logger using WordPress filters: -- `woocommerce_remote_logging_should_send_error`: Control whether an error should be sent to the remote API. -- `woocommerce_remote_logging_error_data`: Modify the error data before sending it to the remote API. -- `woocommerce_remote_logging_log_endpoint`: Customize the endpoint URL for sending log messages. -- `woocommerce_remote_logging_js_error_endpoint`: Customize the endpoint URL for sending JavaScript errors. +You can customize the behavior of the remote logger using WordPress filters. Here are the available filters: -### Example +### `woocommerce_remote_logging_should_send_error` + +Control whether an error should be sent to the remote API. + +**Parameters:** + +- `shouldSend` (boolean): The default decision on whether to send the error. +- `error` (Error): The error object. +- `stackFrames` (Array): An array of stack frames from the error. + +**Return value:** (boolean) Whether the error should be sent. + +**Usage example:** ```js import { addFilter } from '@wordpress/hooks'; @@ -62,20 +94,81 @@ addFilter( 'woocommerce_remote_logging_should_send_error', 'my-plugin', (shouldSend, error, stackFrames) => { - const containsPluginFrame = stackFrames.some( - ( frame ) => - frame.url && frame.url.includes( '/my-plugin/' ); - ); - // Custom logic to determine if the error should be sent + const containsPluginFrame = stackFrames.some( + (frame) => frame.url && frame.url.includes( /YOUR_PLUGIN_ASSET_PATH/ ) + ); + // Only send errors that originate from our plugin return shouldSend && containsPluginFrame; } ); ``` -### API Reference +### `woocommerce_remote_logging_error_data` -- `init(config: RemoteLoggerConfig): void`: Initializes the remote logger with the given configuration. -- `log(severity: LogSeverity, message: string, extraData?: object): Promise`: Logs a message with the specified severity and optional extra data. -- `captureException(error: Error, extraData?: object): void`: Captures an error and sends it to the remote API. +Modify the error data before sending it to the remote API. -For more detailed information about types and interfaces, refer to the source code and inline documentation. +**Parameters:** + +- `errorData` (ErrorData): The error data object to be sent. + +**Return value:** (ErrorData) The modified error data object. + +**Usage example:** + +```js +import { addFilter } from '@wordpress/hooks'; + +addFilter( + 'woocommerce_remote_logging_error_data', + 'my-plugin', + (errorData) => { + // Custom logic to modify error data + errorData.tags = [ ...errorData.tags, 'my-plugin' ]; + return errorData; + } +); +``` + +### `woocommerce_remote_logging_log_endpoint` + +Modify the URL of the remote logging API endpoint. + +**Parameters:** + +- `endpoint` (string): The default endpoint URL. + +**Return value:** (string) The modified endpoint URL. + +**Usage example:** + +```js +import { addFilter } from '@wordpress/hooks'; + +addFilter( + 'woocommerce_remote_logging_log_endpoint', + 'my-plugin', + (endpoint) => 'https://my-custom-endpoint.com/log' +); +``` + +### `woocommerce_remote_logging_js_error_endpoint` + +Modify the URL of the remote logging API endpoint for JavaScript errors. + +**Parameters:** + +- `endpoint` (string): The default endpoint URL for JavaScript errors. + +**Return value:** (string) The modified endpoint URL for JavaScript errors. + +**Usage example:** + +```js +import { addFilter } from '@wordpress/hooks'; + +addFilter( + 'woocommerce_remote_logging_js_error_endpoint', + 'my-plugin', + (endpoint) => 'https://my-custom-endpoint.com/js-error-log' +); +``` diff --git a/plugins/woocommerce/changelog/update-lys-tour-meta b/packages/js/remote-logging/changelog/50993-fix-some-comment-typos similarity index 50% rename from plugins/woocommerce/changelog/update-lys-tour-meta rename to packages/js/remote-logging/changelog/50993-fix-some-comment-typos index 662915b86e2..3dce9f8d32b 100644 --- a/plugins/woocommerce/changelog/update-lys-tour-meta +++ b/packages/js/remote-logging/changelog/50993-fix-some-comment-typos @@ -1,4 +1,4 @@ Significance: patch Type: update -Migrate LYS user meta +Comment: Fix some comment typos. diff --git a/packages/js/remote-logging/changelog/update-remote-logging-readme b/packages/js/remote-logging/changelog/update-remote-logging-readme new file mode 100644 index 00000000000..db2068ed42a --- /dev/null +++ b/packages/js/remote-logging/changelog/update-remote-logging-readme @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update README.md to document the filters specs & usage diff --git a/packages/js/remote-logging/src/remote-logger.ts b/packages/js/remote-logging/src/remote-logger.ts index e4e71d72bac..06832a995d8 100644 --- a/packages/js/remote-logging/src/remote-logger.ts +++ b/packages/js/remote-logging/src/remote-logger.ts @@ -292,7 +292,7 @@ export class RemoteLogger { .map( this.getFormattedFrame ) .join( '\n\n' ); - // Set hard limit of 8192 characters for the stack trace so it does not use too much user bandwith and also our computation. + // Set hard limit of 8192 characters for the stack trace so it does not use too much user bandwidth and also our computation. return trace.length > 8192 ? trace.substring( 0, 8192 ) : trace; } diff --git a/packages/js/tracks/changelog/50993-fix-some-comment-typos b/packages/js/tracks/changelog/50993-fix-some-comment-typos new file mode 100644 index 00000000000..3dce9f8d32b --- /dev/null +++ b/packages/js/tracks/changelog/50993-fix-some-comment-typos @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Comment: Fix some comment typos. diff --git a/packages/js/tracks/src/stats.ts b/packages/js/tracks/src/stats.ts index 79a28497776..2040f1e5676 100644 --- a/packages/js/tracks/src/stats.ts +++ b/packages/js/tracks/src/stats.ts @@ -22,7 +22,7 @@ const GROUP_PREFIX = 'x_woocommerce-'; * @param {Record | string} group - The group of stats or a single stat name. * @param {string} [name] - The name of the stat if group is a string. * - * @return {URLSearchParams} The constructed querys. + * @return {URLSearchParams} The constructed query. */ function buildQueryParams( group: Record< string, string > | string, diff --git a/plugins/woo-ai/changelog/50047-fix-typos b/plugins/woo-ai/changelog/50047-fix-typos new file mode 100644 index 00000000000..e5d2f2b3db2 --- /dev/null +++ b/plugins/woo-ai/changelog/50047-fix-typos @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Comment: Fix comment typos across various files. diff --git a/plugins/woo-ai/src/utils/categorySelector.ts b/plugins/woo-ai/src/utils/categorySelector.ts index 7322497bb1c..8f4f1ecea85 100644 --- a/plugins/woo-ai/src/utils/categorySelector.ts +++ b/plugins/woo-ai/src/utils/categorySelector.ts @@ -1,5 +1,5 @@ /** - * Helper function to select a checkbox if it exists within a element + * Helper function to select a checkbox if it exists within an element * * @param element - The DOM element to check for a checkbox */ diff --git a/plugins/woocommerce-admin/changelog/50047-fix-typos b/plugins/woocommerce-admin/changelog/50047-fix-typos new file mode 100644 index 00000000000..e5d2f2b3db2 --- /dev/null +++ b/plugins/woocommerce-admin/changelog/50047-fix-typos @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Comment: Fix comment typos across various files. diff --git a/plugins/woocommerce-admin/client/analytics/components/report-summary/test/index.js b/plugins/woocommerce-admin/client/analytics/components/report-summary/test/index.js index 970dee29eec..697071d8b69 100644 --- a/plugins/woocommerce-admin/client/analytics/components/report-summary/test/index.js +++ b/plugins/woocommerce-admin/client/analytics/components/report-summary/test/index.js @@ -134,7 +134,7 @@ describe( 'ReportSummary', () => { ).toBeInTheDocument(); } ); - test( 'should display SummaryListPlaceholder when isRequesting is true', () => { + test( 'should display SummaryListPlaceholder when summaryData.isRequesting is true', () => { const { container } = renderChart( 'number', null, null, false, true ); expect( diff --git a/plugins/woocommerce-admin/client/analytics/report/categories/index.js b/plugins/woocommerce-admin/client/analytics/report/categories/index.js index 904f1e1bac1..02110c765da 100644 --- a/plugins/woocommerce-admin/client/analytics/report/categories/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/categories/index.js @@ -69,7 +69,6 @@ class CategoriesReport extends Component { Order # ', 'woocommerce' @@ -210,7 +210,7 @@ export const advancedFilters = applyFilters( 'Select an IP address filter match', 'woocommerce' ), - /* translators: A sentence describing a order number filter. See screen shot for context: https://cloudup.com/ccxhyH2mEDg */ + /* translators: A sentence describing an order number filter. See screen shot for context: https://cloudup.com/ccxhyH2mEDg */ title: __( 'IP Address ', 'woocommerce' diff --git a/plugins/woocommerce-admin/client/analytics/report/products/index.js b/plugins/woocommerce-admin/client/analytics/report/products/index.js index 9307d67e207..bab9201f385 100644 --- a/plugins/woocommerce-admin/client/analytics/report/products/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/products/index.js @@ -82,7 +82,6 @@ class ProductsReport extends Component { mode={ mode } charts={ charts } endpoint="products" - isRequesting={ isRequesting } query={ chartQuery } selectedChart={ getSelectedChart( query.chart, charts ) } filters={ filters } diff --git a/plugins/woocommerce-admin/client/analytics/report/taxes/index.js b/plugins/woocommerce-admin/client/analytics/report/taxes/index.js index 1b089e790c0..493ca5e30b8 100644 --- a/plugins/woocommerce-admin/client/analytics/report/taxes/index.js +++ b/plugins/woocommerce-admin/client/analytics/report/taxes/index.js @@ -52,7 +52,6 @@ class TaxesReport extends Component { { mode={ mode } charts={ charts } endpoint="variations" - isRequesting={ isRequesting } query={ chartQuery } selectedChart={ getSelectedChart( query.chart, charts ) } filters={ filters } diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/hooks/auto-block-preview-event-listener.ts b/plugins/woocommerce-admin/client/customize-store/assembler-hub/hooks/auto-block-preview-event-listener.ts index 29701bd02da..1aa7ed1aece 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/hooks/auto-block-preview-event-listener.ts +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/hooks/auto-block-preview-event-listener.ts @@ -441,7 +441,7 @@ export const useAddAutoBlockPreviewEventListenersAndObservers = ( unsubscribeCallbacks.push( removeEventListenerHidePopover ); } - // Add event listner to the button which will insert a default pattern + // Add event listener to the button which will insert a default pattern // when there are no patterns inserted in the block preview. const removePatternButtonClickListener = addPatternButtonClickListener( documentElement, diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/opt-in/opt-in.tsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/opt-in/opt-in.tsx index 23758b5e218..584b9d7bcd7 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/opt-in/opt-in.tsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/opt-in/opt-in.tsx @@ -49,7 +49,7 @@ export const OptInSubscribe = () => { await apiFetch< { success: boolean; } >( { - path: `/wc/private/patterns`, + path: '/wc-admin/patterns', method: 'POST', } ); }; diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/pattern-screen/style.scss b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/pattern-screen/style.scss index eb35a88ebfd..a3dfbffe7be 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/pattern-screen/style.scss +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/pattern-screen/style.scss @@ -75,7 +75,7 @@ } } - div.block-editor-block-patterns-list > div:nth-last-child(2) { + div.block-editor-block-patterns-list > div:nth-last-child(1 of .block-editor-block-patterns-list__list-item) { margin-bottom: 0; } } diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-homepage-ptk/sidebar-navigation-screen-homepage-ptk.tsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-homepage-ptk/sidebar-navigation-screen-homepage-ptk.tsx index e3b0c875c3f..593f12e7b21 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-homepage-ptk/sidebar-navigation-screen-homepage-ptk.tsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/sidebar-navigation-screen-homepage-ptk/sidebar-navigation-screen-homepage-ptk.tsx @@ -264,7 +264,7 @@ export const SidebarNavigationScreenHomepagePTK = ( { + { additionalGateways.length > 0 && ( + + ) } + + ); +}; diff --git a/plugins/woocommerce-admin/client/settings-payments/components/payment-method.tsx b/plugins/woocommerce-admin/client/settings-payments/components/payment-method.tsx new file mode 100644 index 00000000000..fa7adf07c28 --- /dev/null +++ b/plugins/woocommerce-admin/client/settings-payments/components/payment-method.tsx @@ -0,0 +1,183 @@ +/** + * External dependencies + */ +import { useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { PaymentGateway } from '@woocommerce/data'; +import { WooPaymentMethodsLogos } from '@woocommerce/onboarding'; + +/** + * Internal dependencies + */ +import { getAdminSetting } from '~/utils/admin-settings'; +import sanitizeHTML from '~/lib/sanitize-html'; +import { WCPayInstallButton } from './wcpay-install-button'; + +export const PaymentMethod = ( { + id, + enabled, + title, + method_title, + method_description, + settings_url, +}: PaymentGateway ) => { + const isWooPayEligible = getAdminSetting( 'isWooPayEligible', false ); + const [ isEnabled, setIsEnabled ] = useState( enabled ); + const [ isLoading, setIsLoading ] = useState( false ); + + const toggleEnabled = async ( e: React.MouseEvent ) => { + e.preventDefault(); + setIsLoading( true ); + + if ( ! window.woocommerce_admin.nonces?.gateway_toggle ) { + // eslint-disable-next-line no-console + console.warn( 'Unexpected error: Nonce not found' ); + // Redirect to payment setting page if nonce is not found. Users should still be able to toggle the payment method from that page. + window.location.href = settings_url; + return; + } + + try { + const response = await fetch( window.woocommerce_admin.ajax_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams( { + action: 'woocommerce_toggle_gateway_enabled', + security: window.woocommerce_admin.nonces?.gateway_toggle, + gateway_id: id, + } ), + } ); + + const result = await response.json(); + + if ( result.success ) { + if ( result.data === true ) { + setIsEnabled( true ); + } else if ( result.data === false ) { + setIsEnabled( false ); + } else if ( result.data === 'needs_setup' ) { + window.location.href = settings_url; + } + } else { + window.location.href = settings_url; + } + } catch ( error ) { + // eslint-disable-next-line no-console + console.error( 'Error toggling gateway:', error ); + } finally { + setIsLoading( false ); + } + }; + + return ( + + + +
+ + { method_title } + + { id !== 'pre_install_woocommerce_payments_promotion' && + method_title !== title && ( + +  â€“  + { title } + + ) } + { id === 'pre_install_woocommerce_payments_promotion' && ( +
+ +
+ ) } +
+ + + + + { isEnabled + ? __( 'Yes', 'woocommerce' ) + : __( 'No', 'woocommerce' ) } + + + + + + { id === 'pre_install_woocommerce_payments_promotion' ? ( + + ) : ( + + { enabled + ? __( 'Manage', 'woocommerce' ) + : __( 'Finish setup', 'woocommerce' ) } + + ) } + + + ); +}; diff --git a/plugins/woocommerce-admin/client/settings-payments/components/wcpay-install-button.tsx b/plugins/woocommerce-admin/client/settings-payments/components/wcpay-install-button.tsx new file mode 100644 index 00000000000..a5583bd73f6 --- /dev/null +++ b/plugins/woocommerce-admin/client/settings-payments/components/wcpay-install-button.tsx @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import React, { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { + PAYMENT_GATEWAYS_STORE_NAME, + PLUGINS_STORE_NAME, +} from '@woocommerce/data'; +import { Button } from '@wordpress/components'; +import { resolveSelect, useDispatch } from '@wordpress/data'; +import { recordEvent } from '@woocommerce/tracks'; + +const slug = 'woocommerce-payments'; +export const WCPayInstallButton = () => { + const [ installing, setInstalling ] = useState( false ); + const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME ); + const { createNotice } = useDispatch( 'core/notices' ); + + const redirectToSettings = async () => { + const paymentGateway = await resolveSelect( + PAYMENT_GATEWAYS_STORE_NAME + ).getPaymentGateway( slug.replace( /-/g, '_' ) ); + + if ( paymentGateway?.settings_url ) { + window.location.href = paymentGateway.settings_url; + } + }; + + const installWooCommercePayments = async () => { + if ( installing ) return; + + setInstalling( true ); + recordEvent( 'settings_payments_recommendations_setup', { + extension_selected: slug, + } ); + + try { + await installAndActivatePlugins( [ slug ] ); + redirectToSettings(); + } catch ( error ) { + if ( error instanceof Error ) { + createNotice( 'error', error.message ); + } + setInstalling( false ); + } + }; + + return ( + + ); +}; diff --git a/plugins/woocommerce-admin/client/settings-payments/settings-payments-main.scss b/plugins/woocommerce-admin/client/settings-payments/settings-payments-main.scss index a63d140104c..64beb4baac8 100644 --- a/plugins/woocommerce-admin/client/settings-payments/settings-payments-main.scss +++ b/plugins/woocommerce-admin/client/settings-payments/settings-payments-main.scss @@ -1,9 +1,29 @@ +@import "~/wp-admin-scripts/payment-method-promotions/payment-promotion-row.scss"; + .settings-payments-main__container { - h1 { - color: #fff; + .settings-payments-main__spinner { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + width: 100%; + } + + + table.wc_gateways { + .other-payment-methods__button-text { + margin-right: 4px; + } + + td.other-payment-methods-row { + border-top: 1px solid #c3c4c7; + background-color: #fff; + + } + + .other-payment-methods__image { + vertical-align: middle; + margin-right: 8px; + } } - background: #000; - text-align: center; - padding: 50px 0; - width: 100%; } diff --git a/plugins/woocommerce-admin/client/settings-payments/settings-payments-main.tsx b/plugins/woocommerce-admin/client/settings-payments/settings-payments-main.tsx index c2671e205bf..94f85355931 100644 --- a/plugins/woocommerce-admin/client/settings-payments/settings-payments-main.tsx +++ b/plugins/woocommerce-admin/client/settings-payments/settings-payments-main.tsx @@ -1,17 +1,108 @@ /** * External dependencies */ -import '@wordpress/element'; +import { useMemo } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { PaymentGateway } from '@woocommerce/data'; /** * Internal dependencies */ import './settings-payments-main.scss'; +import { PaymentMethod } from './components/payment-method'; +import { OtherPaymentMethods } from './components/other-payment-methods'; +import { PaymentsBannerWrapper } from '~/payments/payment-settings-banner'; export const SettingsPaymentsMain: React.FC = () => { + const [ paymentGateways, error ] = useMemo( () => { + const script = document.getElementById( + 'experimental_wc_settings_payments_gateways' + ); + + try { + if ( script && script.textContent ) { + return [ + JSON.parse( script.textContent ) as PaymentGateway[], + null, + ]; + } + throw new Error( 'Could not find payment gateways data' ); + } catch ( e ) { + return [ [], e as Error ]; + } + }, [] ); + + if ( error ) { + // This is a temporary error message to be replaced by error boundary. + return ( +
+

+ { __( 'Error loading payment gateways', 'woocommerce' ) } +

+

{ error.message }

+
+ ); + } + return (
-

Main payments screen

+
+ +
+ + + + + + +
+ + + + + + + + + + + + { paymentGateways.map( + ( gateway: PaymentGateway ) => ( + + ) + ) } + + + + + +
+ { __( 'Method', 'woocommerce' ) } + + { __( 'Enabled', 'woocommerce' ) } + + { __( + 'Description', + 'woocommerce' + ) } +
+ +
+
); }; diff --git a/plugins/woocommerce-admin/client/task-lists/fills/tax/avalara/card.tsx b/plugins/woocommerce-admin/client/task-lists/fills/tax/avalara/card.tsx deleted file mode 100644 index d88b1c114aa..00000000000 --- a/plugins/woocommerce-admin/client/task-lists/fills/tax/avalara/card.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { getAdminLink } from '@woocommerce/settings'; -import interpolateComponents from '@automattic/interpolate-components'; -import { recordEvent } from '@woocommerce/tracks'; - -/** - * Internal dependencies - */ -import { PartnerCard } from '../components/partner-card'; -import { TaxChildProps } from '../utils'; -import logo from './logo.png'; - -export const Card: React.FC< TaxChildProps > = ( { task } ) => { - const { additionalData: { avalaraActivated } = {} } = task; - - return ( - , - }, - } ), - __( - 'Cross-border and multi-channel compliance', - 'woocommerce' - ), - __( 'Automate filing & remittance', 'woocommerce' ), - __( - 'Return-ready, jurisdiction-level reporting.', - 'woocommerce' - ), - ] } - terms={ '' } - actionText={ - avalaraActivated - ? __( 'Continue setup', 'woocommerce' ) - : __( 'Download', 'woocommerce' ) - } - onClick={ () => { - recordEvent( 'tasklist_tax_select_option', { - selected_option: 'avalara', - } ); - - if ( avalaraActivated ) { - window.location.href = getAdminLink( - '/admin.php?page=wc-settings&tab=tax§ion=avatax' - ); - - return; - } - - window.open( - new URL( - 'https://woocommerce.com/products/woocommerce-avatax/' - ).toString(), - '_blank' - ); - } } - /> - ); -}; diff --git a/plugins/woocommerce-admin/client/task-lists/fills/tax/avalara/logo.png b/plugins/woocommerce-admin/client/task-lists/fills/tax/avalara/logo.png deleted file mode 100644 index 8430603eb9f..00000000000 Binary files a/plugins/woocommerce-admin/client/task-lists/fills/tax/avalara/logo.png and /dev/null differ diff --git a/plugins/woocommerce-admin/client/task-lists/fills/tax/components/partner-card.scss b/plugins/woocommerce-admin/client/task-lists/fills/tax/components/partner-card.scss index 7ad3feef6b7..9bcdb291555 100644 --- a/plugins/woocommerce-admin/client/task-lists/fills/tax/components/partner-card.scss +++ b/plugins/woocommerce-admin/client/task-lists/fills/tax/components/partner-card.scss @@ -41,7 +41,7 @@ .woocommerce-tax-partner-card__terms { color: $gray-600; - font-size: 9px; + font-size: 12px; margin-bottom: $gap-smaller; } diff --git a/plugins/woocommerce-admin/client/task-lists/fills/tax/components/partner-card.tsx b/plugins/woocommerce-admin/client/task-lists/fills/tax/components/partner-card.tsx index 30e2e1e5eb7..cef2b27b108 100644 --- a/plugins/woocommerce-admin/client/task-lists/fills/tax/components/partner-card.tsx +++ b/plugins/woocommerce-admin/client/task-lists/fills/tax/components/partner-card.tsx @@ -15,7 +15,8 @@ export const PartnerCard: React.FC< { description: string; benefits: ( string | JSX.Element )[]; terms: string | JSX.Element; - actionText: string; + children?: React.ReactNode; + actionText?: string; onClick: () => void; isBusy?: boolean; } > = ( { @@ -27,6 +28,7 @@ export const PartnerCard: React.FC< { actionText, onClick, isBusy, + children, } ) => { return (
@@ -59,14 +61,18 @@ export const PartnerCard: React.FC< {
{ terms }
- + { children ? ( + children + ) : ( + + ) }
); diff --git a/plugins/woocommerce-admin/client/task-lists/fills/tax/index.tsx b/plugins/woocommerce-admin/client/task-lists/fills/tax/index.tsx index c4c95cdb95e..f98fd06a168 100644 --- a/plugins/woocommerce-admin/client/task-lists/fills/tax/index.tsx +++ b/plugins/woocommerce-admin/client/task-lists/fills/tax/index.tsx @@ -17,6 +17,7 @@ import { useEffect, useState, createElement, + useMemo, } from '@wordpress/element'; import { WooOnboardingTask } from '@woocommerce/onboarding'; @@ -25,6 +26,7 @@ import { WooOnboardingTask } from '@woocommerce/onboarding'; */ import { redirectToTaxSettings } from './utils'; import { Card as WooCommerceTaxCard } from './woocommerce-tax/card'; +import { Card as StripeTaxCard } from './stripe-tax/card'; import { createNoticesFromResponse } from '../../../lib/notices'; import { getCountryCode } from '~/dashboard/utils'; import { ManualConfiguration } from './manual-configuration'; @@ -150,20 +152,21 @@ export const Tax: React.FC< TaxProps > = ( { onComplete, query, task } ) => { } ); }, [ updateOptions ] ); - const getVisiblePartners = () => { + const partners = useMemo( () => { const countryCode = getCountryCode( generalSettings?.woocommerce_default_country ) || ''; const { additionalData: { woocommerceTaxCountries = [], + stripeTaxCountries = [], taxJarActivated, woocommerceTaxActivated, woocommerceShippingActivated, } = {}, } = task; - const partners = [ + const allPartners = [ { id: 'woocommerce-tax', card: WooCommerceTaxCard, @@ -174,31 +177,35 @@ export const Tax: React.FC< TaxProps > = ( { onComplete, query, task } ) => { ! woocommerceShippingActivated && woocommerceTaxCountries.includes( countryCode ), }, + { + id: 'stripe-tax', + card: StripeTaxCard, + + isVisible: stripeTaxCountries.includes( countryCode ), + }, ]; - return partners.filter( ( partner ) => partner.isVisible ); - }; - - const partners = getVisiblePartners(); + return allPartners.filter( ( partner ) => partner.isVisible ); + // eslint-disable-next-line react-hooks/exhaustive-deps -- the partner list shouldn't be changing in the middle of interaction. for some reason the country is becoming null in a re-render and causing unexpected behaviour + }, [] ); + const { auto } = query; useEffect( () => { - const { auto } = query; - if ( auto === 'true' ) { onAutomate(); - return; } + }, [ auto, onAutomate ] ); + useEffect( () => { if ( query.partner ) { return; } - recordEvent( 'tasklist_tax_view_options', { options: partners.map( ( partner ) => partner.id ), } ); - }, [ onAutomate, partners, query ] ); + }, [ partners, query.partner ] ); - const getCurrentPartner = () => { + const currentPartner = useMemo( () => { if ( ! query.partner ) { return null; } @@ -206,7 +213,7 @@ export const Tax: React.FC< TaxProps > = ( { onComplete, query, task } ) => { return ( partners.find( ( partner ) => partner.id === query.partner ) || null ); - }; + }, [ partners, query.partner ] ); const childProps = { isPending, @@ -220,8 +227,6 @@ export const Tax: React.FC< TaxProps > = ( { onComplete, query, task } ) => { return ; } - const currentPartner = getCurrentPartner(); - if ( ! partners.length ) { return ( diff --git a/plugins/woocommerce-admin/client/task-lists/fills/tax/stripe-tax/card.tsx b/plugins/woocommerce-admin/client/task-lists/fills/tax/stripe-tax/card.tsx new file mode 100644 index 00000000000..3795a7dee60 --- /dev/null +++ b/plugins/woocommerce-admin/client/task-lists/fills/tax/stripe-tax/card.tsx @@ -0,0 +1,107 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { getAdminLink } from '@woocommerce/settings'; +import { recordEvent } from '@woocommerce/tracks'; +import { Plugins } from '@woocommerce/components'; +import { dispatch, useDispatch } from '@wordpress/data'; +import { SETTINGS_STORE_NAME } from '@woocommerce/data'; +import { Button } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { PartnerCard } from '../components/partner-card'; +import { TaxChildProps } from '../utils'; +import StripeTaxLogo from './stripe-tax-logo.svg'; +import { createNoticesFromResponse } from '~/lib/notices'; + +const STRIPE_TAX_PLUGIN_SLUG = 'stripe-tax-for-woocommerce'; + +const redirectToStripeTaxSettings = () => { + window.location.href = getAdminLink( + '/admin.php?page=wc-settings&tab=stripe_tax_for_woocommerce' + ); +}; + +export const Card: React.FC< TaxChildProps > = ( { + task: { + additionalData: { stripeTaxActivated } = { + stripeTaxActivated: false, + }, + }, +} ) => { + const { createSuccessNotice } = useDispatch( 'core/notices' ); + + return ( + {} } + > + { stripeTaxActivated ? ( + + ) : ( + { + recordEvent( 'tasklist_tax_select_option', { + selected_option: STRIPE_TAX_PLUGIN_SLUG, + } ); + } } + onComplete={ () => { + recordEvent( 'tasklist_tax_install_plugin_success', { + selected_option: STRIPE_TAX_PLUGIN_SLUG, + } ); + const { updateAndPersistSettingsForGroup } = + dispatch( SETTINGS_STORE_NAME ); + updateAndPersistSettingsForGroup( 'general', { + general: { + woocommerce_calc_taxes: 'yes', // Stripe tax requires tax calculation to be enabled so let's do it here to save the user from doing it manually + }, + } ).then( () => { + createSuccessNotice( + __( + "Stripe Tax for Woocommerce has been successfully installed. Let's configure it now.", + 'woocommerce' + ) + ); + redirectToStripeTaxSettings(); + } ); + } } + onError={ ( errors, response ) => { + recordEvent( 'tasklist_tax_install_plugin_error', { + selected_option: STRIPE_TAX_PLUGIN_SLUG, + errors, + } ); + createNoticesFromResponse( response ); + } } + installButtonVariant="secondary" + pluginSlugs={ [ STRIPE_TAX_PLUGIN_SLUG ] } + /> + ) } + + ); +}; diff --git a/plugins/woocommerce-admin/client/task-lists/fills/tax/stripe-tax/stripe-tax-logo.svg b/plugins/woocommerce-admin/client/task-lists/fills/tax/stripe-tax/stripe-tax-logo.svg new file mode 100644 index 00000000000..d290cefc63f --- /dev/null +++ b/plugins/woocommerce-admin/client/task-lists/fills/tax/stripe-tax/stripe-tax-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/woocommerce-admin/client/typings/global.d.ts b/plugins/woocommerce-admin/client/typings/global.d.ts index 7e0aeebf77c..9c3a8c79e4b 100644 --- a/plugins/woocommerce-admin/client/typings/global.d.ts +++ b/plugins/woocommerce-admin/client/typings/global.d.ts @@ -88,6 +88,12 @@ declare global { getUserSetting?: ( name: string ) => string | undefined; setUserSetting?: ( name: string, value: string ) => void; deleteUserSetting?: ( name: string ) => void; + woocommerce_admin: { + ajax_url: string; + nonces: { + gateway_toggle?: string; + } + } } } diff --git a/plugins/woocommerce-beta-tester/api/tools/class-fake-wcpayments.php b/plugins/woocommerce-beta-tester/api/tools/class-fake-wcpayments.php index 01fa658c3a4..7292cc43e74 100644 --- a/plugins/woocommerce-beta-tester/api/tools/class-fake-wcpayments.php +++ b/plugins/woocommerce-beta-tester/api/tools/class-fake-wcpayments.php @@ -59,4 +59,15 @@ class Fake_WCPayments extends WC_Payment_Gateway_WCPay { public function is_available() { return true; } + + /** + * Checks if the account has not completed onboarding due to users abandoning the process half way. + * Also used by WC Core to complete the task "Set up WooPayments". + * Called directly by WooCommerce Core. + * + * @return bool + */ + public function is_account_partially_onboarded(): bool { + return false; + } } diff --git a/plugins/woocommerce-beta-tester/changelog/update-woo-pay-task b/plugins/woocommerce-beta-tester/changelog/update-woo-pay-task new file mode 100644 index 00000000000..8a1b90226ac --- /dev/null +++ b/plugins/woocommerce-beta-tester/changelog/update-woo-pay-task @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update Fake_WCPayments is_account_partially_onboarded to return false diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/button/frontend.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/button/frontend.tsx index 3086aee599e..65bf4f8f5d8 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/button/frontend.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/button/frontend.tsx @@ -104,22 +104,20 @@ const { state } = store< Store >( 'woocommerce/product-button', { }, get addToCartText(): string { const context = getContext(); + const inTheCartText = state.inTheCartText || ''; // We use the temporary number of items when there's no animation, or the // second part of the animation hasn't started. - if ( + const showTemporaryNumber = context.animationStatus === AnimationStatus.IDLE || - context.animationStatus === AnimationStatus.SLIDE_OUT - ) { - return getButtonText( - context.addToCartText, - state.inTheCartText!, - context.temporaryNumberOfItems - ); - } + context.animationStatus === AnimationStatus.SLIDE_OUT; + const numberOfItems = showTemporaryNumber + ? context.temporaryNumberOfItems + : state.numberOfItemsInTheCart; + return getButtonText( context.addToCartText, - state.inTheCartText!, - state.numberOfItemsInTheCart + inTheCartText, + numberOfItems ); }, get displayViewCart(): boolean { diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/button/index.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/button/index.tsx index c10b7870d9b..4ebb6647356 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/button/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/button/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @wordpress/no-unsafe-wp-apis */ /** * External dependencies */ diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/attributes.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/attributes.ts deleted file mode 100644 index 2405642245e..00000000000 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/attributes.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * External dependencies - */ -import type { BlockAttributes } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { ImageSizing } from './types'; - -export const blockAttributes: BlockAttributes = { - showProductLink: { - type: 'boolean', - default: true, - }, - showSaleBadge: { - type: 'boolean', - default: true, - }, - saleBadgeAlign: { - type: 'string', - default: 'right', - }, - imageSizing: { - type: 'string', - default: ImageSizing.SINGLE, - }, - productId: { - type: 'number', - default: 0, - }, - isDescendentOfQueryLoop: { - type: 'boolean', - default: false, - }, - isDescendentOfSingleProductBlock: { - type: 'boolean', - default: false, - }, - width: { - type: 'string', - }, - height: { - type: 'string', - }, - scale: { - type: 'string', - default: 'cover', - }, - aspectRatio: { - type: 'string', - }, -}; - -export default blockAttributes; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/block.json b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/block.json new file mode 100644 index 00000000000..a73063ebab1 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/block.json @@ -0,0 +1,28 @@ +{ + "name": "woocommerce/product-image", + "version": "1.0.0", + "title": "Product Image", + "description": "Display the main product image.", + "category": "woocommerce-product-elements", + "attributes": { + "showProductLink": { "type": "boolean", "default": true }, + "showSaleBadge": { "type": "boolean", "default": true }, + "saleBadgeAlign": { "type": "string", "default": "right" }, + "imageSizing": { "type": "string", "default": "single" }, + "productId": { "type": "number", "default": 0 }, + "isDescendentOfQueryLoop": { "type": "boolean", "default": false }, + "isDescendentOfSingleProductBlock": { + "type": "boolean", + "default": false + }, + "width": { "type": "string" }, + "height": { "type": "string" }, + "scale": { "type": "string", "default": "cover" }, + "aspectRatio": { "type": "string" } + }, + "usesContext": [ "query", "queryId", "postId" ], + "keywords": [ "WooCommerce" ], + "textdomain": "woocommerce", + "apiVersion": 3, + "$schema": "https://schemas.wp.org/trunk/block.json" +} diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/constants.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/constants.tsx index 3a4cf2388c5..441b95f2e6b 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/constants.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/constants.tsx @@ -1,14 +1,8 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; import { image, Icon } from '@wordpress/icons'; -export const BLOCK_TITLE: string = __( 'Product Image', 'woocommerce' ); export const BLOCK_ICON: JSX.Element = ( ); -export const BLOCK_DESCRIPTION: string = __( - 'Display the main product image.', - 'woocommerce' -); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/edit.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/edit.tsx index 9eaf63bc4d7..8ed366bf609 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/edit.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/edit.tsx @@ -27,11 +27,8 @@ import { */ import Block from './block'; import withProductSelector from '../shared/with-product-selector'; -import { - BLOCK_TITLE as label, - BLOCK_ICON as icon, - BLOCK_DESCRIPTION as description, -} from './constants'; +import { BLOCK_ICON as icon } from './constants'; +import { title, description } from './block.json'; import { BlockAttributes, ImageSizing } from './types'; import { ImageSizeSettings } from './image-size-settings'; @@ -172,4 +169,4 @@ const Edit = ( { ); }; -export default withProductSelector( { icon, label, description } )( Edit ); +export default withProductSelector( { icon, title, description } )( Edit ); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/frontend.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/frontend.ts index b6c773996b2..b0ad02b5ec8 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/frontend.ts @@ -7,6 +7,6 @@ import { withFilteredAttributes } from '@woocommerce/shared-hocs'; * Internal dependencies */ import Block from './block'; -import attributes from './attributes'; +import { attributes } from './block.json'; export default withFilteredAttributes( attributes )( Block ); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/index.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/index.ts deleted file mode 100644 index 7ae60f870f7..00000000000 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * External dependencies - */ -import { registerBlockType } from '@wordpress/blocks'; -import type { BlockConfiguration } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import edit from './edit'; - -import { supports } from './supports'; -import attributes from './attributes'; -import sharedConfig from '../shared/config'; -import { - BLOCK_TITLE as title, - BLOCK_ICON as icon, - BLOCK_DESCRIPTION as description, -} from './constants'; - -const blockConfig: BlockConfiguration = { - ...sharedConfig, - name: 'woocommerce/product-image', - title, - icon: { src: icon }, - keywords: [ 'WooCommerce' ], - description, - usesContext: [ 'query', 'queryId', 'postId' ], - textdomain: 'woocommerce', - attributes, - supports, - edit, -}; - -registerBlockType( 'woocommerce/product-image', { ...blockConfig } ); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/index.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/index.tsx new file mode 100644 index 00000000000..79a486a706e --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/image/index.tsx @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import edit from './edit'; +import { BLOCK_ICON as icon } from './constants'; +import { supports } from './supports'; +import sharedConfig from '../shared/config'; +import metadata from './block.json'; + +registerBlockType( metadata, { + ...sharedConfig, + icon, + supports, + edit, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/price/supports.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/price/supports.ts index d471cf1f180..598a43d2939 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/price/supports.ts +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/price/supports.ts @@ -1,3 +1,4 @@ +/* eslint-disable @wordpress/no-unsafe-wp-apis */ /** * External dependencies */ diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/block.json b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/block.json index 6e8df5f272d..633c831d1c6 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/block.json +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/block.json @@ -5,6 +5,12 @@ "title": "Product Details", "description": "Display a product's description, attributes, and reviews.", "category": "woocommerce-product-elements", + "attributes": { + "hideTabTitle": { + "type": "boolean", + "default": false + } + }, "keywords": [ "WooCommerce" ], diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/block.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/block.tsx index a011fb84da8..169f7c7cab1 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/block.tsx @@ -8,7 +8,7 @@ interface SingleProductTab { id: string; title: string; active: boolean; - content: string | undefined; + content: React.JSX.Element | undefined; } const ProductTabTitle = ( { @@ -46,15 +46,28 @@ const ProductTabContent = ( { ); }; -export const SingleProductDetails = () => { +export const SingleProductDetails = ( { + hideTabTitle, +}: { + hideTabTitle: boolean; +} ) => { const productTabs = [ { id: 'description', title: 'Description', active: true, - content: __( - 'This block lists description, attributes and reviews for a single product.', - 'woocommerce' + content: ( + <> + { ! hideTabTitle && ( +

{ __( 'Description', 'woocommerce' ) }

+ ) } +

+ { __( + 'This block lists description, attributes and reviews for a single product.', + 'woocommerce' + ) } +

+ ), }, { diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/edit.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/edit.tsx index 471f9f7e30b..d4d652c3ee2 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/edit.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/edit.tsx @@ -1,9 +1,10 @@ /** * External dependencies */ -import { useBlockProps } from '@wordpress/block-editor'; -import { Disabled } from '@wordpress/components'; +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +import { Disabled, PanelBody, ToggleControl } from '@wordpress/components'; import type { BlockEditProps } from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -12,8 +13,11 @@ import Block from './block'; import { Attributes } from './types'; import './editor.scss'; -const Edit = ( { attributes }: BlockEditProps< Attributes > ) => { - const { className } = attributes; +const Edit = ( { + attributes, + setAttributes, +}: BlockEditProps< Attributes > ) => { + const { className, hideTabTitle } = attributes; const blockProps = useBlockProps( { className, } ); @@ -21,8 +25,24 @@ const Edit = ( { attributes }: BlockEditProps< Attributes > ) => { return ( <>
+ + + + setAttributes( { + hideTabTitle: ! hideTabTitle, + } ) + } + /> + + - +
diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/style.scss b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/style.scss index 5a426110c5d..ee76bea98ec 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/style.scss +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/style.scss @@ -11,6 +11,7 @@ html body .wp-block-woocommerce-product-details.is-style-minimal { padding: 0; border-bottom-width: 0; border-bottom-color: inherit; /* Backward compatibility for TT3, TT2. */ + overflow: visible; &::after, &::before { @@ -26,14 +27,14 @@ html body .wp-block-woocommerce-product-details.is-style-minimal { border-bottom-width: 2px; border-radius: 0; margin: 0; - padding: 0 1em; + padding: 0; display: inline-block; float: none; /* Backward compatibility for TT3, TT2. */ font-weight: bold; opacity: 0.65; a { - padding: 0.5em 0; + padding: 0.5em 1em; color: inherit; border: none; text-shadow: none; @@ -42,6 +43,7 @@ html body .wp-block-woocommerce-product-details.is-style-minimal { } &:hover, + &:focus-within, &.active { color: inherit; background: inherit; @@ -55,6 +57,12 @@ html body .wp-block-woocommerce-product-details.is-style-minimal { } } + /* Remove default focus styles in favor of the ones we defined above. */ + &:focus:not(:focus-visible), + a:focus:not(:focus-visible) { + outline: none; + } + @media only screen and ( max-width: 768px ) { display: block; border-left-width: 2px; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/types.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/types.ts index aa9b37b316a..36a5d5b5d58 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/types.ts +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/product-details/types.ts @@ -1,3 +1,4 @@ export interface Attributes { className?: string; + hideTabTitle?: boolean; } diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/attributes.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/attributes.ts deleted file mode 100644 index 46b5750999a..00000000000 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/attributes.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * External dependencies - */ -import type { BlockAttributes } from '@wordpress/blocks'; - -export const blockAttributes: BlockAttributes = { - productId: { - type: 'number', - default: 0, - }, - isDescendentOfQueryLoop: { - type: 'boolean', - default: false, - }, - isDescendentOfSingleProductTemplate: { - type: 'boolean', - default: false, - }, -}; - -export default blockAttributes; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/block.json b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/block.json new file mode 100644 index 00000000000..391abb53d39 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/block.json @@ -0,0 +1,20 @@ +{ + "name": "woocommerce/product-sale-badge", + "version": "1.0.0", + "title": "On-Sale Badge", + "description": "Displays an on-sale badge if the product is on-sale.", + "category": "woocommerce-product-elements", + "attributes": { + "productId": { "type": "number", "default": 0 }, + "isDescendentOfQueryLoop": { "type": "boolean", "default": false }, + "isDescendentOfSingleProductTemplate": { + "type": "boolean", + "default": false + } + }, + "usesContext": [ "query", "queryId", "postId" ], + "keywords": [ "WooCommerce" ], + "textdomain": "woocommerce", + "apiVersion": 3, + "$schema": "https://schemas.wp.org/trunk/block.json" +} diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/block.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/block.tsx index 1ed112ffc9e..8486402e3cf 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/block.tsx @@ -18,7 +18,8 @@ import type { HTMLAttributes } from 'react'; import './style.scss'; import type { BlockAttributes } from './types'; -type Props = BlockAttributes & HTMLAttributes< HTMLDivElement >; +type Props = BlockAttributes & + HTMLAttributes< HTMLDivElement > & { align: boolean }; export const Block = ( props: Props ): JSX.Element | null => { const { className, align } = props; diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/constants.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/constants.tsx deleted file mode 100644 index d81fb73e894..00000000000 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/constants.tsx +++ /dev/null @@ -1,14 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { percent, Icon } from '@wordpress/icons'; - -export const BLOCK_TITLE: string = __( 'On-Sale Badge', 'woocommerce' ); -export const BLOCK_ICON: JSX.Element = ( - -); -export const BLOCK_DESCRIPTION: string = __( - 'Displays an on-sale badge if the product is on-sale.', - 'woocommerce' -); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/index.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/index.ts deleted file mode 100644 index 3f195dea9bd..00000000000 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * External dependencies - */ -import { registerBlockType } from '@wordpress/blocks'; -import type { BlockConfiguration } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import sharedConfig from '../shared/config'; -import attributes from './attributes'; -import edit from './edit'; -import { - BLOCK_TITLE as title, - BLOCK_ICON as icon, - BLOCK_DESCRIPTION as description, -} from './constants'; -import { supports } from './support'; - -const blockConfig: BlockConfiguration = { - ...sharedConfig, - title, - description, - icon: { src: icon }, - supports, - attributes, - edit, - usesContext: [ 'query', 'queryId', 'postId' ], - ancestor: [ - ...( sharedConfig.ancestor || [] ), - 'woocommerce/product-gallery', - ], -}; - -registerBlockType( 'woocommerce/product-sale-badge', { ...blockConfig } ); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/index.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/index.tsx new file mode 100644 index 00000000000..81a2e7d4cd0 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/index.tsx @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; +import { percent, Icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import sharedConfig from '../shared/config'; +import edit from './edit'; +import { supports } from './support'; +import metadata from './block.json'; + +registerBlockType( metadata, { + ...sharedConfig, + icon: ( + + ), + supports, + edit, + ancestor: [ + ...( sharedConfig.ancestor || [] ), + 'woocommerce/product-gallery', + ], +} ); diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/types.ts b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/types.ts index 3395dfacbfa..4b23eb63506 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/types.ts +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sale-badge/types.ts @@ -1,6 +1,5 @@ export interface BlockAttributes { productId: number; - align: 'left' | 'center' | 'right'; isDescendentOfQueryLoop?: boolean | undefined; isDescendentOfSingleProductTemplate?: boolean; } diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sku/edit.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sku/edit.tsx index fcc09be82ee..f75e4e60056 100644 --- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sku/edit.tsx +++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/sku/edit.tsx @@ -60,9 +60,9 @@ const Edit = ( {
diff --git a/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/types.ts b/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/types.ts index 11c7375376d..06676bc2720 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/types.ts +++ b/plugins/woocommerce-blocks/assets/js/base/components/block-error-boundary/types.ts @@ -26,7 +26,7 @@ interface BlockErrorBase { */ button?: React.ReactNode; /** - * Controls whether to show the error block or fail silently + * Controls whether to show the error block or fail silently. */ showErrorBlock?: boolean; } diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/coupon/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/coupon/index.tsx index 4d0f25b0c12..df91b57a98a 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/coupon/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/coupon/index.tsx @@ -2,13 +2,14 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; +import { useState, useRef } from '@wordpress/element'; import Button from '@woocommerce/base-components/button'; import LoadingMask from '@woocommerce/base-components/loading-mask'; import { withInstanceId } from '@wordpress/compose'; import { ValidatedTextInput, ValidationInputError, + ValidatedTextInputHandle, Panel, } from '@woocommerce/blocks-components'; import { useSelect } from '@wordpress/data'; @@ -55,6 +56,7 @@ export const TotalsCoupon = ( { validationErrorId: store.getValidationErrorId( textInputId ), }; } ); + const inputRef = useRef< ValidatedTextInputHandle >( null ); const handleCouponSubmit: MouseEventHandler< HTMLButtonElement > = ( e: MouseEvent< HTMLButtonElement > @@ -65,6 +67,8 @@ export const TotalsCoupon = ( { if ( result ) { setCouponValue( '' ); setIsCouponFormVisible( false ); + } else if ( inputRef.current?.focus ) { + inputRef.current.focus(); } } ); } else { @@ -104,6 +108,7 @@ export const TotalsCoupon = ( { focusOnMount={ true } validateOnMount={ false } showError={ false } + ref={ inputRef } />
diff --git a/plugins/woocommerce/patterns/header-minimal.php b/plugins/woocommerce/patterns/header-minimal.php index 589e9ad6f49..18ca4db4a37 100644 --- a/plugins/woocommerce/patterns/header-minimal.php +++ b/plugins/woocommerce/patterns/header-minimal.php @@ -20,9 +20,9 @@
- - + +
diff --git a/plugins/woocommerce/readme.txt b/plugins/woocommerce/readme.txt index 04146902c1c..d3730c2db5b 100644 --- a/plugins/woocommerce/readme.txt +++ b/plugins/woocommerce/readme.txt @@ -4,7 +4,7 @@ Tags: online store, ecommerce, shop, shopping cart, sell online Requires at least: 6.5 Tested up to: 6.6 Requires PHP: 7.4 -Stable tag: 9.2.1 +Stable tag: 9.2.3 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html diff --git a/plugins/woocommerce/src/Admin/API/AI/Images.php b/plugins/woocommerce/src/Admin/API/AI/Images.php index 5acd60e2030..8ac0f7023f4 100644 --- a/plugins/woocommerce/src/Admin/API/AI/Images.php +++ b/plugins/woocommerce/src/Admin/API/AI/Images.php @@ -65,13 +65,10 @@ class Images extends AIEndpoint { if ( $last_business_description === $business_description ) { return rest_ensure_response( - $this->prepare_item_for_response( - array( - 'ai_content_generated' => true, - 'images' => array(), - ), - $request - ) + array( + 'ai_content_generated' => true, + 'images' => array(), + ), ); } diff --git a/plugins/woocommerce/src/Admin/API/Init.php b/plugins/woocommerce/src/Admin/API/Init.php index 65522e28501..67332f10e48 100644 --- a/plugins/woocommerce/src/Admin/API/Init.php +++ b/plugins/woocommerce/src/Admin/API/Init.php @@ -93,6 +93,7 @@ class Init { 'Automattic\WooCommerce\Admin\API\AI\Patterns', 'Automattic\WooCommerce\Admin\API\AI\Product', 'Automattic\WooCommerce\Admin\API\AI\Products', + 'Automattic\WooCommerce\Admin\API\Patterns', ); } diff --git a/plugins/woocommerce/src/Admin/API/Notes.php b/plugins/woocommerce/src/Admin/API/Notes.php index d96d8165c6c..3c21865fcd9 100644 --- a/plugins/woocommerce/src/Admin/API/Notes.php +++ b/plugins/woocommerce/src/Admin/API/Notes.php @@ -538,7 +538,7 @@ class Notes extends \WC_REST_CRUD_Controller { * * @param string $url The URL needing a nonce. * @param string $action The nonce action. - * @param string $name The nonce anme. + * @param string $name The nonce name. * @return string A fully formed URL. */ private function maybe_add_nonce_to_url( string $url, string $action = '', string $name = '' ) : string { @@ -547,7 +547,7 @@ class Notes extends \WC_REST_CRUD_Controller { } if ( empty( $name ) ) { - // Default paramater name. + // Default parameter name. $name = '_wpnonce'; } diff --git a/plugins/woocommerce/src/Admin/API/OnboardingTasks.php b/plugins/woocommerce/src/Admin/API/OnboardingTasks.php index 5e8cfe19ca6..16b7d652b7c 100644 --- a/plugins/woocommerce/src/Admin/API/OnboardingTasks.php +++ b/plugins/woocommerce/src/Admin/API/OnboardingTasks.php @@ -37,7 +37,7 @@ class OnboardingTasks extends \WC_REST_Data_Controller { protected $rest_base = 'onboarding/tasks'; /** - * Duration to milisecond mapping. + * Duration to millisecond mapping. * * @var array */ diff --git a/plugins/woocommerce/src/Admin/API/Patterns.php b/plugins/woocommerce/src/Admin/API/Patterns.php new file mode 100644 index 00000000000..a4a21dc3596 --- /dev/null +++ b/plugins/woocommerce/src/Admin/API/Patterns.php @@ -0,0 +1,93 @@ + \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_pattern' ), + 'permission_callback' => function () { + return is_user_logged_in(); + }, + ), + array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'update_patterns' ), + 'permission_callback' => function () { + return is_user_logged_in(); + }, + ), + ) + ); + } + + /** + * Fetch a single pattern from the PTK to ensure the API is available. + * + * @return WP_Error|WP_REST_Response + */ + public function get_pattern() { + $ptk_client = Package::container()->get( PTKClient::class ); + + $response = $ptk_client->fetch_patterns( + array( + 'per_page' => 1, + ) + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + return rest_ensure_response( + array( + 'success' => true, + ) + ); + } + + /** + * Fetch the patterns from the PTK and update the transient. + * + * @return WP_REST_Response + */ + public function update_patterns() { + $ptk_patterns_store = Package::container()->get( PTKPatternsStore::class ); + + $ptk_patterns_store->fetch_patterns(); + + $block_patterns = Package::container()->get( BlockPatterns::class ); + + $block_patterns->register_ptk_patterns(); + + return rest_ensure_response( + array( + 'success' => true, + ) + ); + } +} diff --git a/plugins/woocommerce/src/Admin/API/PaymentGatewaySuggestions.php b/plugins/woocommerce/src/Admin/API/PaymentGatewaySuggestions.php index 09e23aa22ee..4fea841cdca 100644 --- a/plugins/woocommerce/src/Admin/API/PaymentGatewaySuggestions.php +++ b/plugins/woocommerce/src/Admin/API/PaymentGatewaySuggestions.php @@ -2,7 +2,7 @@ /** * REST API Payment Gateway Suggestions Controller * - * Handles requests to install and activate depedent plugins. + * Handles requests to install and activate dependent plugins. */ namespace Automattic\WooCommerce\Admin\API; diff --git a/plugins/woocommerce/src/Admin/API/Plugins.php b/plugins/woocommerce/src/Admin/API/Plugins.php index 746deb7f84e..f8b3e43150f 100644 --- a/plugins/woocommerce/src/Admin/API/Plugins.php +++ b/plugins/woocommerce/src/Admin/API/Plugins.php @@ -2,7 +2,7 @@ /** * REST API Plugins Controller * - * Handles requests to install and activate depedent plugins. + * Handles requests to install and activate dependent plugins. */ namespace Automattic\WooCommerce\Admin\API; diff --git a/plugins/woocommerce/src/Admin/API/ProductsLowInStock.php b/plugins/woocommerce/src/Admin/API/ProductsLowInStock.php index 223df3303cc..9e8ed8b9556 100644 --- a/plugins/woocommerce/src/Admin/API/ProductsLowInStock.php +++ b/plugins/woocommerce/src/Admin/API/ProductsLowInStock.php @@ -263,7 +263,7 @@ final class ProductsLowInStock extends \WC_REST_Products_Controller { /** * Return a query string for low in stock products. - * The query string incldues the following replacement strings: + * The query string includes the following replacement strings: * - :selects * - :postmeta_join * - :postmeta_wheres diff --git a/plugins/woocommerce/src/Admin/API/Reports/Categories/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Categories/Controller.php index 682009ddcab..463ed1e3482 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Categories/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Categories/Controller.php @@ -32,7 +32,7 @@ class Controller extends GenericController implements ExportableInterface { protected $rest_base = 'reports/categories'; /** - * Get data from `'categories'` Query. + * Get data from `'categories'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Categories/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Categories/Query.php index cf3395fc738..cba0dd7ff62 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Categories/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Categories/Query.php @@ -23,7 +23,7 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Categories\Query * - * @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Categories\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { @@ -32,22 +32,26 @@ class Query extends ReportsQuery { /** * Valid fields for Categories report. * - * @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Categories\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get categories data based on the current query vars. * - * @deprecated 9.3.0 Categories\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Categories\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_categories_query_args', $this->get_query_vars() ); $results = \WC_Data_Store::load( self::REPORT_NAME )->get_data( $args ); return apply_filters( 'woocommerce_analytics_categories_select_query', $results, $args ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Coupons/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Coupons/Controller.php index 83b99679229..d7f1fd5d818 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Coupons/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Coupons/Controller.php @@ -31,7 +31,7 @@ class Controller extends GenericController implements ExportableInterface { protected $rest_base = 'reports/coupons'; /** - * Get data from `'coupons'` Query. + * Get data from `'coupons'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Coupons/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Coupons/Query.php index 8d7620eecd5..690861ae4d4 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Coupons/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Coupons/Query.php @@ -22,29 +22,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Coupons\Query * - * @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Coupons\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Products report. * - * @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Coupons\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get product data based on the current query vars. * - * @deprecated 9.3.0 Coupons\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Coupons\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_coupons_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-coupons' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/Controller.php index f5f4fed67f4..593b3c28915 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/Controller.php @@ -54,7 +54,7 @@ class Controller extends GenericStatsController { } /** - * Get data from `'coupons-stats'` Query. + * Get data from `'coupons-stats'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/Query.php index d0585502aba..0defa18ad41 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Coupons/Stats/Query.php @@ -22,29 +22,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Coupons\Stats\Query * - * @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Coupons\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Products report. * - * @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Coupons\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get product data based on the current query vars. * - * @deprecated 9.3.0 Coupons\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Coupons\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_coupons_stats_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-coupons-stats' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Customers/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Customers/Controller.php index 218596ab530..003fc089b0d 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Customers/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Customers/Controller.php @@ -34,7 +34,7 @@ class Controller extends GenericController implements ExportableInterface { protected $rest_base = 'reports/customers'; /** - * Get data from Query. + * Get data from Customers\Query. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Customers/Stats/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Customers/Stats/Query.php index 5b14922570b..d3adebde7a3 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Customers/Stats/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Customers/Stats/Query.php @@ -23,18 +23,20 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Customers\Stats\Query * - * @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use `Reports\Customers\Query` with a custom name, `GenericQuery`, `\WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Customers report. * - * @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use `Reports\Customers\Query` with a custom name, `GenericQuery`, `\WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`Reports\Customers\Query` with a custom name, `GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array( 'per_page' => get_option( 'posts_per_page' ), // not sure if this should be the default. 'page' => 1, @@ -47,11 +49,13 @@ class Query extends ReportsQuery { /** * Get product data based on the current query vars. * - * @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use Reports\Customers\Query with a custom name, GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Customers\Stats\Query class is deprecated, please use `Reports\Customers\Query` with a custom name, `GenericQuery`, `\WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'x.x.x', '`Reports\Customers\Query` with a custom name, `GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_customers_stats_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-customers-stats' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Downloads/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Downloads/Controller.php index c2e4fed9807..1330e2ad4e1 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Downloads/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Downloads/Controller.php @@ -32,7 +32,7 @@ class Controller extends GenericController implements ExportableInterface { protected $rest_base = 'reports/downloads'; /** - * Get data from `'downloads'` Query. + * Get data from `'downloads'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Downloads/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Downloads/Query.php index 596a6a68892..d47c19deefb 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Downloads/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Downloads/Query.php @@ -22,29 +22,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Downloads\Query * - * @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Downloads\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for downloads report. * - * @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Downloads\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get downloads data based on the current query vars. * - * @deprecated 9.3.0 Downloads\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Downloads\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_downloads_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-downloads' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/Controller.php index c3be86ece6b..9d8e5f1bed2 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/Controller.php @@ -60,7 +60,7 @@ class Controller extends GenericStatsController { } /** - * Get data from `'downloads-stats'` Query. + * Get data from `'downloads-stats'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/Query.php index a86bf113b2e..059218e63f3 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Downloads/Stats/Query.php @@ -12,29 +12,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Downloads\Stats\Query * - * @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Downloads\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Orders report. * - * @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Downloads\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get revenue data based on the current query vars. * - * @deprecated 9.3.0 Downloads\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Downloads\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_downloads_stats_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-downloads-stats' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/GenericController.php b/plugins/woocommerce/src/Admin/API/Reports/GenericController.php index 5ceead76416..2910e23b7d9 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/GenericController.php +++ b/plugins/woocommerce/src/Admin/API/Reports/GenericController.php @@ -175,7 +175,7 @@ abstract class GenericController extends \WC_REST_Reports_Controller { /** * Get the report data. * - * Prepares query params, fetches the report data from the Query object, + * Prepares query params, fetches the report data from the data store, * prepares it for the response, and packs it into the convention-conforming response object. * * @throws \WP_Error When the queried data is invalid. @@ -237,7 +237,7 @@ abstract class GenericController extends \WC_REST_Reports_Controller { } /** - * Maps query arguments from the REST request, to be fed to Query. + * Maps query arguments from the REST request, to be used to query the datastore. * * `WP_REST_Request` does not expose a method to return all params covering defaults, * as it does for `$request['param']` accessor. diff --git a/plugins/woocommerce/src/Admin/API/Reports/GenericStatsController.php b/plugins/woocommerce/src/Admin/API/Reports/GenericStatsController.php index 61f5c195961..5ee255356fa 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/GenericStatsController.php +++ b/plugins/woocommerce/src/Admin/API/Reports/GenericStatsController.php @@ -207,7 +207,7 @@ abstract class GenericStatsController extends GenericController { /** * Get the report data. * - * Prepares query params, fetches the report data from the Query object, + * Prepares query params, fetches the report data from the data store, * prepares it for the response, and packs it into the convention-conforming response object. * * @override GenericController::get_items() diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php index 20d84ed8c60..2a6b81fad94 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php @@ -31,7 +31,7 @@ class Controller extends GenericController implements ExportableInterface { protected $rest_base = 'reports/orders'; /** - * Get data from Query. + * Get data from Orders\Query. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php index c988d146af9..7d42995158b 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php @@ -86,7 +86,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { */ protected function assign_report_columns() { $table_name = self::get_db_table_name(); - // Avoid ambigious columns in SQL query. + // Avoid ambiguous columns in SQL query. $this->report_columns = array( 'order_id' => "DISTINCT {$table_name}.order_id", 'parent_id' => "{$table_name}.parent_id", diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/Controller.php index 8dd2f0c6116..ee5d3f24a4f 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/Controller.php @@ -31,7 +31,7 @@ class Controller extends GenericStatsController { protected $rest_base = 'reports/orders/stats'; /** - * Get data from Query. + * Get data from Orders\Stats\Query. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php index e8492d8937e..4652a2b3a16 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php @@ -96,7 +96,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { */ protected function assign_report_columns() { $table_name = self::get_db_table_name(); - // Avoid ambigious columns in SQL query. + // Avoid ambiguous columns in SQL query. $refunds = "ABS( SUM( CASE WHEN {$table_name}.net_total < 0 THEN {$table_name}.net_total ELSE 0 END ) )"; $gross_sales = "( SUM({$table_name}.total_sales)" . diff --git a/plugins/woocommerce/src/Admin/API/Reports/PerformanceIndicators/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/PerformanceIndicators/Controller.php index d59639ec8dc..564cf206b6a 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/PerformanceIndicators/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/PerformanceIndicators/Controller.php @@ -478,7 +478,7 @@ class Controller extends GenericController { /** * Prepare links for the request. * - * @param \Automattic\WooCommerce\Admin\API\Reports\Query $object Object data. + * @param object $object data. * @return array */ protected function prepare_links( $object ) { diff --git a/plugins/woocommerce/src/Admin/API/Reports/Products/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Products/Controller.php index 5771528e6ac..60df83d6800 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Products/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Products/Controller.php @@ -42,7 +42,7 @@ class Controller extends GenericController implements ExportableInterface { ); /** - * Get data from `'products'` Query. + * Get data from `'products'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Products/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Products/Query.php index 1d3fd72f772..e7bbed3d199 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Products/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Products/Query.php @@ -23,29 +23,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Products\Query * - * @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Products\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Products report. * - * @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Products\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get product data based on the current query vars. * - * @deprecated 9.3.0 Products\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Products\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_products_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-products' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/Controller.php index 0bbc9e33471..acf047a37f9 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/Controller.php @@ -48,7 +48,7 @@ class Controller extends GenericStatsController { } /** - * Get data from `'products-stats'` Query. + * Get data from `'products-stats'` GenericQuery. * * @override GenericController::get_datastore_data() * @@ -61,7 +61,7 @@ class Controller extends GenericStatsController { } /** - * Maps query arguments from the REST request, to be fed to Query. + * Maps query arguments from the REST request to be used to query the datastore. * * @param \WP_REST_Request $request Full request object. * @return array Simplified array of params. diff --git a/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/Query.php index 1314a5ebdbe..d6245c8f732 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Products/Stats/Query.php @@ -23,29 +23,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Products\Stats\Query * - * @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Products\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Products report. * - * @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Products\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get product data based on the current query vars. * - * @deprecated 9.3.0 Products\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Products\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_products_stats_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-products-stats' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Query.php index 7161de91bbd..a6d2a346de9 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Query.php @@ -10,17 +10,31 @@ defined( 'ABSPATH' ) || exit; /** * Admin\API\Reports\Query * - * @deprecated 9.3.0 Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ abstract class Query extends \WC_Object_Query { + + /** + * Create a new query. + * + * @deprecated 9.3.0 Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. + * + * @param array $args Criteria to query on in a format similar to WP_Query. + */ + public function __construct( $args = array() ) { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + parent::__construct( $args ); + } + /** * Get report data matching the current query vars. * - * @deprecated 9.3.0 Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array|object of WC_Product objects */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); /* translators: %s: Method name */ return new \WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) ); } diff --git a/plugins/woocommerce/src/Admin/API/Reports/StatsDataStoreTrait.php b/plugins/woocommerce/src/Admin/API/Reports/StatsDataStoreTrait.php index aefc9778f53..f05204336aa 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/StatsDataStoreTrait.php +++ b/plugins/woocommerce/src/Admin/API/Reports/StatsDataStoreTrait.php @@ -105,7 +105,7 @@ trait StatsDataStoreTrait { 'pages' => $total_pages, 'page_no' => (int) $query_args['page'], ); - // If the requested page is out off range, return the deault empty object. + // If the requested page is out off range, return the default empty object. if ( $query_args['page'] >= 1 && $query_args['page'] <= $total_pages ) { // Fetch the actual data. $data = $this->get_noncached_stats_data( $query_args, $params, $data, $expected_interval_count ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Taxes/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Taxes/Controller.php index 7cf447bbd67..9d6cbd54364 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Taxes/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Taxes/Controller.php @@ -36,7 +36,7 @@ class Controller extends GenericController implements ExportableInterface { protected $rest_base = 'reports/taxes'; /** - * Get data from `'taxes'` Query. + * Get data from `'taxes'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Taxes/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Taxes/Query.php index 1fe84e1ab6e..bcc5ced65d7 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Taxes/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Taxes/Query.php @@ -22,29 +22,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Taxes\Query * - * @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Taxes\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Taxes report. * - * @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Taxes\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get product data based on the current query vars. * - * @deprecated 9.3.0 Taxes\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Taxes\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_taxes_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-taxes' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/Controller.php index 1e0b5c723dc..f551d54d04e 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/Controller.php @@ -84,7 +84,7 @@ class Controller extends GenericStatsController { } /** - * Get data from `'taxes-stats'` Query. + * Get data from `'taxes-stats'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/Query.php index 35f15789614..98301fc1b82 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Taxes/Stats/Query.php @@ -23,29 +23,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Taxes\Stats\Query * - * @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Taxes\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Taxes report. * - * @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Taxes\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get tax stats data based on the current query vars. * - * @deprecated 9.3.0 Taxes\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Taxes\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_taxes_stats_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-taxes-stats' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Variations/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Variations/Controller.php index adbf022424b..43c499e2636 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Variations/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Variations/Controller.php @@ -50,7 +50,7 @@ class Controller extends GenericController implements ExportableInterface { ); /** - * Get data from `'variations'` Query. + * Get data from `'variations'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Variations/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Variations/Query.php index 1fd01151a1d..da0d19ec78a 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Variations/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Variations/Query.php @@ -23,29 +23,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Variations\Query * - * @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Variations\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Products report. * - * @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Variations\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get product data based on the current query vars. * - * @deprecated 9.3.0 Variations\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Variations\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_variations_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-variations' ); diff --git a/plugins/woocommerce/src/Admin/API/Reports/Variations/Stats/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Variations/Stats/Controller.php index dc3ff448d2c..68e6590dce7 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Variations/Stats/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Variations/Stats/Controller.php @@ -46,7 +46,7 @@ class Controller extends GenericStatsController { } /** - * Get data from `'variations-stats'` Query. + * Get data from `'variations-stats'` GenericQuery. * * @override GenericController::get_datastore_data() * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Variations/Stats/Query.php b/plugins/woocommerce/src/Admin/API/Reports/Variations/Stats/Query.php index 5a6e08bb089..0613472bb38 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Variations/Stats/Query.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Variations/Stats/Query.php @@ -23,29 +23,33 @@ use Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery; /** * API\Reports\Variations\Stats\Query * - * @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Variations\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. */ class Query extends ReportsQuery { /** * Valid fields for Products report. * - * @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Variations\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ protected function get_default_query_vars() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + return array(); } /** * Get variations data based on the current query vars. * - * @deprecated 9.3.0 Variations\Stats\Query class is deprecated, please use GenericQuery or \WC_Object_Query instead. + * @deprecated 9.3.0 Variations\Stats\Query class is deprecated. Please use `GenericQuery`, \WC_Object_Query`, or use `DataStore` directly. * * @return array */ public function get_data() { + wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '9.3.0', '`GenericQuery`, `\WC_Object_Query`, or direct `DataStore` use' ); + $args = apply_filters( 'woocommerce_analytics_variations_stats_query_args', $this->get_query_vars() ); $data_store = \WC_Data_Store::load( 'report-variations-stats' ); diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php index cfc54d4569c..063e01fd1a5 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php @@ -61,7 +61,7 @@ abstract class Task { protected $task_list; /** - * Duration to milisecond mapping. + * Duration to millisecond mapping. * * @var string */ diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Tax.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Tax.php index 662d4fec80b..ba33d9b2362 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Tax.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Tax.php @@ -121,7 +121,7 @@ class Tax extends Task { } /** - * Addtional data. + * Additional data. * * @return array */ @@ -129,9 +129,11 @@ class Tax extends Task { return array( 'avalara_activated' => PluginsHelper::is_plugin_active( 'woocommerce-avatax' ), 'tax_jar_activated' => class_exists( 'WC_Taxjar' ), + 'stripe_tax_activated' => PluginsHelper::is_plugin_active( 'stripe-tax-for-woocommerce' ), 'woocommerce_tax_activated' => PluginsHelper::is_plugin_active( 'woocommerce-tax' ), 'woocommerce_shipping_activated' => PluginsHelper::is_plugin_active( 'woocommerce-shipping' ), 'woocommerce_tax_countries' => self::get_automated_support_countries(), + 'stripe_tax_countries' => self::get_stripe_tax_support_countries(), ); } @@ -162,4 +164,54 @@ class Tax extends Task { return $tax_supported_countries; } + + /** + * Get an array of countries that support Stripe tax. + * + * @return array + */ + private static function get_stripe_tax_support_countries() { + // https://docs.stripe.com/tax/supported-countries#supported-countries accurate as of 2024-08-26. + // countries with remote sales not included. + return array( + 'AU', + 'AT', + 'BE', + 'BG', + 'CA', + 'HR', + 'CY', + 'CZ', + 'DK', + 'EE', + 'FI', + 'FR', + 'DE', + 'GR', + 'HK', + 'HU', + 'IE', + 'IT', + 'JP', + 'LV', + 'LT', + 'LU', + 'MT', + 'NL', + 'NZ', + 'NO', + 'PL', + 'PT', + 'RO', + 'SG', + 'SK', + 'SI', + 'ES', + 'SE', + 'CH', + 'AE', + 'GB', + 'US', + ); + } } diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php index 5a269ca620f..5c6d45255f6 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php @@ -147,11 +147,9 @@ class WooCommercePayments extends Task { * @return bool */ public static function is_connected() { - if ( class_exists( '\WC_Payments' ) ) { - $wc_payments_gateway = \WC_Payments::get_gateway(); - return method_exists( $wc_payments_gateway, 'is_connected' ) - ? $wc_payments_gateway->is_connected() - : false; + $wc_payments_gateway = self::get_woo_payments_gateway(); + if ( $wc_payments_gateway && method_exists( $wc_payments_gateway, 'is_connected' ) ) { + return $wc_payments_gateway->is_connected(); } return false; @@ -164,11 +162,9 @@ class WooCommercePayments extends Task { * @return bool */ public static function is_account_partially_onboarded() { - if ( class_exists( '\WC_Payments' ) ) { - $wc_payments_gateway = \WC_Payments::get_gateway(); - return method_exists( $wc_payments_gateway, 'is_account_partially_onboarded' ) - ? $wc_payments_gateway->is_account_partially_onboarded() - : false; + $wc_payments_gateway = self::get_woo_payments_gateway(); + if ( $wc_payments_gateway && method_exists( $wc_payments_gateway, 'is_account_partially_onboarded' ) ) { + return $wc_payments_gateway->is_account_partially_onboarded(); } return false; @@ -195,4 +191,17 @@ class WooCommercePayments extends Task { } return false; } + + /** + * Get the WooPayments gateway. + * + * @return \WC_Payments|null + */ + private static function get_woo_payments_gateway() { + $payment_gateways = WC()->payment_gateways->payment_gateways(); + if ( isset( $payment_gateways['woocommerce_payments'] ) ) { + return $payment_gateways['woocommerce_payments']; + } + return null; + } } diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php index d31f8cf0e04..7e274815e91 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php @@ -172,7 +172,7 @@ class Init { if ( ! PageController::is_admin_page() ) { return; } - // Dequeing this to avoid conflicts, until we remove the 'woocommerce-page' class. + // Dequeuing this to avoid conflicts, until we remove the 'woocommerce-page' class. wp_dequeue_style( 'woocommerce-blocktheme' ); } diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductFormsController.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductFormsController.php index d5b586d7ad0..f9fc30bc84e 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductFormsController.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductFormsController.php @@ -59,7 +59,7 @@ class ProductFormsController { } /** - * Create ot update a product_form post for each product form template. + * Create or update a product_form post for each product form template. * If the post already exists, it will be updated. * If the post does not exist, it will be created even if the action is `update`. * diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/RedirectionController.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/RedirectionController.php index c5e2e135243..be9cb5e4d61 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/RedirectionController.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/RedirectionController.php @@ -96,7 +96,7 @@ class RedirectionController { /** * Check if a product is supported by the new experience. * - * @param array $product_templates The registered product teamplates. + * @param array $product_templates The registered product templates. */ public function set_product_templates( array $product_templates ): void { $this->product_templates = $product_templates; diff --git a/plugins/woocommerce/src/Admin/Features/ProductDataViews/Init.php b/plugins/woocommerce/src/Admin/Features/ProductDataViews/Init.php new file mode 100644 index 00000000000..26909eff3ce --- /dev/null +++ b/plugins/woocommerce/src/Admin/Features/ProductDataViews/Init.php @@ -0,0 +1,145 @@ +has_data_views_support() ) { + add_action( 'admin_menu', array( $this, 'woocommerce_add_new_products_dashboard' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + + if ( $this->is_product_data_view_page() ) { + add_filter( + 'admin_body_class', + static function ( $classes ) { + return "$classes is-fullscreen-mode"; + } + ); + } + } + } + + /** + * Returns true if we are on a JS powered admin page. + */ + private static function is_product_data_view_page() { + // phpcs:disable WordPress.Security.NonceVerification + return isset( $_GET['page'] ) && 'woocommerce-products-dashboard' === $_GET['page']; + // phpcs:enable WordPress.Security.NonceVerification + } + + /** + * Checks for data views support. + */ + private function has_data_views_support() { + if ( Utils::wp_version_compare( '6.6', '>=' ) ) { + return true; + } + + if ( is_plugin_active( 'gutenberg/gutenberg.php' ) ) { + $gutenberg_version = ''; + + if ( defined( 'GUTENBERG_VERSION' ) ) { + $gutenberg_version = GUTENBERG_VERSION; + } + + if ( ! $gutenberg_version ) { + $gutenberg_data = get_file_data( + WP_PLUGIN_DIR . '/gutenberg/gutenberg.php', + array( 'Version' => 'Version' ) + ); + $gutenberg_version = $gutenberg_data['Version']; + } + return version_compare( $gutenberg_version, '19.0', '>=' ); + } + + return false; + } + + /** + * Enqueue styles needed for the rich text editor. + */ + public function enqueue_styles() { + if ( ! $this->is_product_data_view_page() ) { + return; + } + wp_enqueue_style( 'wc-product-editor' ); + } + + /** + * Enqueue scripts needed for the product form block editor. + */ + public function enqueue_scripts() { + if ( ! $this->is_product_data_view_page() ) { + return; + } + + $script_handle = 'wc-admin-edit-product'; + wp_register_script( $script_handle, '', array( 'wp-blocks' ), '0.1.0', true ); + wp_enqueue_script( $script_handle ); + wp_enqueue_media(); + wp_register_style( 'wc-global-presets', false ); // phpcs:ignore + wp_add_inline_style( 'wc-global-presets', wp_get_global_stylesheet( array( 'presets' ) ) ); + wp_enqueue_style( 'wc-global-presets' ); + } + + /** + * Replaces the default posts menu item with the new posts dashboard. + */ + public function woocommerce_add_new_products_dashboard() { + $gutenberg_experiments = get_option( 'gutenberg-experiments' ); + if ( ! $gutenberg_experiments ) { + return; + } + $ptype_obj = get_post_type_object( 'product' ); + add_submenu_page( + 'woocommerce', + $ptype_obj->labels->name, + esc_html__( 'All Products', 'woocommerce' ), + 'manage_woocommerce', + 'woocommerce-products-dashboard', + array( $this, 'woocommerce_products_dashboard' ), + 1 + ); + } + + /** + * Renders the new posts dashboard page. + */ + public function woocommerce_products_dashboard() { + $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min'; + $version = Constants::get_constant( 'WC_VERSION' ); + if ( function_exists( 'gutenberg_url' ) ) { + // phpcs:disable WordPress.WP.EnqueuedResourceParameters.MissingVersion + wp_register_style( + 'wp-gutenberg-posts-dashboard', + gutenberg_url( 'build/edit-site/posts.css', __FILE__ ), + array( 'wp-components' ), + ); + // phpcs:enable WordPress.WP.EnqueuedResourceParameters.MissingVersion + wp_enqueue_style( 'wp-gutenberg-posts-dashboard' ); + } + WCAdminAssets::get_instance(); + wp_enqueue_script( 'wc-admin-product-editor', WC()->plugin_url() . '/assets/js/admin/product-editor' . $suffix . '.js', array( 'wc-product-editor' ), $version, false ); + wp_add_inline_script( 'wp-edit-site', 'window.wc.productEditor.initializeProductsDashboard( "woocommerce-products-dashboard" );', 'after' ); + wp_enqueue_script( 'wp-edit-site' ); + + echo '
'; + } +} diff --git a/plugins/woocommerce/src/Admin/Notes/Note.php b/plugins/woocommerce/src/Admin/Notes/Note.php index 8191b6aede7..de8cde59dc7 100644 --- a/plugins/woocommerce/src/Admin/Notes/Note.php +++ b/plugins/woocommerce/src/Admin/Notes/Note.php @@ -676,7 +676,7 @@ class Note extends \WC_Data { * * @param string $note_action_name Name of action to add a nonce to. * @param string $nonce_action The nonce action. - * @param string $nonce_name The nonce Name. This is used as the paramater name in the resulting URL for the action. + * @param string $nonce_name The nonce Name. This is used as the parameter name in the resulting URL for the action. * @return void * @throws \Exception If note name cannot be found. */ diff --git a/plugins/woocommerce/src/Admin/PageController.php b/plugins/woocommerce/src/Admin/PageController.php index 16d8b7125a2..0d8738d1ba5 100644 --- a/plugins/woocommerce/src/Admin/PageController.php +++ b/plugins/woocommerce/src/Admin/PageController.php @@ -220,7 +220,7 @@ class PageController { */ public function get_current_page() { // If 'current_screen' hasn't fired yet, the current page calculation - // will fail which causes `false` to be returned for all subsquent calls. + // will fail which causes `false` to be returned for all subsequent calls. if ( ! did_action( 'current_screen' ) ) { _doing_it_wrong( __FUNCTION__, esc_html__( 'Current page retrieval should be called on or after the `current_screen` hook.', 'woocommerce' ), '0.16.0' ); } @@ -394,7 +394,7 @@ class PageController { } /** - * Returns true if we are on a page registed with this controller. + * Returns true if we are on a page registered with this controller. * * @return boolean */ @@ -530,7 +530,7 @@ class PageController { */ public function remove_app_entry_page_menu_item() { global $submenu; - // User does not have capabilites to see the submenu. + // User does not have capabilities to see the submenu. if ( ! current_user_can( 'manage_woocommerce' ) || empty( $submenu['woocommerce'] ) ) { return; } diff --git a/plugins/woocommerce/src/Admin/PluginsHelper.php b/plugins/woocommerce/src/Admin/PluginsHelper.php index cc5d302908b..efcf9152f9e 100644 --- a/plugins/woocommerce/src/Admin/PluginsHelper.php +++ b/plugins/woocommerce/src/Admin/PluginsHelper.php @@ -365,7 +365,7 @@ class PluginsHelper { } /** - * Callback regsitered by OnboardingPlugins::install_and_activate_async. + * Callback registered by OnboardingPlugins::install_and_activate_async. * * It is used to call install_plugins and activate_plugins with a custom logger. * diff --git a/plugins/woocommerce/src/Admin/PluginsInstallLoggers/AsyncPluginsInstallLogger.php b/plugins/woocommerce/src/Admin/PluginsInstallLoggers/AsyncPluginsInstallLogger.php index 1e884328851..87faf90aa7a 100644 --- a/plugins/woocommerce/src/Admin/PluginsInstallLoggers/AsyncPluginsInstallLogger.php +++ b/plugins/woocommerce/src/Admin/PluginsInstallLoggers/AsyncPluginsInstallLogger.php @@ -32,7 +32,7 @@ class AsyncPluginsInstallLogger implements PluginsInstallLogger { 'no' ); - // Set status as failed in case we run out of exectuion time. + // Set status as failed in case we run out of execution time. register_shutdown_function( function () { $error = error_get_last(); @@ -57,7 +57,7 @@ class AsyncPluginsInstallLogger implements PluginsInstallLogger { } /** - * Retreive the option. + * Retrieve the option. * * @return false|mixed|void */ diff --git a/plugins/woocommerce/src/Admin/RemoteSpecs/RuleProcessors/Transformers/TransformerService.php b/plugins/woocommerce/src/Admin/RemoteSpecs/RuleProcessors/Transformers/TransformerService.php index ec3fc8c9c5b..9519627f374 100644 --- a/plugins/woocommerce/src/Admin/RemoteSpecs/RuleProcessors/Transformers/TransformerService.php +++ b/plugins/woocommerce/src/Admin/RemoteSpecs/RuleProcessors/Transformers/TransformerService.php @@ -39,7 +39,7 @@ class TransformerService { * @param bool $is_default_set flag on is default value set. * @param string $default_value default value. * - * @throws InvalidArgumentException Throws when one of the requried arguments is missing. + * @throws InvalidArgumentException Throws when one of the required arguments is missing. * @return mixed|null */ public static function apply( $target_value, array $transformer_configs, $is_default_set, $default_value ) { diff --git a/plugins/woocommerce/src/Blocks/AssetsController.php b/plugins/woocommerce/src/Blocks/AssetsController.php index 1bb55c0f0c1..13236dc8145 100644 --- a/plugins/woocommerce/src/Blocks/AssetsController.php +++ b/plugins/woocommerce/src/Blocks/AssetsController.php @@ -64,11 +64,11 @@ final class AssetsController { $this->api->register_script( 'wc-price-format', 'assets/client/blocks/price-format.js', array(), false ); // Vendor scripts for blocks frontends (not including cart and checkout). - $this->api->register_script( 'wc-blocks-frontend-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-frontend-vendors-frontend' ), array(), false ); + $this->api->register_script( 'wc-blocks-frontend-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-frontend-vendors-frontend' ), array(), true ); // Cart and checkout frontend scripts. - $this->api->register_script( 'wc-cart-checkout-vendors', $this->api->get_block_asset_build_path( 'wc-cart-checkout-vendors-frontend' ), array(), false ); - $this->api->register_script( 'wc-cart-checkout-base', $this->api->get_block_asset_build_path( 'wc-cart-checkout-base-frontend' ), array(), false ); + $this->api->register_script( 'wc-cart-checkout-vendors', $this->api->get_block_asset_build_path( 'wc-cart-checkout-vendors-frontend' ), array(), true ); + $this->api->register_script( 'wc-cart-checkout-base', $this->api->get_block_asset_build_path( 'wc-cart-checkout-base-frontend' ), array(), true ); $this->api->register_script( 'wc-blocks-checkout', 'assets/client/blocks/blocks-checkout.js' ); $this->api->register_script( 'wc-blocks-components', 'assets/client/blocks/blocks-components.js' ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Summary.php b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Summary.php index e36c1d77f74..3560292ab02 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Summary.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Summary.php @@ -29,11 +29,11 @@ class Summary extends AbstractOrderConfirmationBlock { } $content = '
    '; - $content .= $this->render_summary_row( __( 'Order number:', 'woocommerce' ), $order->get_order_number() ); + $content .= $this->render_summary_row( __( 'Order #:', 'woocommerce' ), $order->get_order_number() ); $content .= $this->render_summary_row( __( 'Date:', 'woocommerce' ), wc_format_datetime( $order->get_date_created() ) ); $content .= $this->render_summary_row( __( 'Total:', 'woocommerce' ), $order->get_formatted_order_total() ); $content .= $this->render_summary_row( __( 'Email:', 'woocommerce' ), $order->get_billing_email() ); - $content .= $this->render_summary_row( __( 'Payment method:', 'woocommerce' ), $order->get_payment_method_title() ); + $content .= $this->render_summary_row( __( 'Payment:', 'woocommerce' ), $order->get_payment_method_title() ); $content .= '
'; return $content; diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php index 45f5ab2b48e..bf42d344a52 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection.php @@ -324,7 +324,7 @@ class ProductCollection extends AbstractBlock { $is_enhanced_pagination_enabled = ! ( $this->parsed_block['attrs']['forcePageReload'] ?? false ); // Only proceed if the block is a product collection block, - // enhaced pagination is enabled and query IDs match. + // enhanced pagination is enabled and query IDs match. if ( $is_product_collection_block && $is_enhanced_pagination_enabled && $query_id === $parsed_query_id ) { $block_content = $this->process_pagination_links( $block_content ); } @@ -414,8 +414,8 @@ class ProductCollection extends AbstractBlock { /** * Check inner blocks of Product Collection block if there's one - * incompatible with Interactivity API and if so, disable client-side - * naviagtion. + * incompatible with the Interactivity API and if so, disable client-side + * navigation. * * @param array $parsed_block The block being rendered. * @return string Returns the parsed block, unmodified. @@ -867,7 +867,7 @@ class ProductCollection extends AbstractBlock { * - For array items with numeric keys, we merge them as normal. * - For array items with string keys: * - * - If the value isn't array, we'll use the value comming from the merge array. + * - If the value isn't array, we'll use the value coming from the merge array. * $base = ['orderby' => 'date'] * $new = ['orderby' => 'meta_value_num'] * Result: ['orderby' => 'meta_value_num'] diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductDetails.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductDetails.php index c512966d6ad..907d198dde9 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductDetails.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductDetails.php @@ -1,6 +1,7 @@ render_tabs(); + if ( $hide_tab_title ) { + remove_filter( 'woocommerce_product_description_heading', '__return_empty_string' ); + remove_filter( 'woocommerce_product_additional_information_heading', '__return_empty_string' ); + remove_filter( 'woocommerce_reviews_title', '__return_empty_string' ); + + // Remove the first `h2` of every `.wc-tab`. This is required for the Reviews tabs when there are no reviews and for plugin tabs. + $tabs_html = new WP_HTML_Tag_Processor( $tabs ); + while ( $tabs_html->next_tag( array( 'class_name' => 'wc-tab' ) ) ) { + if ( $tabs_html->next_tag( 'h2' ) ) { + $tabs_html->set_attribute( 'hidden', 'true' ); + } + } + $tabs = $tabs_html->get_updated_html(); + } + $classname = $attributes['className'] ?? ''; $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterPrice.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterPrice.php index 838adae31b3..97b73763b65 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterPrice.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterPrice.php @@ -234,6 +234,7 @@ final class ProductFilterPrice extends AbstractBlock { data-wc-bind--max="context.maxRange" data-wc-bind--value="context.minPrice" data-wc-on--change="actions.updateProducts" + data-wc-on--input="actions.updateRange" >
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php index ec5b78d9f6d..8183c1b9f5e 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php @@ -778,7 +778,7 @@ class ProductQuery extends AbstractBlock { * - For array items with numeric keys, we merge them as normal. * - For array items with string keys: * - * - If the value isn't array, we'll use the value comming from the merge array. + * - If the value isn't array, we'll use the value coming from the merge array. * $base = ['orderby' => 'date'] * $new = ['orderby' => 'meta_value_num'] * Result: ['orderby' => 'meta_value_num'] diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php index 55e95ccca87..1d729677171 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php @@ -114,7 +114,9 @@ class ProductRating extends AbstractBlock { $post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : ''; $product = wc_get_product( $post_id ); - if ( $product && $product->get_review_count() > 0 ) { + if ( $product && $product->get_review_count() > 0 + && $product->get_reviews_allowed() + && wc_reviews_enabled() ) { $product_reviews_count = $product->get_review_count(); $product_rating = $product->get_average_rating(); $parsed_attributes = $this->parse_attributes( $attributes ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypesController.php b/plugins/woocommerce/src/Blocks/BlockTypesController.php index 5eb5ab2c8a0..f6d84ecc051 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypesController.php +++ b/plugins/woocommerce/src/Blocks/BlockTypesController.php @@ -285,7 +285,7 @@ final class BlockTypesController { * and prevent them from showing as an option in the Legacy Widget block. * * @param array $widget_types An array of widgets hidden in core. - * @return array $widget_types An array inluding the WooCommerce widgets to hide. + * @return array $widget_types An array including the WooCommerce widgets to hide. */ public function hide_legacy_widgets_with_block_equivalent( $widget_types ) { array_push( diff --git a/plugins/woocommerce/src/Blocks/Domain/Bootstrap.php b/plugins/woocommerce/src/Blocks/Domain/Bootstrap.php index 7966386aceb..dbf929aa3fb 100644 --- a/plugins/woocommerce/src/Blocks/Domain/Bootstrap.php +++ b/plugins/woocommerce/src/Blocks/Domain/Bootstrap.php @@ -169,8 +169,6 @@ class Bootstrap { $this->container->get( BlockPatterns::class ); $this->container->get( BlockTypesController::class ); $this->container->get( ClassicTemplatesCompatibility::class ); - $this->container->get( ArchiveProductTemplatesCompatibility::class )->init(); - $this->container->get( SingleProductTemplateCompatibility::class )->init(); $this->container->get( Notices::class )->init(); $this->container->get( PTKPatternsStore::class ); $this->container->get( TemplateOptions::class )->init(); @@ -276,18 +274,6 @@ class Bootstrap { return new ClassicTemplatesCompatibility( $asset_data_registry ); } ); - $this->container->register( - ArchiveProductTemplatesCompatibility::class, - function () { - return new ArchiveProductTemplatesCompatibility(); - } - ); - $this->container->register( - SingleProductTemplateCompatibility::class, - function () { - return new SingleProductTemplateCompatibility(); - } - ); $this->container->register( DraftOrders::class, function ( Container $container ) { diff --git a/plugins/woocommerce/src/Blocks/QueryFilters.php b/plugins/woocommerce/src/Blocks/QueryFilters.php index 76489d8a8cd..274566be8b0 100644 --- a/plugins/woocommerce/src/Blocks/QueryFilters.php +++ b/plugins/woocommerce/src/Blocks/QueryFilters.php @@ -18,7 +18,7 @@ final class QueryFilters { } /** - * Filter the posts clauses of the main query to suport global filters. + * Filter the posts clauses of the main query to support global filters. * * @param array $args Query args. * @param \WP_Query $wp_query WP_Query object. diff --git a/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php b/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php index 3d622a345c2..cbd427547b0 100644 --- a/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php +++ b/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php @@ -21,10 +21,6 @@ abstract class AbstractTemplateCompatibility { * Initialization method. */ public function init() { - if ( ! wc_current_theme_is_fse_theme() ) { - return; - } - $this->set_hook_data(); add_filter( @@ -61,9 +57,9 @@ abstract class AbstractTemplateCompatibility { * @since 7.6.0 * @param boolean. */ - $is_disabled_compatility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false ); + $is_disabled_compatibility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false ); - if ( $is_disabled_compatility_layer ) { + if ( $is_disabled_compatibility_layer ) { return $block_content; } diff --git a/plugins/woocommerce/src/Blocks/Templates/ArchiveProductTemplatesCompatibility.php b/plugins/woocommerce/src/Blocks/Templates/ArchiveProductTemplatesCompatibility.php index 74f36155582..6fba40e7149 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ArchiveProductTemplatesCompatibility.php +++ b/plugins/woocommerce/src/Blocks/Templates/ArchiveProductTemplatesCompatibility.php @@ -320,7 +320,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility } /** - * Check if block is within the product-query namespace + * Check whether block is within the product-query namespace. * * @param array $block Parsed block data. */ @@ -331,7 +331,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility } /** - * Check if block has isInherited attribute asigned + * Check whether block has isInherited attribute assigned. * * @param array $block Parsed block data. */ @@ -357,7 +357,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility } /** - * Check if block is a Post template + * Check whether block is a Post template. * * @param string $block_name Block name. */ @@ -366,7 +366,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility } /** - * Check if block is a Product Template + * Check whether block is a Product Template. * * @param string $block_name Block name. */ @@ -375,7 +375,7 @@ class ArchiveProductTemplatesCompatibility extends AbstractTemplateCompatibility } /** - * Check if block is eaither a Post template or Product Template + * Check if block is either a Post template or a Product Template * * @param string $block_name Block name. */ diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductAttributeTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductAttributeTemplate.php index 828133704a3..153eadb3c9f 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ProductAttributeTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/ProductAttributeTemplate.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Blocks\Templates; +use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; /** @@ -61,6 +62,9 @@ class ProductAttributeTemplate extends AbstractTemplate { } if ( isset( $queried_object->taxonomy ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) ) { + $compatibility_layer = new ArchiveProductTemplatesCompatibility(); + $compatibility_layer->init(); + $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) ); if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) { diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php index 2f3d3383ccb..7059ed379b9 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Blocks\Templates; +use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; /** @@ -49,6 +50,9 @@ class ProductCatalogTemplate extends AbstractTemplate { */ public function render_block_template() { if ( ! is_embed() && ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) ) { + $compatibility_layer = new ArchiveProductTemplatesCompatibility(); + $compatibility_layer->init(); + $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) ); if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) { diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductCategoryTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductCategoryTemplate.php index 604cbb51aa7..233d7c2a877 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ProductCategoryTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/ProductCategoryTemplate.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Blocks\Templates; +use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; /** @@ -55,6 +56,9 @@ class ProductCategoryTemplate extends AbstractTemplate { */ public function render_block_template() { if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_cat' ) ) { + $compatibility_layer = new ArchiveProductTemplatesCompatibility(); + $compatibility_layer->init(); + $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) ); if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) { diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductSearchResultsTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductSearchResultsTemplate.php index ea8daab4768..c421fa7a0a0 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ProductSearchResultsTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/ProductSearchResultsTemplate.php @@ -1,6 +1,7 @@ init(); + $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) ); if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) { diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductTagTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductTagTemplate.php index 6049f429dcd..fef90fdc5ad 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ProductTagTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/ProductTagTemplate.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Blocks\Templates; +use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; /** @@ -55,6 +56,9 @@ class ProductTagTemplate extends AbstractTemplate { */ public function render_block_template() { if ( ! is_embed() && is_product_taxonomy() && is_tax( 'product_tag' ) ) { + $compatibility_layer = new ArchiveProductTemplatesCompatibility(); + $compatibility_layer->init(); + $templates = get_block_templates( array( 'slug__in' => array( self::SLUG ) ) ); if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) { diff --git a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php index 942f807422f..839ed177e09 100644 --- a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php @@ -23,7 +23,7 @@ class SingleProductTemplate extends AbstractTemplate { */ public function init() { add_action( 'template_redirect', array( $this, 'render_block_template' ) ); - add_filter( 'get_block_templates', array( $this, 'update_single_product_content' ), 11, 3 ); + add_filter( 'get_block_templates', array( $this, 'update_single_product_content' ), 11, 1 ); } /** @@ -51,13 +51,34 @@ class SingleProductTemplate extends AbstractTemplate { if ( ! is_embed() && is_singular( 'product' ) ) { global $post; - $valid_slugs = array( self::SLUG ); - if ( 'product' === $post->post_type && $post->post_name ) { + $compatibility_layer = new SingleProductTemplateCompatibility(); + $compatibility_layer->init(); + + $valid_slugs = array( self::SLUG ); + $single_product_slug = 'product' === $post->post_type && $post->post_name ? 'single-product-' . $post->post_name : ''; + if ( $single_product_slug ) { $valid_slugs[] = 'single-product-' . $post->post_name; } $templates = get_block_templates( array( 'slug__in' => $valid_slugs ) ); - if ( isset( $templates[0] ) && BlockTemplateUtils::template_has_legacy_template_block( $templates[0] ) ) { + if ( count( $templates ) === 0 ) { + return; + } + + // Use the first template by default. + $template = $templates[0]; + + // Check if there is a template matching the slug `single-product-{post_name}`. + if ( count( $valid_slugs ) > 1 && count( $templates ) > 1 ) { + foreach ( $templates as $t ) { + if ( $single_product_slug === $t->slug ) { + $template = $t; + break; + } + } + } + + if ( isset( $template ) && BlockTemplateUtils::template_has_legacy_template_block( $template ) ) { add_filter( 'woocommerce_disable_compatibility_layer', '__return_true' ); } @@ -68,12 +89,10 @@ class SingleProductTemplate extends AbstractTemplate { /** * Add the block template objects to be used. * - * @param array $query_result Array of template objects. - * @param array $query Optional. Arguments to retrieve templates. - * @param string $template_type wp_template or wp_template_part. + * @param array $query_result Array of template objects. * @return array */ - public function update_single_product_content( $query_result, $query, $template_type ) { + public function update_single_product_content( $query_result ) { $query_result = array_map( function ( $template ) { if ( str_contains( $template->slug, self::SLUG ) ) { diff --git a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplateCompatibility.php b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplateCompatibility.php index 05191bcbdbb..caa7b691576 100644 --- a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplateCompatibility.php +++ b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplateCompatibility.php @@ -1,6 +1,8 @@ get_registered( $block['attrs']['slug'] ); + $pattern_blocks = parse_blocks( $pattern['content'] ); + + if ( self::has_block_including_patterns( $block_names, $pattern_blocks ) ) { + return true; + } + } + } + + return false; + } + /** * Returns whether the passed `$template` has the legacy template block. * @@ -703,7 +736,13 @@ class BlockTemplateUtils { * @return boolean */ public static function template_has_legacy_template_block( $template ) { - return has_block( 'woocommerce/legacy-template', $template->content ); + if ( has_block( 'woocommerce/legacy-template', $template->content ) ) { + return true; + } + + $blocks = parse_blocks( $template->content ); + + return self::has_block_including_patterns( array( 'woocommerce/legacy-template' ), $blocks ); } /** diff --git a/plugins/woocommerce/src/Caching/ObjectCache.php b/plugins/woocommerce/src/Caching/ObjectCache.php index 925509d17b4..646308a3c32 100644 --- a/plugins/woocommerce/src/Caching/ObjectCache.php +++ b/plugins/woocommerce/src/Caching/ObjectCache.php @@ -166,7 +166,7 @@ abstract class ObjectCache { * @param object|array $object The new object that will replace the already cached one. * @param int|string|null $id Id of the object to be cached, if null, get_object_id will be used to get it. * @param int $expiration Expiration of the cached data in seconds from the current time, or DEFAULT_EXPIRATION to use the default value. - * @return bool True on success, false on error or if no object wiith the supplied id was cached. + * @return bool True on success, false on error or if no object with the supplied id was cached. * @throws CacheException Invalid parameter, or null id was passed and get_object_id returns null too. */ public function update_if_cached( $object, $id = null, int $expiration = self::DEFAULT_EXPIRATION ): bool { diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php index aa804aef3a8..ab60693811a 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php @@ -330,7 +330,7 @@ class CLIRunner { * --- * * [--order-types] - * : Comma seperated list of order types that needs to be verified. For example, --order-types=shop_order,shop_order_refund + * : Comma-separated list of order types that needs to be verified. For example, --order-types=shop_order,shop_order_refund * --- * default: Output of function `wc_get_order_types( 'cot-migration' )` * @@ -385,7 +385,7 @@ class CLIRunner { if ( 0 === count( $order_types ) ) { return WP_CLI::error( sprintf( - /* Translators: %s is the comma seperated list of order types. */ + /* Translators: %s is the comma-separated list of order types. */ __( 'Passed order type does not match any registered order types. Following order types are registered: %s', 'woocommerce' ), implode( ',', wc_get_order_types( 'cot-migration' ) ) ) @@ -927,7 +927,7 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC; * # Cleanup post data for order 314. * $ wp wc hpos cleanup 314 * - * # Cleanup postmeta for orders with IDs betweeen 10 and 100 and order 314. + * # Cleanup postmeta for orders with IDs between 10 and 100 and order 314. * $ wp wc hpos cleanup 10-100 314 * * # Cleanup postmeta for all orders. @@ -1161,10 +1161,10 @@ ORDER BY $meta_table.order_id ASC, $meta_table.meta_key ASC; * --- * * [--meta_keys=] - * : Comma separated list of meta keys to backfill. + * : Comma-separated list of meta keys to backfill. * * [--props=] - * : Comma separated list of order properties to backfill. + * : Comma-separated list of order properties to backfill. * * @since 8.6.0 * diff --git a/plugins/woocommerce/src/Database/Migrations/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/MetaToCustomTableMigrator.php index 1aa9740e146..573fdedace3 100644 --- a/plugins/woocommerce/src/Database/Migrations/MetaToCustomTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/MetaToCustomTableMigrator.php @@ -134,7 +134,7 @@ abstract class MetaToCustomTableMigrator extends TableMigrator { * @return string Generated queries for batch update. Would be of the form: * INSERT INTO $table ( $columns ) VALUES * ($value for row 1) - * ($valye for row 2) + * ($value for row 2) * ... * ON DUPLICATE KEY UPDATE * $column1 = VALUES($column1) diff --git a/plugins/woocommerce/src/Database/Migrations/TableMigrator.php b/plugins/woocommerce/src/Database/Migrations/TableMigrator.php index 4d6665c08f8..e9b2f0a283b 100644 --- a/plugins/woocommerce/src/Database/Migrations/TableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/TableMigrator.php @@ -14,7 +14,7 @@ namespace Automattic\WooCommerce\Database\Migrations; abstract class TableMigrator { /** - * An array of cummulated error messages. + * An array of cumulated error messages. * * @var array */ diff --git a/plugins/woocommerce/src/Internal/Admin/Homescreen.php b/plugins/woocommerce/src/Internal/Admin/Homescreen.php index 3356ed5c67f..1a9cd90f776 100644 --- a/plugins/woocommerce/src/Internal/Admin/Homescreen.php +++ b/plugins/woocommerce/src/Internal/Admin/Homescreen.php @@ -63,7 +63,7 @@ class Homescreen { /** * Set free shipping in the same country as the store default - * Flag rate in all other countries when any of the following conditions are ture + * Flag rate in all other countries when any of the following conditions are true * * - The store sells physical products, has JP and WCS installed and connected, and is located in the US. * - The store sells physical products, and is not located in US/Canada/Australia/UK (irrelevant if JP is installed or not). @@ -242,7 +242,7 @@ class Homescreen { */ public function update_link_structure() { global $submenu; - // User does not have capabilites to see the submenu. + // User does not have capabilities to see the submenu. if ( ! current_user_can( 'manage_woocommerce' ) || empty( $submenu['woocommerce'] ) ) { return; } diff --git a/plugins/woocommerce/src/Internal/Admin/Loader.php b/plugins/woocommerce/src/Internal/Admin/Loader.php index 41e11ae613d..21caf32b9ba 100644 --- a/plugins/woocommerce/src/Internal/Admin/Loader.php +++ b/plugins/woocommerce/src/Internal/Admin/Loader.php @@ -111,7 +111,7 @@ class Loader { /** * Set up a div for the header embed to render into. - * The initial contents here are meant as a place loader for when the PHP page initialy loads. + * The initial contents here are meant as a place loader for when the PHP page initially loads. */ public static function embed_page_header() { if ( ! PageController::is_admin_page() && ! PageController::is_embed_page() ) { @@ -541,7 +541,7 @@ class Loader { } /** - * Return an object defining the currecy options for the site's current currency + * Return an object defining the currency options for the site's current currency * * @return array Settings for the current currency { * Array of settings. diff --git a/plugins/woocommerce/src/Internal/Admin/Marketing.php b/plugins/woocommerce/src/Internal/Admin/Marketing.php index b9efa6461f6..2c0d4348bd8 100644 --- a/plugins/woocommerce/src/Internal/Admin/Marketing.php +++ b/plugins/woocommerce/src/Internal/Admin/Marketing.php @@ -158,7 +158,7 @@ class Marketing { } /** - * Order marketing menu items alphabeticaly. + * Order marketing menu items alphabetically. * Overview should be first, and Coupons should be second, followed by other marketing menu items. * * @return void @@ -178,7 +178,7 @@ class Marketing { if ( false === $overview_key ) { /* - * If Overview is not found we may be on a site witha different language. + * If Overview is not found, we may be on a site with a different language. * We can use a fallback and try to find the overview page by its path. */ $overview_key = array_search( 'admin.php?page=wc-admin&path=/marketing', array_column( $marketing_submenu, self::SUBMENU_LOCATION_KEY ), true ); @@ -194,7 +194,7 @@ class Marketing { if ( false === $coupons_key ) { /* - * If Coupons is not found we may be on a site witha different language. + * If Coupons is not found, we may be on a site with a different language. * We can use a fallback and try to find the coupons page by its path. */ $coupons_key = array_search( 'edit.php?post_type=shop_coupon', array_column( $marketing_submenu, self::SUBMENU_LOCATION_KEY ), true ); diff --git a/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php b/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php index fdf819dda31..fad1838532a 100644 --- a/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php +++ b/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php @@ -170,7 +170,7 @@ class Edit { * * @since 7.4.0 * - * @oaram WC_Order $order The order being edited. + * @param WC_Order $order The order being edited. */ do_action( 'add_meta_boxes_' . $this->screen_id, $this->order ); diff --git a/plugins/woocommerce/src/Internal/Admin/ProductReviews/ReviewsListTable.php b/plugins/woocommerce/src/Internal/Admin/ProductReviews/ReviewsListTable.php index 131af7c87b4..fd6683672ec 100644 --- a/plugins/woocommerce/src/Internal/Admin/ProductReviews/ReviewsListTable.php +++ b/plugins/woocommerce/src/Internal/Admin/ProductReviews/ReviewsListTable.php @@ -632,7 +632,7 @@ class ReviewsListTable extends WP_List_Table { /** * Gets the name of the default primary column. * - * @return string Name of the primary colum. + * @return string Name of the primary column. */ protected function get_primary_column_name() : string { return 'comment'; diff --git a/plugins/woocommerce/src/Internal/Admin/Schedulers/ImportScheduler.php b/plugins/woocommerce/src/Internal/Admin/Schedulers/ImportScheduler.php index 2f90a8d5d80..9922d19b5ad 100644 --- a/plugins/woocommerce/src/Internal/Admin/Schedulers/ImportScheduler.php +++ b/plugins/woocommerce/src/Internal/Admin/Schedulers/ImportScheduler.php @@ -60,7 +60,7 @@ abstract class ImportScheduler implements ImportInterface { * Get batch sizes. * * @internal - * @retun array + * @return array */ public static function get_batch_sizes() { return array_merge( @@ -96,7 +96,7 @@ abstract class ImportScheduler implements ImportInterface { * * @internal * @param integer|boolean $days Number of days to import. - * @param boolean $skip_existing Skip exisiting records. + * @param boolean $skip_existing Skip existing records. */ public static function import_batch_init( $days, $skip_existing ) { $batch_size = static::get_batch_size( 'import' ); @@ -117,7 +117,7 @@ abstract class ImportScheduler implements ImportInterface { * @internal * @param int $batch_number Batch number to import (essentially a query page number). * @param int|bool $days Number of days to import. - * @param bool $skip_existing Skip exisiting records. + * @param bool $skip_existing Skip existing records. * @return void */ public static function import_batch( $batch_number, $days, $skip_existing ) { diff --git a/plugins/woocommerce/src/Internal/Admin/Settings.php b/plugins/woocommerce/src/Internal/Admin/Settings.php index 2d47e46e0c3..a72ae43f098 100644 --- a/plugins/woocommerce/src/Internal/Admin/Settings.php +++ b/plugins/woocommerce/src/Internal/Admin/Settings.php @@ -78,7 +78,7 @@ class Settings { } /** - * Return an object defining the currecy options for the site's current currency + * Return an object defining the currency options for the site's current currency * * @return array Settings for the current currency { * Array of settings. @@ -258,7 +258,7 @@ class Settings { } /** - * Removes non necesary feature properties for the client side. + * Removes non-necessary feature properties for the client side. * * @return array */ diff --git a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php index 8a4c185fb8e..0b65041e322 100644 --- a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php +++ b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php @@ -62,14 +62,26 @@ class ComingSoonAdminBarBadge { public function output_css() { if ( is_admin_bar_showing() ) { echo '