: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$breadcrumb_separator = str_replace( '”', '', $breadcrumb_separator );
woocommerce_breadcrumb( array(
'delimiter' => ' ' . $breadcrumb_separator . ' ',
'home' => et_()->array_get( $args, 'breadcrumb_home_text', '' ),
case 'woocommerce_show_product_images':
// WC Images module needs to modify global variable's property. Thus it is performed
// here instead at module's class since the $product global might be modified
$gallery_ids = $product->get_gallery_image_ids();
$image_id = $product->get_image_id();
$show_image = 'on' === $args['show_product_image'];
$show_gallery = 'on' === $args['show_product_gallery'];
$show_sale_badge = 'on' === $args['show_sale_badge'];
// If featured image is disabled, replace it with first gallery image's id (if gallery
// is enabled) or replaced it with empty string (if gallery is disabled as well)
if ( $show_gallery && isset( $gallery_ids[0] ) ) {
$product->set_image_id( $gallery_ids[0] );
// Remove first image from the gallery because it'll be added as thumbnail and will be duplicated.
unset( $gallery_ids[0] );
$product->set_gallery_image_ids( $gallery_ids );
$product->set_image_id( '' );
// Replaced gallery image ids with empty array
$product->set_gallery_image_ids( array() );
if ( $show_sale_badge && function_exists( 'woocommerce_show_product_sale_flash' ) ) {
woocommerce_show_product_sale_flash();
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
call_user_func( $function_name );
// Reset product's actual featured image id
$product->set_image_id( $image_id );
// Reset product's actual gallery image id
$product->set_gallery_image_ids( $gallery_ids );
case 'wc_get_stock_html':
echo wc_get_stock_html( $product );
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
call_user_func( $function_name, wc_add_to_cart_message( $product->get_id(), false, true ) );
// Save existing notices to restore them as many times as we need.
$et_wc_cached_notices = WC()->session->get( 'wc_notices', array() );
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
call_user_func( $function_name );
// Restore notices which were removed after wc_print_notices() executed to render multiple modules on page.
if ( ! empty( $et_wc_cached_notices ) && empty( WC()->session->get( 'wc_notices', array() ) ) ) {
WC()->session->set( 'wc_notices', $et_wc_cached_notices );
case 'woocommerce_upsell_display':
$order = isset( $args['order'] ) ? $args['order'] : '';
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
call_user_func( $function_name, '', '', '', $order );
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
call_user_func( $function_name );
$output = ob_get_clean();
// Reset original product variable to global $product
et_theme_builder_wc_reset_global_objects();
} else if ( $overwrite_global ) {
if ( $overwrite_product ) {
$product = $original_product;
if ( $overwrite_wp_query ) {
$wp_query = $original_wp_query;
* Rendering the content will enable Divi Builder to take over the entire
function et_builder_wc_product_render_layout() {
do_action( 'et_builder_wc_product_before_render_layout' );
do_action( 'et_builder_wc_product_after_render_layout' );
* Force WooCommerce to load default template over theme's custom template when builder's
* et_builder_from_scratch is used to prevent unexpected custom layout which makes builder
* experience inconsistent
* @param string $template
function et_builder_wc_override_template_part( $template, $slug, $name ) {
// Only force load default `content-single-product.php` template
$is_content_single_product = 'content' === $slug && 'single-product' === $name;
return $is_content_single_product ? WC()->plugin_path() . "/templates/{$slug}-{$name}.php" : $template;
* Disable all default WooCommerce single layout hooks.
function et_builder_wc_disable_default_layout() {
// To remove a hook, the $function_to_remove and $priority arguments must match
// with which the hook was added.
'woocommerce_before_main_content',
'woocommerce_breadcrumb',
'woocommerce_before_single_product_summary',
'woocommerce_show_product_sale_flash',
'woocommerce_before_single_product_summary',
'woocommerce_show_product_images',
'woocommerce_single_product_summary',
'woocommerce_template_single_title',
'woocommerce_single_product_summary',
'woocommerce_template_single_rating',
'woocommerce_single_product_summary',
'woocommerce_template_single_price',
'woocommerce_single_product_summary',
'woocommerce_template_single_excerpt',
'woocommerce_single_product_summary',
'woocommerce_template_single_add_to_cart',
'woocommerce_single_product_summary',
'woocommerce_template_single_meta',
'woocommerce_single_product_summary',
'woocommerce_template_single_sharing',
'woocommerce_after_single_product_summary',
'woocommerce_output_product_data_tabs',
'woocommerce_after_single_product_summary',
'woocommerce_upsell_display',
'woocommerce_after_single_product_summary',
'woocommerce_output_related_products',
* Overrides the default WooCommerce layout.
* @see woocommerce/includes/wc-template-functions.php
function et_builder_wc_override_default_layout() {
if ( ! is_singular( 'product' ) ) {
// global $post won't be available with `after_setup_theme` hook and hence `wp` hook is used.
if ( ! et_pb_is_pagebuilder_used( $post->ID ) ) {
$product_page_layout = et_builder_wc_get_product_layout( $post->ID );
$is_product_content_modified = 'modified' === get_post_meta( $post->ID, ET_BUILDER_WC_PRODUCT_PAGE_CONTENT_STATUS_META_KEY, true );
$is_preview_loading = is_preview();
// BFB was enabled but page content wasn't saved yet. Load default layout on FE.
if ( 'et_build_from_scratch' === $product_page_layout && ! $is_product_content_modified && ! $is_preview_loading ) {
* The `has_shortcode()` check does not work here. Hence solving the need using `strpos()`.
* The WHY behind the check is explained in the following issue.
* @see https://github.com/elegantthemes/Divi/issues/16155
if ( ! $product_page_layout && ! et_core_is_fb_enabled()
|| ( $product_page_layout && 'et_build_from_scratch' !== $product_page_layout )
// Force use WooCommerce's default template if current theme is not Divi or Extra (handling
// possible custom template on DBP / Child Theme)
if ( ! in_array( wp_get_theme()->get( 'Name' ), array( 'Divi', 'Extra' ) ) ) {
add_filter( 'wc_get_template_part', 'et_builder_wc_override_template_part', 10, 3 );
et_builder_wc_disable_default_layout();
do_action( 'et_builder_wc_product_before_render_layout_registration' );
// Add render content on product page
add_action( 'woocommerce_after_single_product_summary', 'et_builder_wc_product_render_layout', 5 );
* Skips setting default content on Product post type during Builder activation.
* Otherwise, the description would be shown in both Product Tabs and at the end of the
* default WooCommerce layout set at
* @see et_builder_wc_get_initial_content()
function et_builder_wc_skip_initial_content( $flag, $post ) {
if ( ! ( $post instanceof WP_Post ) ) {
if ( 'product' !== $post->post_type ) {
* Determine whether given content has WooCommerce module inside it or not
* @since 4.0 Added ET_Builder_Element class exists check.
function et_builder_has_woocommerce_module( $content = '' ) {
if ( ! class_exists( 'ET_Builder_Element' ) ) {
$has_woocommerce_module = false;
$woocommerce_modules = ET_Builder_Element::get_woocommerce_modules();
foreach ( $woocommerce_modules as $module ) {
if ( has_shortcode( $content, $module ) ) {
$has_woocommerce_module = true;
// Stop the loop once any shortcode is found
return apply_filters( 'et_builder_has_woocommerce_module', $has_woocommerce_module );
* Check if current global $post uses builder / layout block, not `product` CPT, and contains
* WooCommerce module inside it. This check is needed because WooCommerce by default only adds
* scripts and style to `product` CPT while WooCommerce Modules can be used at any CPT
* @since 4.1.0 check if layout block is used instead of builder
function et_builder_wc_is_non_product_post_type() {
if ( $post && 'product' === $post->post_type ) {
$types = et_theme_builder_get_layout_post_types();
$layouts = et_theme_builder_get_template_layouts();
foreach ( $types as $type ) {
if ( ! isset( $layouts[ $type ] ) ) {
if ( $layouts[ $type ]['override'] && et_builder_has_woocommerce_module( get_post_field( 'post_content', $layouts[ $type ]['id'] ) ) ) {
// If no post found, bail early
$is_builder_used = et_pb_is_pagebuilder_used( $post->ID );
$is_layout_block_used = has_block( 'divi/layout', $post->post_content );
// If no builder or layout block used, bail early
if ( ! $is_builder_used && ! $is_layout_block_used ) {
$has_wc_module = et_builder_has_woocommerce_module( $post->post_content );
if ( ( $is_builder_used || $is_layout_block_used ) && $has_wc_module ) {
* Load WooCommerce related scripts. This function basically redo what `WC_Frontend_Scripts::load_scripts()`
* does without the `product` CPT limitation.
* @todo Once more WooCommerce Modules are added (checkout, account, etc), revisit this method and
* compare it against `WC_Frontend_Scripts::load_scripts()`. Some of the script queues are
* removed here because there is currently no WooCommerce module equivalent of them
* @since 4.3.3 Loads WC scripts on Shop, Product Category & Product Tags archives.
function et_builder_wc_load_scripts() {
$is_shop = function_exists( 'is_shop' ) && is_shop();
// is_product_taxonomy() is not returning TRUE for Category & Tags.
// Hence we check Category & Tag archives individually.
$is_product_category = function_exists( 'is_product_category' ) && is_product_category();
$is_product_tag = function_exists( 'is_product_tag' ) && is_product_tag();
// If current page is not non-`product` CPT which using builder, stop early
if ( ( ! et_builder_wc_is_non_product_post_type()
|| ! class_exists( 'WC_Frontend_Scripts' ) )
&& function_exists( 'et_fb_enabled' )
&& ! $is_product_category
// Simply enqueue the scripts; All of them have been registered
if ( 'yes' === get_option( 'woocommerce_enable_ajax_add_to_cart' ) ) {
wp_enqueue_script( 'wc-add-to-cart' );
if ( current_theme_supports( 'wc-product-gallery-zoom' ) ) {
wp_enqueue_script( 'zoom' );
if ( current_theme_supports( 'wc-product-gallery-slider' ) ) {
wp_enqueue_script( 'flexslider' );
if ( current_theme_supports( 'wc-product-gallery-lightbox' ) ) {
wp_enqueue_script( 'photoswipe-ui-default' );
wp_enqueue_style( 'photoswipe-default-skin' );
add_action( 'wp_footer', 'woocommerce_photoswipe' );
wp_enqueue_script( 'wc-single-product' );
if ( 'geolocation_ajax' === get_option( 'woocommerce_default_customer_address' ) ) {
$ua = strtolower( wc_get_user_agent() ); // Exclude common bots from geolocation by user agent.
if ( ! strstr( $ua, 'bot' ) && ! strstr( $ua, 'spider' ) && ! strstr( $ua, 'crawl' ) ) {
wp_enqueue_script( 'wc-geolocation' );
wp_enqueue_script( 'woocommerce' );
wp_enqueue_script( 'wc-cart-fragments' );
$wc_styles = WC_Frontend_Scripts::get_styles();
foreach ( $wc_styles as $style_handle => $wc_style ) {
if ( ! isset( $wc_style['has_rtl'] ) ) {
$wc_style['has_rtl'] = false;
wp_enqueue_style( $style_handle, $wc_style['src'], $wc_style['deps'], $wc_style['version'], $wc_style['media'], $wc_style['has_rtl'] );
* Add WooCommerce body class name on non `product` CPT builder page
function et_builder_wc_add_body_class( $classes ) {
if ( et_builder_wc_is_non_product_post_type() ) {
$classes[] = 'woocommerce';
$classes[] = 'woocommerce-page';
* Add product class name on inner content wrapper page on non `product` CPT builder page with woocommerce modules
function et_builder_wc_add_inner_content_class( $classes ) {
// The class is required on any post with woocommerce modules and on product pages.
if ( et_builder_wc_is_non_product_post_type() || is_product() ) {
* Add WooCommerce class names on Divi Shop Page (not WooCommerce Shop).
* @param array $classes Array of Classes.
function et_builder_wc_add_outer_content_class( $classes ) {