AI
1 year ago
AIContent
2 weeks ago
Assets
1 month ago
BlockTypes
2 weeks ago
Domain
2 weeks ago
Images
1 year ago
Integrations
2 years ago
Patterns
2 weeks ago
Payments
2 weeks ago
Registry
2 years ago
SharedStores
3 months ago
Shipping
2 weeks ago
Templates
3 weeks ago
Utils
1 week ago
Assets.php
2 years ago
AssetsController.php
2 weeks ago
BlockPatterns.php
2 weeks ago
BlockTemplatesController.php
9 months ago
BlockTemplatesRegistry.php
2 weeks ago
BlockTypesController.php
2 weeks ago
CoreBreadcrumbsCompatibility.php
2 weeks ago
DependencyDetection.php
2 weeks ago
InboxNotifications.php
2 years ago
Installer.php
3 months ago
Library.php
2 years ago
Options.php
2 years ago
Package.php
1 year ago
QueryFilters.php
1 month ago
TemplateOptions.php
1 year ago
BlockTypesController.php
703 lines
| 1 | <?php |
| 2 | declare(strict_types=1); |
| 3 | |
| 4 | namespace Automattic\WooCommerce\Blocks; |
| 5 | |
| 6 | use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; |
| 7 | use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; |
| 8 | use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry; |
| 9 | use Automattic\WooCommerce\Blocks\BlockTypes\Cart; |
| 10 | use Automattic\WooCommerce\Blocks\BlockTypes\Checkout; |
| 11 | use Automattic\WooCommerce\Blocks\BlockTypes\MiniCartContents; |
| 12 | use Automattic\WooCommerce\Internal\Features\BlockEditorUnifiedAssets; |
| 13 | use Automattic\WooCommerce\Internal\ShopperLists\ShopperListsController; |
| 14 | |
| 15 | /** |
| 16 | * BlockTypesController class. |
| 17 | * |
| 18 | * @since 5.0.0 |
| 19 | * @internal |
| 20 | */ |
| 21 | final class BlockTypesController { |
| 22 | |
| 23 | /** |
| 24 | * Instance of the asset API. |
| 25 | * |
| 26 | * @var AssetApi |
| 27 | */ |
| 28 | protected $asset_api; |
| 29 | |
| 30 | /** |
| 31 | * Instance of the asset data registry. |
| 32 | * |
| 33 | * @var AssetDataRegistry |
| 34 | */ |
| 35 | protected $asset_data_registry; |
| 36 | |
| 37 | /** |
| 38 | * Holds the registered blocks that have WooCommerce blocks as their parents. |
| 39 | * |
| 40 | * @var array List of registered blocks. |
| 41 | */ |
| 42 | private $registered_blocks_with_woocommerce_parents; |
| 43 | |
| 44 | /** |
| 45 | * Whether register_blocks() has run in this request. |
| 46 | * |
| 47 | * Static because it mirrors the WordPress block-type registry, which is a process-global singleton: once |
| 48 | * any controller has registered the blocks they are registered for the whole request, regardless of which |
| 49 | * container instance owns the controller. Only tracks the AbstractBlock-based block types registered by |
| 50 | * register_blocks(); blocks registered through other paths are not reflected here. |
| 51 | * |
| 52 | * @var bool |
| 53 | */ |
| 54 | private static $register_blocks_has_run = false; |
| 55 | |
| 56 | /** |
| 57 | * Constructor. |
| 58 | * |
| 59 | * @param AssetApi $asset_api Instance of the asset API. |
| 60 | * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry. |
| 61 | */ |
| 62 | public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry ) { |
| 63 | $this->asset_api = $asset_api; |
| 64 | $this->asset_data_registry = $asset_data_registry; |
| 65 | $this->init(); |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Initialize class features. |
| 70 | */ |
| 71 | protected function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingPublic |
| 72 | add_action( 'init', array( $this, 'register_blocks' ) ); |
| 73 | add_action( 'wp_loaded', array( $this, 'register_block_patterns' ) ); |
| 74 | add_filter( 'block_categories_all', array( $this, 'register_block_categories' ), 10, 2 ); |
| 75 | add_filter( 'render_block', array( $this, 'add_data_attributes' ), 10, 2 ); |
| 76 | add_action( 'woocommerce_login_form_end', array( $this, 'redirect_to_field' ) ); |
| 77 | add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_legacy_widgets_with_block_equivalent' ) ); |
| 78 | add_filter( 'block_type_metadata_settings', array( $this, 'use_single_block_editor_style' ), 10, 2 ); |
| 79 | add_filter( 'register_block_type_args', array( $this, 'enqueue_block_style_for_classic_themes' ), 10, 2 ); |
| 80 | add_filter( 'block_core_breadcrumbs_post_type_settings', array( $this, 'set_product_breadcrumbs_preferred_taxonomy' ), 10, 3 ); |
| 81 | add_filter( 'block_core_breadcrumbs_items', array( $this, 'apply_woocommerce_breadcrumb_filters' ), 10, 1 ); |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Get registered blocks that have WooCommerce blocks as their parents. Adds the value to the |
| 86 | * `registered_blocks_with_woocommerce_parents` cache if `init` has been fired. |
| 87 | * |
| 88 | * @return array Registered blocks with WooCommerce blocks as parents. |
| 89 | */ |
| 90 | public function get_registered_blocks_with_woocommerce_parent() { |
| 91 | // If init has run and the cache is already set, return it. |
| 92 | if ( did_action( 'init' ) && ! empty( $this->registered_blocks_with_woocommerce_parents ) ) { |
| 93 | return $this->registered_blocks_with_woocommerce_parents; |
| 94 | } |
| 95 | |
| 96 | $registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered(); |
| 97 | |
| 98 | if ( ! is_array( $registered_blocks ) ) { |
| 99 | return array(); |
| 100 | } |
| 101 | |
| 102 | $this->registered_blocks_with_woocommerce_parents = array_filter( |
| 103 | $registered_blocks, |
| 104 | function ( $block ) { |
| 105 | if ( empty( $block->parent ) ) { |
| 106 | return false; |
| 107 | } |
| 108 | if ( ! is_array( $block->parent ) ) { |
| 109 | $block->parent = array( $block->parent ); |
| 110 | } |
| 111 | $woocommerce_blocks = array_filter( |
| 112 | $block->parent, |
| 113 | function ( $parent_block_name ) { |
| 114 | return 'woocommerce' === strtok( $parent_block_name, '/' ); |
| 115 | } |
| 116 | ); |
| 117 | return ! empty( $woocommerce_blocks ); |
| 118 | } |
| 119 | ); |
| 120 | return $this->registered_blocks_with_woocommerce_parents; |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Register blocks, hooking up assets and render functions as needed. |
| 125 | */ |
| 126 | public function register_blocks() { |
| 127 | // Set before registering rather than after: it guards against re-entry through the on-demand |
| 128 | // registration in Bootstrap, and a registration failure must not be retried on later filter fires. |
| 129 | self::$register_blocks_has_run = true; |
| 130 | $this->register_block_metadata(); |
| 131 | $block_types = $this->get_block_types(); |
| 132 | |
| 133 | foreach ( $block_types as $block_type ) { |
| 134 | $block_type_class = __NAMESPACE__ . '\\BlockTypes\\' . $block_type; |
| 135 | |
| 136 | new $block_type_class( $this->asset_api, $this->asset_data_registry, new IntegrationRegistry() ); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * Whether register_blocks() has run in this request. |
| 142 | * |
| 143 | * Covers only the AbstractBlock-based block types that register_blocks() registers — blocks registered |
| 144 | * through other paths are not tracked. Used by the on-demand registration on the |
| 145 | * woocommerce_short_description filter (see Bootstrap::maybe_register_blocks_from_content) to avoid |
| 146 | * re-registering the block set when eager registration already ran on init. |
| 147 | * |
| 148 | * @since 11.1.0 |
| 149 | * |
| 150 | * @return bool True if register_blocks() has already run. |
| 151 | */ |
| 152 | public function register_blocks_has_run() { |
| 153 | return self::$register_blocks_has_run; |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Register block metadata collections for WooCommerce blocks. |
| 158 | * |
| 159 | * This method handles the registration of block metadata by using WordPress's block metadata |
| 160 | * collection registration system. It includes a temporary workaround for WordPress 6.7's |
| 161 | * strict path validation that might fail for sites using symlinked plugins. |
| 162 | * |
| 163 | * If the registration fails due to path validation, blocks will fall back to regular |
| 164 | * registration without affecting functionality. |
| 165 | */ |
| 166 | public function register_block_metadata() { |
| 167 | $meta_file_path = WC_ABSPATH . 'assets/client/blocks/blocks-json.php'; |
| 168 | if ( function_exists( 'wp_register_block_metadata_collection' ) && file_exists( $meta_file_path ) ) { |
| 169 | add_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'bypass_block_metadata_doing_it_wrong' ), 10, 4 ); |
| 170 | wp_register_block_metadata_collection( |
| 171 | WC_ABSPATH . 'assets/client/blocks/', |
| 172 | $meta_file_path |
| 173 | ); |
| 174 | remove_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'bypass_block_metadata_doing_it_wrong' ), 10 ); |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * Temporarily bypasses _doing_it_wrong() notices for block metadata collection registration. |
| 180 | * |
| 181 | * WordPress 6.7 introduced block metadata collections (with strict path validation). |
| 182 | * Any sites using symlinks for plugins will fail the validation which causes the metadata |
| 183 | * collection to not be registered. However, the blocks will still fall back to the regular |
| 184 | * registration and no functionality is affected. |
| 185 | * While this validation is being discussed in WordPress Core (#62140), |
| 186 | * this method allows registration to proceed by temporarily disabling |
| 187 | * the relevant notice. |
| 188 | * |
| 189 | * @param bool $trigger Whether to trigger the error. |
| 190 | * @param string $function The function that was called. |
| 191 | * @param string $message A message explaining what was done incorrectly. |
| 192 | * @param string $version The version of WordPress where the message was added. |
| 193 | * @return bool Whether to trigger the error. |
| 194 | */ |
| 195 | public static function bypass_block_metadata_doing_it_wrong( $trigger, $function, $message, $version ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable,Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed,Universal.NamingConventions.NoReservedKeywordParameterNames.functionFound |
| 196 | if ( 'WP_Block_Metadata_Registry::register_collection' === $function ) { |
| 197 | return false; |
| 198 | } |
| 199 | return $trigger; |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Register block patterns |
| 204 | */ |
| 205 | public function register_block_patterns() { |
| 206 | register_block_pattern( |
| 207 | 'woocommerce/order-confirmation-totals-heading', |
| 208 | array( |
| 209 | 'title' => '', |
| 210 | 'inserter' => false, |
| 211 | 'content' => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Order details', 'woocommerce' ) . '</h2><!-- /wp:heading -->', |
| 212 | ) |
| 213 | ); |
| 214 | register_block_pattern( |
| 215 | 'woocommerce/order-confirmation-downloads-heading', |
| 216 | array( |
| 217 | 'title' => '', |
| 218 | 'inserter' => false, |
| 219 | 'content' => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Downloads', 'woocommerce' ) . '</h2><!-- /wp:heading -->', |
| 220 | ) |
| 221 | ); |
| 222 | register_block_pattern( |
| 223 | 'woocommerce/order-confirmation-shipping-heading', |
| 224 | array( |
| 225 | 'title' => '', |
| 226 | 'inserter' => false, |
| 227 | 'content' => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Shipping address', 'woocommerce' ) . '</h2><!-- /wp:heading -->', |
| 228 | ) |
| 229 | ); |
| 230 | register_block_pattern( |
| 231 | 'woocommerce/order-confirmation-billing-heading', |
| 232 | array( |
| 233 | 'title' => '', |
| 234 | 'inserter' => false, |
| 235 | 'content' => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Billing address', 'woocommerce' ) . '</h2><!-- /wp:heading -->', |
| 236 | ) |
| 237 | ); |
| 238 | register_block_pattern( |
| 239 | 'woocommerce/order-confirmation-additional-fields-heading', |
| 240 | array( |
| 241 | 'title' => '', |
| 242 | 'inserter' => false, |
| 243 | 'content' => '<!-- wp:heading {"level":2,"style":{"typography":{"fontSize":"24px"}}} --><h2 class="wp-block-heading" style="font-size:24px">' . esc_html__( 'Additional information', 'woocommerce' ) . '</h2><!-- /wp:heading -->', |
| 244 | ) |
| 245 | ); |
| 246 | } |
| 247 | |
| 248 | /** |
| 249 | * Register block categories |
| 250 | * |
| 251 | * Used in combination with the `block_categories_all` filter, to append |
| 252 | * WooCommerce Blocks related categories to the Gutenberg editor. |
| 253 | * |
| 254 | * @param array $categories The array of already registered categories. |
| 255 | */ |
| 256 | public function register_block_categories( $categories ) { |
| 257 | $woocommerce_block_categories = array( |
| 258 | array( |
| 259 | 'slug' => 'woocommerce', |
| 260 | 'title' => __( 'WooCommerce', 'woocommerce' ), |
| 261 | ), |
| 262 | array( |
| 263 | 'slug' => 'woocommerce-product-elements', |
| 264 | 'title' => __( 'WooCommerce Product Elements', 'woocommerce' ), |
| 265 | ), |
| 266 | ); |
| 267 | |
| 268 | return array_merge( $categories, $woocommerce_block_categories ); |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | * Check if a block should have data attributes appended on render. If it's in an allowed namespace, or the block |
| 273 | * has explicitly been added to the allowed block list, or if one of the block's parents is in the WooCommerce |
| 274 | * namespace it can have data attributes. |
| 275 | * |
| 276 | * @param string $block_name Name of the block to check. |
| 277 | * |
| 278 | * @return boolean |
| 279 | */ |
| 280 | public function block_should_have_data_attributes( $block_name ) { |
| 281 | $block_namespace = strtok( $block_name ?? '', '/' ); |
| 282 | |
| 283 | /** |
| 284 | * Filters the list of allowed block namespaces. |
| 285 | * |
| 286 | * This hook defines which block namespaces should have block name and attribute `data-` attributes appended on render. |
| 287 | * |
| 288 | * @since 5.9.0 |
| 289 | * |
| 290 | * @param array $allowed_namespaces List of namespaces. |
| 291 | */ |
| 292 | $allowed_namespaces = array_merge( array( 'woocommerce', 'woocommerce-checkout' ), (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_namespace', array() ) ); |
| 293 | |
| 294 | /** |
| 295 | * Filters the list of allowed Block Names |
| 296 | * |
| 297 | * This hook defines which block names should have block name and attribute data- attributes appended on render. |
| 298 | * |
| 299 | * @since 5.9.0 |
| 300 | * |
| 301 | * @param array $allowed_namespaces List of namespaces. |
| 302 | */ |
| 303 | $allowed_blocks = (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_block', array() ); |
| 304 | |
| 305 | $blocks_with_woo_parents = $this->get_registered_blocks_with_woocommerce_parent(); |
| 306 | $block_has_woo_parent = in_array( $block_name, array_keys( $blocks_with_woo_parents ), true ); |
| 307 | $in_allowed_namespace_list = in_array( $block_namespace, $allowed_namespaces, true ); |
| 308 | $in_allowed_block_list = in_array( $block_name, $allowed_blocks, true ); |
| 309 | |
| 310 | return $block_has_woo_parent || $in_allowed_block_list || $in_allowed_namespace_list; |
| 311 | } |
| 312 | |
| 313 | /** |
| 314 | * Add data- attributes to blocks when rendered if the block is under the woocommerce/ namespace. |
| 315 | * |
| 316 | * @param string $content Block content. |
| 317 | * @param array $block Parsed block data. |
| 318 | * @return string |
| 319 | */ |
| 320 | public function add_data_attributes( $content, $block ) { |
| 321 | |
| 322 | if ( ! is_string( $content ) || ! $this->block_should_have_data_attributes( $block['blockName'] ) ) { |
| 323 | return $content; |
| 324 | } |
| 325 | |
| 326 | $attributes = (array) $block['attrs']; |
| 327 | $exclude_attributes = array( 'className', 'align' ); |
| 328 | |
| 329 | $processor = new \WP_HTML_Tag_Processor( $content ); |
| 330 | |
| 331 | if ( |
| 332 | false === $processor->next_tag() || $processor->is_tag_closer() |
| 333 | ) { |
| 334 | |
| 335 | return $content; |
| 336 | } |
| 337 | |
| 338 | foreach ( $attributes as $key => $value ) { |
| 339 | if ( ! is_string( $key ) || in_array( $key, $exclude_attributes, true ) ) { |
| 340 | continue; |
| 341 | } |
| 342 | if ( is_bool( $value ) ) { |
| 343 | $value = $value ? 'true' : 'false'; |
| 344 | } |
| 345 | if ( ! is_scalar( $value ) ) { |
| 346 | $value = wp_json_encode( $value ); |
| 347 | } |
| 348 | |
| 349 | // For output consistency, we convert camelCase to kebab-case and output in lowercase. |
| 350 | $key = strtolower( preg_replace( '/(?<!^|\ )[A-Z]/', '-$0', $key ) ); |
| 351 | |
| 352 | $processor->set_attribute( "data-{$key}", $value ); |
| 353 | } |
| 354 | |
| 355 | // Set this last to prevent user-input from overriding it. |
| 356 | $processor->set_attribute( 'data-block-name', $block['blockName'] ); |
| 357 | return $processor->get_updated_html(); |
| 358 | } |
| 359 | |
| 360 | /** |
| 361 | * Adds a redirect field to the login form so blocks can redirect users after login. |
| 362 | */ |
| 363 | public function redirect_to_field() { |
| 364 | // phpcs:ignore WordPress.Security.NonceVerification |
| 365 | if ( empty( $_GET['redirect_to'] ) ) { |
| 366 | return; |
| 367 | } |
| 368 | echo '<input type="hidden" name="redirect" value="' . esc_attr( esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) ) . '" />'; // phpcs:ignore WordPress.Security.NonceVerification |
| 369 | } |
| 370 | |
| 371 | /** |
| 372 | * Hide legacy widgets with a feature complete block equivalent in the inserter |
| 373 | * and prevent them from showing as an option in the Legacy Widget block. |
| 374 | * |
| 375 | * @param array $widget_types An array of widgets hidden in core. |
| 376 | * @return array $widget_types An array including the WooCommerce widgets to hide. |
| 377 | */ |
| 378 | public function hide_legacy_widgets_with_block_equivalent( $widget_types ) { |
| 379 | array_push( |
| 380 | $widget_types, |
| 381 | 'woocommerce_product_search', |
| 382 | 'woocommerce_product_categories', |
| 383 | 'woocommerce_recent_reviews', |
| 384 | 'woocommerce_product_tag_cloud', |
| 385 | 'woocommerce_price_filter', |
| 386 | 'woocommerce_layered_nav', |
| 387 | 'woocommerce_layered_nav_filters', |
| 388 | 'woocommerce_rating_filter' |
| 389 | ); |
| 390 | |
| 391 | return $widget_types; |
| 392 | } |
| 393 | |
| 394 | /** |
| 395 | * Delete product transients when a product is deleted. |
| 396 | * |
| 397 | * @deprecated since 10.6.0 |
| 398 | * @return void |
| 399 | */ |
| 400 | public function delete_product_transients() { |
| 401 | wc_deprecated_function( __METHOD__, '10.6.0' ); |
| 402 | } |
| 403 | |
| 404 | /** |
| 405 | * Get list of block types. |
| 406 | * |
| 407 | * @return array |
| 408 | */ |
| 409 | protected function get_block_types() { |
| 410 | $block_types = array( |
| 411 | 'ActiveFilters', |
| 412 | 'AddToCartForm', |
| 413 | 'AllProducts', |
| 414 | 'AllReviews', |
| 415 | 'AttributeFilter', |
| 416 | 'Breadcrumbs', |
| 417 | 'CartLink', |
| 418 | 'CatalogSorting', |
| 419 | 'CategoryTitle', |
| 420 | 'CategoryDescription', |
| 421 | 'ClassicTemplate', |
| 422 | 'ClassicShortcode', |
| 423 | 'ComingSoon', |
| 424 | 'CouponCode', |
| 425 | 'CustomerAccount', |
| 426 | 'Dropdown', |
| 427 | 'EmailContent', |
| 428 | 'FeaturedCategory', |
| 429 | 'FeaturedProduct', |
| 430 | 'FilterWrapper', |
| 431 | 'HandpickedProducts', |
| 432 | 'MiniCart', |
| 433 | 'NextPreviousButtons', |
| 434 | 'StoreNotices', |
| 435 | 'PaymentMethodIcons', |
| 436 | 'PriceFilter', |
| 437 | 'ProductBestSellers', |
| 438 | 'ProductButton', |
| 439 | 'ProductCategories', |
| 440 | 'ProductCategory', |
| 441 | 'ProductCollection\Controller', |
| 442 | 'ProductCollection\NoResults', |
| 443 | 'ProductFilters', |
| 444 | 'ProductFilterStatus', |
| 445 | 'ProductFilterPrice', |
| 446 | 'ProductFilterPriceSlider', |
| 447 | 'ProductFilterAttribute', |
| 448 | 'ProductFilterRating', |
| 449 | 'ProductFilterActive', |
| 450 | 'ProductFilterRemovableChips', |
| 451 | 'ProductFilterClearButton', |
| 452 | 'ProductFilterCheckboxList', |
| 453 | 'ProductFilterChips', |
| 454 | 'ProductFilterTaxonomy', |
| 455 | 'ProductGallery', |
| 456 | 'ProductGalleryLargeImage', |
| 457 | 'ProductGalleryThumbnails', |
| 458 | 'ProductImage', |
| 459 | 'ProductImageGallery', |
| 460 | 'ProductMeta', |
| 461 | 'ProductNew', |
| 462 | 'ProductOnSale', |
| 463 | 'ProductPrice', |
| 464 | 'ProductTemplate', |
| 465 | 'ProductQuery', |
| 466 | 'ProductAverageRating', |
| 467 | 'ProductRating', |
| 468 | 'ProductRatingCounter', |
| 469 | 'ProductRatingStars', |
| 470 | 'ProductResultsCount', |
| 471 | 'ProductSaleBadge', |
| 472 | 'ProductSearch', |
| 473 | 'ProductSKU', |
| 474 | 'ProductStockIndicator', |
| 475 | 'ProductSummary', |
| 476 | 'ProductTag', |
| 477 | 'ProductTitle', |
| 478 | 'ProductTopRated', |
| 479 | 'ProductsByAttribute', |
| 480 | 'RatingFilter', |
| 481 | 'ReviewsByCategory', |
| 482 | 'ReviewsByProduct', |
| 483 | 'RelatedProducts', |
| 484 | 'SingleProduct', |
| 485 | 'StockFilter', |
| 486 | 'PageContentWrapper', |
| 487 | 'OrderConfirmation\Status', |
| 488 | 'OrderConfirmation\Summary', |
| 489 | 'OrderConfirmation\Totals', |
| 490 | 'OrderConfirmation\TotalsWrapper', |
| 491 | 'OrderConfirmation\Downloads', |
| 492 | 'OrderConfirmation\DownloadsWrapper', |
| 493 | 'OrderConfirmation\BillingAddress', |
| 494 | 'OrderConfirmation\ShippingAddress', |
| 495 | 'OrderConfirmation\BillingWrapper', |
| 496 | 'OrderConfirmation\ShippingWrapper', |
| 497 | 'OrderConfirmation\AdditionalInformation', |
| 498 | 'OrderConfirmation\AdditionalFieldsWrapper', |
| 499 | 'OrderConfirmation\AdditionalFields', |
| 500 | 'OrderConfirmation\CreateAccount', |
| 501 | 'ProductDetails', |
| 502 | 'ProductDescription', |
| 503 | 'ProductSpecifications', |
| 504 | // Generic blocks that will be pushed upstream. |
| 505 | 'Accordion\AccordionGroup', |
| 506 | 'Accordion\AccordionItem', |
| 507 | 'Accordion\AccordionPanel', |
| 508 | 'Accordion\AccordionHeader', |
| 509 | // End: generic blocks that will be pushed upstream. |
| 510 | 'Reviews\ProductReviews', |
| 511 | 'Reviews\ProductReviewRating', |
| 512 | 'Reviews\ProductReviewsTitle', |
| 513 | 'Reviews\ProductReviewForm', |
| 514 | 'Reviews\ProductReviewDate', |
| 515 | 'Reviews\ProductReviewContent', |
| 516 | 'Reviews\ProductReviewAuthorName', |
| 517 | 'Reviews\ProductReviewsPagination', |
| 518 | 'Reviews\ProductReviewsPaginationNext', |
| 519 | 'Reviews\ProductReviewsPaginationPrevious', |
| 520 | 'Reviews\ProductReviewsPaginationNumbers', |
| 521 | 'Reviews\ProductReviewTemplate', |
| 522 | ); |
| 523 | |
| 524 | $block_types = array_merge( |
| 525 | $block_types, |
| 526 | Cart::get_cart_block_types(), |
| 527 | Checkout::get_checkout_block_types(), |
| 528 | MiniCartContents::get_mini_cart_block_types() |
| 529 | ); |
| 530 | |
| 531 | if ( wc_get_container()->get( ShopperListsController::class )->is_enabled( 'saved-for-later' ) ) { |
| 532 | $block_types[] = 'SavedForLater'; |
| 533 | } |
| 534 | |
| 535 | if ( wc_get_container()->get( ShopperListsController::class )->is_enabled( 'wishlist' ) ) { |
| 536 | $block_types[] = 'Wishlist'; |
| 537 | $block_types[] = 'AddToWishlistButton'; |
| 538 | } |
| 539 | |
| 540 | if ( wp_is_block_theme() ) { |
| 541 | $block_types[] = 'AddToCartWithOptions\AddToCartWithOptions'; |
| 542 | $block_types[] = 'AddToCartWithOptions\QuantitySelector'; |
| 543 | $block_types[] = 'AddToCartWithOptions\VariationDescription'; |
| 544 | $block_types[] = 'AddToCartWithOptions\VariationSelector'; |
| 545 | $block_types[] = 'AddToCartWithOptions\VariationSelectorAttribute'; |
| 546 | $block_types[] = 'AddToCartWithOptions\VariationSelectorAttributeName'; |
| 547 | $block_types[] = 'AddToCartWithOptions\GroupedProductSelector'; |
| 548 | $block_types[] = 'AddToCartWithOptions\GroupedProductItem'; |
| 549 | $block_types[] = 'AddToCartWithOptions\GroupedProductItemSelector'; |
| 550 | $block_types[] = 'AddToCartWithOptions\GroupedProductItemLabel'; |
| 551 | } |
| 552 | |
| 553 | /** |
| 554 | * Filters the list of allowed block types. |
| 555 | * |
| 556 | * @since 9.0.0 |
| 557 | * |
| 558 | * @param array $block_types List of block types. |
| 559 | */ |
| 560 | return apply_filters( 'woocommerce_get_block_types', $block_types ); |
| 561 | } |
| 562 | |
| 563 | /** |
| 564 | * By default, when the classic theme is used, block style is always |
| 565 | * enqueued even if the block is not used on the page. We want WooCommerce |
| 566 | * store to always performant so we have to manually enqueue the block style |
| 567 | * on-demand for classic themes. |
| 568 | * |
| 569 | * @internal |
| 570 | * |
| 571 | * @param array $args Block metadata. |
| 572 | * @param string $block_name Block name. |
| 573 | * |
| 574 | * @return array Block metadata. |
| 575 | */ |
| 576 | public function enqueue_block_style_for_classic_themes( $args, $block_name ) { |
| 577 | |
| 578 | // Repeatedly checking the theme is expensive. So statically cache this logic result and remove the filter if not needed. |
| 579 | static $should_enqueue_block_style_for_classic_themes = null; |
| 580 | if ( null === $should_enqueue_block_style_for_classic_themes ) { |
| 581 | $should_enqueue_block_style_for_classic_themes = ! ( |
| 582 | is_admin() || |
| 583 | wp_is_block_theme() || |
| 584 | ( function_exists( 'wp_should_load_block_assets_on_demand' ) && wp_should_load_block_assets_on_demand() ) || |
| 585 | wp_should_load_separate_core_block_assets() |
| 586 | ); |
| 587 | } |
| 588 | if ( ! $should_enqueue_block_style_for_classic_themes ) { |
| 589 | remove_filter( 'register_block_type_args', array( $this, 'enqueue_block_style_for_classic_themes' ), 10 ); |
| 590 | |
| 591 | return $args; |
| 592 | } |
| 593 | |
| 594 | if ( |
| 595 | false === strpos( $block_name, 'woocommerce/' ) || |
| 596 | ( empty( $args['style_handles'] ) && empty( $args['style'] ) |
| 597 | ) |
| 598 | ) { |
| 599 | return $args; |
| 600 | } |
| 601 | |
| 602 | $style_handlers = $args['style_handles'] ?? $args['style']; |
| 603 | |
| 604 | add_filter( |
| 605 | 'render_block_' . $block_name, |
| 606 | static function ( $html ) use ( $style_handlers ) { |
| 607 | array_map( 'wp_enqueue_style', $style_handlers ); |
| 608 | |
| 609 | return $html; |
| 610 | }, |
| 611 | 10 |
| 612 | ); |
| 613 | |
| 614 | $args['style_handles'] = array(); |
| 615 | $args['style'] = array(); |
| 616 | |
| 617 | return $args; |
| 618 | } |
| 619 | |
| 620 | /** |
| 621 | * Use one shared editor stylesheet for WooCommerce blocks. |
| 622 | * |
| 623 | * WordPress loads `style` handles in both the frontend and editor. WooCommerce |
| 624 | * keeps those per-block handles for frontend performance, but removes them in |
| 625 | * admin so the block editor loads the combined stylesheet only. |
| 626 | * |
| 627 | * @internal |
| 628 | * |
| 629 | * @param array $settings Block settings. |
| 630 | * @param array $metadata Block metadata. |
| 631 | * |
| 632 | * @return array Block settings. |
| 633 | */ |
| 634 | public function use_single_block_editor_style( $settings, $metadata ) { |
| 635 | if ( |
| 636 | ! BlockEditorUnifiedAssets::is_enabled() || |
| 637 | ! is_admin() || |
| 638 | ! $this->is_woocommerce_block_metadata( $metadata ) ) { |
| 639 | return $settings; |
| 640 | } |
| 641 | |
| 642 | $settings['style_handles'] = array(); |
| 643 | $settings['style'] = array(); |
| 644 | $settings['editor_style_handles'] = array( 'wc-block-library-style' ); |
| 645 | $settings['editor_style'] = array( 'wc-block-library-style' ); |
| 646 | |
| 647 | return $settings; |
| 648 | } |
| 649 | |
| 650 | /** |
| 651 | * Check whether block metadata belongs to a block bundled with WooCommerce. |
| 652 | * |
| 653 | * @param array $metadata Block metadata. |
| 654 | * |
| 655 | * @return bool Whether the metadata file is in the WooCommerce blocks directory. |
| 656 | */ |
| 657 | private function is_woocommerce_block_metadata( $metadata ) { |
| 658 | static $blocks_path = null; |
| 659 | |
| 660 | if ( null === $blocks_path ) { |
| 661 | $resolved_path = realpath( WC_ABSPATH . 'assets/client/blocks' ); |
| 662 | $blocks_path = false === $resolved_path |
| 663 | ? '' |
| 664 | : trailingslashit( wp_normalize_path( $resolved_path ) ); |
| 665 | } |
| 666 | |
| 667 | if ( '' === $blocks_path || empty( $metadata['file'] ) ) { |
| 668 | return false; |
| 669 | } |
| 670 | |
| 671 | return str_starts_with( |
| 672 | wp_normalize_path( $metadata['file'] ), |
| 673 | $blocks_path |
| 674 | ); |
| 675 | } |
| 676 | |
| 677 | /** |
| 678 | * Set the preferred taxonomy and term for the breadcrumbs block on the product post type. |
| 679 | * |
| 680 | * @internal |
| 681 | * |
| 682 | * @param array $settings The settings for the breadcrumbs block. |
| 683 | * @param string $post_type The post type. |
| 684 | * @param int $post_id The current post ID. |
| 685 | * @return array The settings for the breadcrumbs block. |
| 686 | */ |
| 687 | public function set_product_breadcrumbs_preferred_taxonomy( $settings, $post_type, $post_id = 0 ) { |
| 688 | return Package::container()->get( CoreBreadcrumbsCompatibility::class )->set_product_breadcrumbs_preferred_taxonomy( $settings, $post_type, $post_id ); |
| 689 | } |
| 690 | |
| 691 | /** |
| 692 | * Apply WooCommerce compatibility behavior to Core breadcrumb items. |
| 693 | * |
| 694 | * @internal |
| 695 | * |
| 696 | * @param array $items Array of breadcrumb items from Core. |
| 697 | * @return array Modified breadcrumb items. |
| 698 | */ |
| 699 | public function apply_woocommerce_breadcrumb_filters( $items ) { |
| 700 | return Package::container()->get( CoreBreadcrumbsCompatibility::class )->apply_woocommerce_breadcrumb_filters( $items ); |
| 701 | } |
| 702 | } |
| 703 |