Commit ef0506c0532 for woocommerce
commit ef0506c0532f0827ac9fa7cf53a0836fe0e4c474
Author: Luigi Teschio <gigitux@gmail.com>
Date: Tue Jul 14 17:28:04 2026 +0200
Keep WooCommerce blocks registered while filtering inserter availability (#66314)
* register blocks based on the editor context
* Fix PHPStan block type filtering errors
* add changelog
* simplify logic
* fix logic
diff --git a/plugins/woocommerce/changelog/66314-keep-woocommerce-blocks-registered b/plugins/woocommerce/changelog/66314-keep-woocommerce-blocks-registered
new file mode 100644
index 00000000000..34d5f926a11
--- /dev/null
+++ b/plugins/woocommerce/changelog/66314-keep-woocommerce-blocks-registered
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Keep WooCommerce blocks registered while filtering block inserter availability by editor context.
diff --git a/plugins/woocommerce/src/Blocks/BlockTypesController.php b/plugins/woocommerce/src/Blocks/BlockTypesController.php
index ba0c6f381de..b9db30d3e9d 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypesController.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypesController.php
@@ -62,6 +62,7 @@ final class BlockTypesController {
add_filter( 'render_block', array( $this, 'add_data_attributes' ), 10, 2 );
add_action( 'woocommerce_login_form_end', array( $this, 'redirect_to_field' ) );
add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_legacy_widgets_with_block_equivalent' ) );
+ add_filter( 'allowed_block_types_all', array( $this, 'filter_allowed_block_types' ), 10, 2 );
add_filter( 'register_block_type_args', array( $this, 'enqueue_block_style_for_classic_themes' ), 10, 2 );
add_filter( 'block_core_breadcrumbs_post_type_settings', array( $this, 'set_product_breadcrumbs_preferred_taxonomy' ), 10, 3 );
add_filter( 'block_core_breadcrumbs_items', array( $this, 'apply_woocommerce_breadcrumb_filters' ), 10, 1 );
@@ -358,6 +359,80 @@ final class BlockTypesController {
return $widget_types;
}
+ /**
+ * Filter WooCommerce block availability by editor context.
+ *
+ * Blocks remain registered on the server for compatibility, but some WooCommerce
+ * blocks are hidden from specific editor inserters.
+ *
+ * @param bool|string[] $allowed_block_types Array of block type slugs, or boolean to enable/disable all.
+ * @param \WP_Block_Editor_Context $block_editor_context The current block editor context.
+ * @return bool|string[] Filtered list of allowed block types, or boolean to enable/disable all.
+ */
+ public function filter_allowed_block_types( $allowed_block_types, $block_editor_context ) {
+ if ( false === $allowed_block_types ) {
+ return false;
+ }
+
+ $editor_name = $block_editor_context->name;
+
+ if ( 'core/edit-site' === $editor_name ) {
+ return $allowed_block_types;
+ }
+
+ if ( 'core/edit-post' === $editor_name ) {
+ return $this->remove_allowed_block_types(
+ $allowed_block_types,
+ $this->get_post_editor_hidden_block_types()
+ );
+ }
+
+ if ( in_array( $editor_name, array( 'core/edit-widgets', 'core/customize-widgets' ), true ) ) {
+ $registered_block_types = $this->get_registered_block_types();
+ $registered_woocommerce_block_types = array_filter(
+ $registered_block_types,
+ static function ( $block_type ) {
+ return 0 === strpos( $block_type, 'woocommerce/' );
+ }
+ );
+
+ return $this->remove_allowed_block_types(
+ $allowed_block_types,
+ array_diff( $registered_woocommerce_block_types, $this->get_widget_area_block_types() )
+ );
+ }
+
+ return $allowed_block_types;
+ }
+
+ /**
+ * Remove block types from the current allowed block list.
+ *
+ * @param bool|string[] $allowed_block_types Array of block type slugs, or true to allow all registered block types.
+ * @param string[] $block_types_to_remove Block type slugs to remove.
+ * @return string[] Filtered block type slugs.
+ */
+ private function remove_allowed_block_types( $allowed_block_types, array $block_types_to_remove ) {
+ $allowed_block_types = true === $allowed_block_types ? $this->get_registered_block_types() : (array) $allowed_block_types;
+ $allowed_block_types = array_filter( $allowed_block_types, 'is_string' );
+
+ return array_values(
+ array_diff(
+ $allowed_block_types,
+ $block_types_to_remove
+ )
+ );
+ }
+
+ /**
+ * Get all registered block type slugs.
+ *
+ * @return string[] Registered block type slugs.
+ */
+ private function get_registered_block_types() {
+ return array_keys( \WP_Block_Type_Registry::get_instance()->get_all_registered() );
+ }
+
/**
* Delete product transients when a product is deleted.
*
@@ -369,72 +444,98 @@ final class BlockTypesController {
}
/**
- * Get list of block types allowed in Widget Areas. New blocks won't be
+ * Get list of WooCommerce block types allowed in Widget Areas. New blocks won't be
* exposed in the Widget Area unless specifically added here.
*
- * @return array Array of block types.
+ * @return string[] Array of block type slugs.
*/
protected function get_widget_area_block_types() {
return array(
- 'AllReviews',
- 'Breadcrumbs',
- 'CartLink',
- 'CatalogSorting',
- 'ClassicShortcode',
- 'CustomerAccount',
- 'Dropdown',
- 'FeaturedCategory',
- 'FeaturedProduct',
- 'MiniCart',
- 'ProductCategories',
- 'ProductResultsCount',
- 'ProductSearch',
- 'ReviewsByCategory',
- 'ReviewsByProduct',
- 'ProductFilters',
- 'ProductFilterStatus',
- 'ProductFilterPrice',
- 'ProductFilterPriceSlider',
- 'ProductFilterAttribute',
- 'ProductFilterRating',
- 'ProductFilterActive',
- 'ProductFilterRemovableChips',
- 'ProductFilterClearButton',
- 'ProductFilterCheckboxList',
- 'ProductFilterChips',
- 'ProductFilterTaxonomy',
+ 'woocommerce/all-reviews',
+ 'woocommerce/breadcrumbs',
+ 'woocommerce/cart-link',
+ 'woocommerce/catalog-sorting',
+ 'woocommerce/classic-shortcode',
+ 'woocommerce/customer-account',
+ 'woocommerce/dropdown',
+ 'woocommerce/featured-category',
+ 'woocommerce/featured-product',
+ 'woocommerce/mini-cart',
+ 'woocommerce/product-categories',
+ 'woocommerce/product-results-count',
+ 'woocommerce/product-search',
+ 'woocommerce/reviews-by-category',
+ 'woocommerce/reviews-by-product',
+ 'woocommerce/product-filters',
+ 'woocommerce/product-filter-status',
+ 'woocommerce/product-filter-price',
+ 'woocommerce/product-filter-price-slider',
+ 'woocommerce/product-filter-attribute',
+ 'woocommerce/product-filter-rating',
+ 'woocommerce/product-filter-active',
+ 'woocommerce/product-filter-removable-chips',
+ 'woocommerce/product-filter-clear-button',
+ 'woocommerce/product-filter-checkbox-list',
+ 'woocommerce/product-filter-chips',
+ 'woocommerce/product-filter-taxonomy',
// Keep hidden legacy filter blocks for backward compatibility.
- 'ActiveFilters',
- 'AttributeFilter',
- 'FilterWrapper',
- 'PriceFilter',
- 'RatingFilter',
- 'StockFilter',
+ 'woocommerce/active-filters',
+ 'woocommerce/attribute-filter',
+ 'woocommerce/filter-wrapper',
+ 'woocommerce/price-filter',
+ 'woocommerce/rating-filter',
+ 'woocommerce/stock-filter',
// End: legacy filter blocks.
// Below product grids are hidden from inserter however they could have been used in widgets.
// Keep them for backward compatibility.
- 'HandpickedProducts',
- 'ProductBestSellers',
- 'ProductNew',
- 'ProductOnSale',
- 'ProductTopRated',
- 'ProductsByAttribute',
- 'ProductCategory',
- 'ProductTag',
+ 'woocommerce/handpicked-products',
+ 'woocommerce/product-best-sellers',
+ 'woocommerce/product-new',
+ 'woocommerce/product-on-sale',
+ 'woocommerce/product-top-rated',
+ 'woocommerce/products-by-attribute',
+ 'woocommerce/product-category',
+ 'woocommerce/product-tag',
// End: legacy product grids blocks.
);
}
+ /**
+ * Get list of WooCommerce block types hidden from post editors.
+ *
+ * @return string[] Array of block type slugs.
+ */
+ protected function get_post_editor_hidden_block_types() {
+ return array(
+ 'woocommerce/breadcrumbs',
+ 'woocommerce/catalog-sorting',
+ 'woocommerce/legacy-template',
+ 'woocommerce/product-results-count',
+ 'woocommerce/product-reviews',
+ 'woocommerce/order-confirmation-status',
+ 'woocommerce/order-confirmation-summary',
+ 'woocommerce/order-confirmation-totals',
+ 'woocommerce/order-confirmation-totals-wrapper',
+ 'woocommerce/order-confirmation-downloads',
+ 'woocommerce/order-confirmation-downloads-wrapper',
+ 'woocommerce/order-confirmation-billing-address',
+ 'woocommerce/order-confirmation-shipping-address',
+ 'woocommerce/order-confirmation-billing-wrapper',
+ 'woocommerce/order-confirmation-shipping-wrapper',
+ 'woocommerce/order-confirmation-additional-information',
+ 'woocommerce/order-confirmation-additional-fields-wrapper',
+ 'woocommerce/order-confirmation-additional-fields',
+ );
+ }
+
/**
* Get list of block types.
*
* @return array
*/
protected function get_block_types() {
- global $pagenow;
-
$block_types = array(
'ActiveFilters',
'AddToCartForm',
@@ -578,45 +679,6 @@ final class BlockTypesController {
$block_types[] = 'AddToCartWithOptions\GroupedProductItemLabel';
}
- /**
- * This enables specific blocks in Widget Areas using an opt-in approach.
- */
- if ( in_array( $pagenow, array( 'widgets.php', 'themes.php', 'customize.php' ), true ) && ( empty( $_GET['page'] ) || 'gutenberg-edit-site' !== $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
- $block_types = array_intersect(
- $block_types,
- $this->get_widget_area_block_types()
- );
- }
-
- /**
- * This disables specific blocks in Post and Page editor by not registering them.
- */
- if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) ) {
- $block_types = array_diff(
- $block_types,
- array(
- 'Breadcrumbs',
- 'CatalogSorting',
- 'ClassicTemplate',
- 'ProductResultsCount',
- 'ProductReviews',
- 'OrderConfirmation\Status',
- 'OrderConfirmation\Summary',
- 'OrderConfirmation\Totals',
- 'OrderConfirmation\TotalsWrapper',
- 'OrderConfirmation\Downloads',
- 'OrderConfirmation\DownloadsWrapper',
- 'OrderConfirmation\BillingAddress',
- 'OrderConfirmation\ShippingAddress',
- 'OrderConfirmation\BillingWrapper',
- 'OrderConfirmation\ShippingWrapper',
- 'OrderConfirmation\AdditionalInformation',
- 'OrderConfirmation\AdditionalFieldsWrapper',
- 'OrderConfirmation\AdditionalFields',
- )
- );
- }
-
/**
* Filters the list of allowed block types.
*
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypesController.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypesController.php
index b75082a26fd..515fb607586 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypesController.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypesController.php
@@ -20,6 +20,13 @@ class BlockTypesController extends \WP_UnitTestCase {
*/
private $block_types_controller;
+ /**
+ * Block types registered during a test.
+ *
+ * @var string[]
+ */
+ private $registered_test_block_types = array();
+
/**
* Sets up a new TestedBlockTypesController so it can be tested.
*
@@ -34,6 +41,23 @@ class BlockTypesController extends \WP_UnitTestCase {
);
}
+ /**
+ * Tear down test fixtures.
+ */
+ public function tearDown(): void {
+ foreach ( $this->registered_test_block_types as $block_type ) {
+ if ( \WP_Block_Type_Registry::get_instance()->is_registered( $block_type ) ) {
+ unregister_block_type( $block_type );
+ }
+ }
+
+ $this->registered_test_block_types = array();
+
+ remove_filter( 'allowed_block_types_all', array( $this->block_types_controller, 'filter_allowed_block_types' ), 10 );
+
+ parent::tearDown();
+ }
+
/**
* Register 3 blocks, one will be allowed by full name, one by namespace,and one because it has a parent with a
* woocommerce namespace.
@@ -94,4 +118,210 @@ class BlockTypesController extends \WP_UnitTestCase {
$answer = $this->block_types_controller->block_should_have_data_attributes( 'child-of-woo/block-name' );
$this->assertTrue( $answer );
}
+
+ /**
+ * @testdox Should hide post-editor WooCommerce blocks from unrestricted post editors.
+ */
+ public function test_edit_post_context_hides_post_editor_block_types_from_unrestricted_list(): void {
+ foreach (
+ array(
+ 'core/test-paragraph',
+ 'third-party/test-block',
+ 'woocommerce/product-search',
+ 'woocommerce/breadcrumbs',
+ 'woocommerce/product-reviews',
+ 'woocommerce/order-confirmation-status',
+ ) as $block_type
+ ) {
+ $this->register_test_block_type( $block_type );
+ }
+
+ $result = $this->block_types_controller->filter_allowed_block_types(
+ true,
+ $this->get_block_editor_context( 'core/edit-post' )
+ );
+
+ $this->assertContains( 'core/test-paragraph', $result, 'Non-WooCommerce blocks should remain available.' );
+ $this->assertContains( 'third-party/test-block', $result, 'Third-party blocks should remain available.' );
+ $this->assertContains( 'woocommerce/product-search', $result, 'WooCommerce blocks outside the denylist should remain available.' );
+ $this->assertNotContains( 'woocommerce/breadcrumbs', $result, 'Store Breadcrumbs should be hidden in post editors.' );
+ $this->assertNotContains( 'woocommerce/product-reviews', $result, 'Product Reviews should be hidden in post editors.' );
+ $this->assertNotContains( 'woocommerce/order-confirmation-status', $result, 'Order Confirmation blocks should be hidden in post editors.' );
+ }
+
+ /**
+ * @testdox Should keep only widget-area WooCommerce blocks in widget editors.
+ *
+ * @dataProvider widget_editor_context_provider
+ *
+ * @param string $editor_context_name Editor context name.
+ */
+ public function test_widget_context_hides_woocommerce_blocks_not_allowed_in_widget_areas( string $editor_context_name ): void {
+ foreach (
+ array(
+ 'core/test-paragraph',
+ 'third-party/test-block',
+ 'woocommerce/product-search',
+ 'woocommerce/product-filters',
+ 'woocommerce/checkout',
+ 'woocommerce/order-confirmation-status',
+ ) as $block_type
+ ) {
+ $this->register_test_block_type( $block_type );
+ }
+
+ $result = $this->block_types_controller->filter_allowed_block_types(
+ true,
+ $this->get_block_editor_context( $editor_context_name )
+ );
+
+ $this->assertContains( 'core/test-paragraph', $result, 'Non-WooCommerce blocks should remain available.' );
+ $this->assertContains( 'third-party/test-block', $result, 'Third-party blocks should remain available.' );
+ $this->assertContains( 'woocommerce/product-search', $result, 'Widget-area WooCommerce blocks should remain available.' );
+ $this->assertContains( 'woocommerce/product-filters', $result, 'Widget-area WooCommerce blocks should remain available.' );
+ $this->assertNotContains( 'woocommerce/checkout', $result, 'WooCommerce blocks outside the widget allowlist should be hidden.' );
+ $this->assertNotContains( 'woocommerce/order-confirmation-status', $result, 'WooCommerce blocks outside the widget allowlist should be hidden.' );
+ }
+
+ /**
+ * @testdox Should leave Site Editor block availability unchanged.
+ */
+ public function test_site_editor_context_leaves_allowed_block_types_unchanged(): void {
+ $allowed_block_types = array(
+ 'core/test-paragraph',
+ 'woocommerce/breadcrumbs',
+ 'woocommerce/checkout',
+ );
+
+ $result = $this->block_types_controller->filter_allowed_block_types(
+ $allowed_block_types,
+ $this->get_block_editor_context( 'core/edit-site' )
+ );
+
+ $this->assertSame( $allowed_block_types, $result, 'Site Editor block availability should not be changed.' );
+ }
+
+ /**
+ * @testdox Should preserve an existing post editor allowlist while removing denied WooCommerce blocks.
+ */
+ public function test_edit_post_context_preserves_existing_allowlist(): void {
+ $allowed_block_types = array(
+ 'core/test-paragraph',
+ 'woocommerce/product-search',
+ 'woocommerce/catalog-sorting',
+ 'third-party/test-block',
+ );
+
+ $result = $this->block_types_controller->filter_allowed_block_types(
+ $allowed_block_types,
+ $this->get_block_editor_context( 'core/edit-post' )
+ );
+
+ $this->assertSame(
+ array(
+ 'core/test-paragraph',
+ 'woocommerce/product-search',
+ 'third-party/test-block',
+ ),
+ $result,
+ 'Existing allowlists should keep their original restrictions.'
+ );
+ }
+
+ /**
+ * @testdox Should preserve an existing widget editor allowlist while removing disallowed WooCommerce blocks.
+ */
+ public function test_widget_context_preserves_existing_allowlist(): void {
+ $allowed_block_types = array(
+ 'core/test-paragraph',
+ 'woocommerce/product-search',
+ 'woocommerce/checkout',
+ 'third-party/test-block',
+ );
+
+ $result = $this->block_types_controller->filter_allowed_block_types(
+ $allowed_block_types,
+ $this->get_block_editor_context( 'core/edit-widgets' )
+ );
+
+ $this->assertSame(
+ array(
+ 'core/test-paragraph',
+ 'woocommerce/product-search',
+ 'third-party/test-block',
+ ),
+ $result,
+ 'Existing allowlists should keep their original restrictions.'
+ );
+ }
+
+ /**
+ * @testdox Should preserve false block availability for restricted editors.
+ *
+ * @dataProvider restricted_editor_context_provider
+ *
+ * @param string $editor_context_name Editor context name.
+ */
+ public function test_restricted_context_preserves_false_allowed_block_types( string $editor_context_name ): void {
+ $result = $this->block_types_controller->filter_allowed_block_types(
+ false,
+ $this->get_block_editor_context( $editor_context_name )
+ );
+
+ $this->assertFalse( $result, 'Existing disabled block availability should be preserved.' );
+ }
+
+ /**
+ * Data provider for widget editor contexts.
+ *
+ * @return array<string, array<string>>
+ */
+ public function widget_editor_context_provider(): array {
+ return array(
+ 'widgets editor' => array( 'core/edit-widgets' ),
+ 'customizer editor' => array( 'core/customize-widgets' ),
+ );
+ }
+
+ /**
+ * Data provider for restricted editor contexts.
+ *
+ * @return array<string, array<string>>
+ */
+ public function restricted_editor_context_provider(): array {
+ return array(
+ 'post editor' => array( 'core/edit-post' ),
+ 'widgets editor' => array( 'core/edit-widgets' ),
+ 'customizer editor' => array( 'core/customize-widgets' ),
+ );
+ }
+
+ /**
+ * Register a block type and track it for cleanup.
+ *
+ * @param string $block_type Block type slug.
+ */
+ private function register_test_block_type( string $block_type ): void {
+ if ( \WP_Block_Type_Registry::get_instance()->is_registered( $block_type ) ) {
+ return;
+ }
+
+ register_block_type( $block_type );
+
+ $this->registered_test_block_types[] = $block_type;
+ }
+
+ /**
+ * Get a block editor context for tests.
+ *
+ * @param string $editor_context_name Editor context name.
+ * @return \WP_Block_Editor_Context Block editor context.
+ */
+ private function get_block_editor_context( string $editor_context_name ): \WP_Block_Editor_Context {
+ return new \WP_Block_Editor_Context(
+ array(
+ 'name' => $editor_context_name,
+ )
+ );
+ }
}