asset_api = $asset_api;
$this->asset_data_registry = $asset_data_registry;
$this->init();
}
/**
* Initialize class features.
*/
protected function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod.MissingPublic
add_action( 'init', array( $this, 'register_blocks' ) );
add_action( 'wp_loaded', array( $this, 'register_block_patterns' ) );
add_filter( 'block_categories_all', array( $this, 'register_block_categories' ), 10, 2 );
add_filter( 'render_block', array( $this, 'add_data_attributes' ), self::DATA_ATTRIBUTES_PRIORITY, 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( 'block_type_metadata_settings', array( $this, 'use_single_block_editor_style' ), 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 );
}
/**
* Get registered blocks that have WooCommerce blocks as their parents. Adds the value to the
* `registered_blocks_with_woocommerce_parents` cache if `init` has been fired.
*
* @return array Registered blocks with WooCommerce blocks as parents.
*/
public function get_registered_blocks_with_woocommerce_parent() {
// If init has run and the cache is already set, return it.
if ( did_action( 'init' ) && ! empty( $this->registered_blocks_with_woocommerce_parents ) ) {
return $this->registered_blocks_with_woocommerce_parents;
}
$registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered();
if ( ! is_array( $registered_blocks ) ) {
return array();
}
$this->registered_blocks_with_woocommerce_parents = array_filter(
$registered_blocks,
function ( $block ) {
if ( empty( $block->parent ) ) {
return false;
}
if ( ! is_array( $block->parent ) ) {
$block->parent = array( $block->parent );
}
$woocommerce_blocks = array_filter(
$block->parent,
function ( $parent_block_name ) {
return 'woocommerce' === strtok( $parent_block_name, '/' );
}
);
return ! empty( $woocommerce_blocks );
}
);
return $this->registered_blocks_with_woocommerce_parents;
}
/**
* Register blocks, hooking up assets and render functions as needed.
*/
public function register_blocks() {
// Set before registering rather than after: it guards against re-entry through the on-demand
// registration in Bootstrap, and a registration failure must not be retried on later filter fires.
self::$register_blocks_has_run = true;
$this->register_block_metadata();
$block_types = $this->get_block_types();
foreach ( $block_types as $block_type ) {
$block_type_class = __NAMESPACE__ . '\\BlockTypes\\' . $block_type;
new $block_type_class( $this->asset_api, $this->asset_data_registry, new IntegrationRegistry() );
}
}
/**
* Prepare block rendering for an email, and register the blocks if this request skipped that.
*
* Registration runs third-party code, and an error there must not stop the email from being sent.
*
* @internal
*/
public function register_blocks_for_email(): void {
$this->suspend_data_attributes_for_email_render();
if ( self::$register_blocks_has_run ) {
return;
}
try {
$this->register_blocks();
} catch ( \Throwable $e ) {
wc_caught_exception( $e, __METHOD__ );
}
}
/**
* Stop adding data- attributes to blocks until the email render ends.
*
* No block that can appear in an email reads the attributes back, so in email HTML they are only weight. A
* filter an extension removed is left alone.
*/
private function suspend_data_attributes_for_email_render(): void {
$data_attributes_callback = array( $this, 'add_data_attributes' );
if ( self::DATA_ATTRIBUTES_PRIORITY !== has_filter( 'render_block', $data_attributes_callback ) ) {
return;
}
remove_filter( 'render_block', $data_attributes_callback, self::DATA_ATTRIBUTES_PRIORITY );
add_action( 'woocommerce_email_editor_render_end', array( $this, 'restore_data_attributes_after_email_render' ) );
}
/**
* Add the data- attributes filter back once the email render has ended.
*
* Only a suspension hooks this, so a render whose end action never reached it is repaired by the next one.
*
* @internal
*/
public function restore_data_attributes_after_email_render(): void {
add_filter( 'render_block', array( $this, 'add_data_attributes' ), self::DATA_ATTRIBUTES_PRIORITY, 2 );
remove_action( 'woocommerce_email_editor_render_end', array( $this, 'restore_data_attributes_after_email_render' ) );
}
/**
* Whether register_blocks() has run in this request.
*
* Covers only the AbstractBlock-based block types that register_blocks() registers — blocks registered
* through other paths are not tracked. Used by the on-demand registration on the
* woocommerce_short_description filter (see Bootstrap::maybe_register_blocks_from_content) to avoid
* re-registering the block set when eager registration already ran on init.
*
* @since 11.1.0
*
* @return bool True if register_blocks() has already run.
*/
public function register_blocks_has_run() {
return self::$register_blocks_has_run;
}
/**
* Register block metadata collections for WooCommerce blocks.
*
* This method handles the registration of block metadata by using WordPress's block metadata
* collection registration system. It includes a temporary workaround for WordPress 6.7's
* strict path validation that might fail for sites using symlinked plugins.
*
* If the registration fails due to path validation, blocks will fall back to regular
* registration without affecting functionality.
*/
public function register_block_metadata() {
$meta_file_path = WC_ABSPATH . 'assets/client/blocks/blocks-json.php';
if ( function_exists( 'wp_register_block_metadata_collection' ) && file_exists( $meta_file_path ) ) {
add_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'bypass_block_metadata_doing_it_wrong' ), 10, 4 );
wp_register_block_metadata_collection(
WC_ABSPATH . 'assets/client/blocks/',
$meta_file_path
);
remove_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'bypass_block_metadata_doing_it_wrong' ), 10 );
}
}
/**
* Temporarily bypasses _doing_it_wrong() notices for block metadata collection registration.
*
* WordPress 6.7 introduced block metadata collections (with strict path validation).
* Any sites using symlinks for plugins will fail the validation which causes the metadata
* collection to not be registered. However, the blocks will still fall back to the regular
* registration and no functionality is affected.
* While this validation is being discussed in WordPress Core (#62140),
* this method allows registration to proceed by temporarily disabling
* the relevant notice.
*
* @param bool $trigger Whether to trigger the error.
* @param string $function The function that was called.
* @param string $message A message explaining what was done incorrectly.
* @param string $version The version of WordPress where the message was added.
* @return bool Whether to trigger the error.
*/
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
if ( 'WP_Block_Metadata_Registry::register_collection' === $function ) {
return false;
}
return $trigger;
}
/**
* Register block patterns
*/
public function register_block_patterns() {
register_block_pattern(
'woocommerce/order-confirmation-totals-heading',
array(
'title' => '',
'inserter' => false,
'content' => '
',
)
);
// Referenced from the default Cart page content created at install; registration must not depend on the Cart block type being enabled, or the page renders nothing for the reference.
$shop_permalink = WC()->call_function( 'wc_get_page_permalink', 'shop' );
register_block_pattern(
'woocommerce/cart-empty-message',
array(
'title' => '',
'inserter' => false,
'content' => '
' . esc_html__( 'Your cart is currently empty!', 'woocommerce' ) . '