: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* All WooCommerce modules specific functions.php stuff goes here
* Define required constants.
if ( ! defined( 'ET_BUILDER_WC_PRODUCT_LONG_DESC_META_KEY' ) ) {
// Post meta key to retrieve/save Long description metabox content.
define( 'ET_BUILDER_WC_PRODUCT_LONG_DESC_META_KEY', '_et_pb_old_content' );
if ( ! defined( 'ET_BUILDER_WC_PRODUCT_PAGE_LAYOUT_META_KEY' ) ) {
// Post meta key to retrieve/save Long description metabox content.
define( 'ET_BUILDER_WC_PRODUCT_PAGE_LAYOUT_META_KEY', '_et_pb_product_page_layout' );
if ( ! defined( 'ET_BUILDER_WC_PRODUCT_PAGE_CONTENT_STATUS_META_KEY' ) ) {
// Post meta key to track Product page content status changes.
define( 'ET_BUILDER_WC_PRODUCT_PAGE_CONTENT_STATUS_META_KEY', '_et_pb_woo_page_content_status' );
* Returning <img> string for default image placeholder
function et_builder_wc_placeholder_img() {
'<img src="%1$s" alt="2$s" />',
et_core_esc_attr( 'placeholder', ET_BUILDER_PLACEHOLDER_LANDSCAPE_IMAGE_DATA ),
esc_attr__( 'Product image', 'et_builder' )
* Gets the Product Content options.
* This array is used in Divi Page Settings metabox and in Divi Theme Options ⟶ Builder ⟶ Post Type integration.
* @param string $translation_context Translation Context to indicate if translation origins from Divi Theme or
* from the Builder. Optional. Default 'et_builder'.
function et_builder_wc_get_page_layouts( $translation_context = 'et_builder' ) {
switch ( $translation_context ) {
$product_page_layouts = array(
'et_build_from_scratch' => esc_html__( 'Build From Scratch', 'Divi' ),
'et_default_layout' => esc_html__( 'Default', 'Divi' ),
$product_page_layouts = array(
'et_build_from_scratch' => esc_html__( 'Build From Scratch', 'et_builder' ),
'et_default_layout' => et_builder_i18n( 'Default' ),
return $product_page_layouts;
* Adds WooCommerce Module settings to the Builder settings.
* Adding in the Builder Settings tab will ensure that the field is available in Extra Theme and
* @since 4.0.3 Hide Product Content layout settings Divi Builder Plugin options.
* @param array $builder_settings_fields
function et_builder_wc_add_settings( $builder_settings_fields ) {
// Bail early to hide WooCommerce Settings tab under the Builder tab.
// If $fields['tab_slug'] is not equal to the tab slug (i.e. woocommerce_page_layout) then WooCommerce settings tab won't be displayed.
// {@see ET_Builder_Settings::_get_builder_settings_in_epanel_format}
if ( ! et_is_woocommerce_plugin_active() ) {
return $builder_settings_fields;
'et_pb_woocommerce_product_layout' => array(
'id' => 'et_pb_woocommerce_product_layout',
'label' => esc_html__( 'Product Layout', 'et_builder' ),
'description' => esc_html__( 'Here you can choose Product Page Layout for WooCommerce.', 'et_builder' ),
'et_right_sidebar' => esc_html__( 'Right Sidebar', 'et_builder' ),
'et_left_sidebar' => esc_html__( 'Left Sidebar', 'et_builder' ),
'et_no_sidebar' => esc_html__( 'No Sidebar', 'et_builder' ),
'et_full_width_page' => esc_html__( 'Fullwidth', 'et_builder' ),
'default' => 'et_right_sidebar',
'validation_type' => 'simple_text',
'et_save_values' => true,
'tab_slug' => 'post_type_integration',
'toggle_slug' => 'performance',
'et_pb_woocommerce_page_layout' => array(
'id' => 'et_pb_woocommerce_product_page_layout',
'label' => esc_html__( 'Product Content', 'et_builder' ),
'description' => esc_html__( '"Build From Scratch" loads a pre-built WooCommerce page layout, with which you build on when the Divi Builder is enabled. "Default" option lets you use default WooCommerce page layout.', 'et_builder' ),
'options' => et_builder_wc_get_page_layouts(),
'default' => 'et_build_from_scratch',
'validation_type' => 'simple_text',
'et_save_values' => true,
'tab_slug' => 'post_type_integration',
'toggle_slug' => 'performance',
// Hide setting in DBP : https://github.com/elegantthemes/Divi/issues/17378
if ( et_is_builder_plugin_active() ) {
unset( $fields['et_pb_woocommerce_product_layout'] );
return array_merge( $builder_settings_fields, $fields );
* Gets the pre-built layout for WooCommerce product pages.
* @type string $existing_shortcode Existing builder shortcode.
function et_builder_wc_get_initial_content( $args = array() ) {
* Filters the Top section Background in the default WooCommerce Modules layout.
* @param string $color Default empty.
$et_builder_wc_initial_top_section_bg = apply_filters( 'et_builder_wc_initial_top_section_bg', '' );
[et_pb_section custom_padding="0px||||false|false" background_color="' . esc_attr( $et_builder_wc_initial_top_section_bg ) . '"]
[et_pb_row width="100%" custom_padding="0px||0px||false|false"]
[et_pb_column type="4_4"]
[et_pb_wc_breadcrumb][/et_pb_wc_breadcrumb]
[et_pb_wc_cart_notice][/et_pb_wc_cart_notice]
[et_pb_row custom_padding="0px||||false|false" width="100%"]
[et_pb_column type="1_2"]
[et_pb_wc_images][/et_pb_wc_images]
[et_pb_column type="1_2"]
[et_pb_wc_title][/et_pb_wc_title]
[et_pb_wc_rating][/et_pb_wc_rating]
[et_pb_wc_price][/et_pb_wc_price]
[et_pb_wc_description][/et_pb_wc_description]
[et_pb_wc_add_to_cart form_field_text_align="center"][/et_pb_wc_add_to_cart]
[et_pb_wc_meta][/et_pb_wc_meta]
[et_pb_column type="4_4"]
[et_pb_wc_upsells columns_number="3"][/et_pb_wc_upsells]
[et_pb_wc_related_products columns_number="3"][/et_pb_wc_related_products]
if ( ! empty( $args['existing_shortcode'] ) ) {
return $content . $args['existing_shortcode'];
* Gets the Product layout for a given Post ID.
* @param int $post_id Post Id.
* @return string The return value will be one of the values from
* {@see et_builder_wc_get_page_layouts()} when the Post ID is valid.
* Empty string otherwise.
function et_builder_wc_get_product_layout( $post_id ) {
$post = get_post( $post_id );
return get_post_meta( $post_id, ET_BUILDER_WC_PRODUCT_PAGE_LAYOUT_META_KEY, true );
* Sets the pre-built layout for WooCommerce product pages.
* @param string $maybe_shortcode_content
* @param string $content Default null. Post content.
function et_builder_wc_set_initial_content( $maybe_shortcode_content, $post_id ) {
$post = get_post( absint( $post_id ) );
if ( ! ( $post instanceof WP_Post ) || 'product' !== $post->post_type ) {
return $maybe_shortcode_content;
// $post_id is a valid Product ID by now.
$product_page_layout = et_builder_wc_get_product_layout( $post_id );
* When FALSE, this means the Product doesn't use Builder at all;
* Or the Product has been using the Builder before WooCommerce Modules QF launched.
if ( ! $product_page_layout ) {
$product_page_layout = et_get_option(
'et_pb_woocommerce_page_layout',
$is_product_content_modified = 'modified' === get_post_meta( $post_id,
ET_BUILDER_WC_PRODUCT_PAGE_CONTENT_STATUS_META_KEY, true );
// Content was already saved or default content should be loaded.
if ( $is_product_content_modified || 'et_default_layout' === $product_page_layout ) {
return $maybe_shortcode_content;
if ( has_shortcode( $maybe_shortcode_content, 'et_pb_section' ) &&
'et_build_from_scratch' === $product_page_layout &&
! empty( $maybe_shortcode_content ) ) {
$args['existing_shortcode'] = $maybe_shortcode_content;
return et_builder_wc_get_initial_content( $args );
* Saves the WooCommerce long description metabox content.
* The content is stored as post meta w/ the key `_et_pb_old_content`.
function et_builder_wc_long_description_metabox_save( $post_id, $post, $request ) {
if ( ! isset( $request['et_bfb_long_description_nonce'] ) ) {
if ( current_user_can( 'edit_posts', $post_id ) && et_core_security_check( 'edit_posts', 'et_bfb_long_description_nonce', '_et_bfb_long_description_nonce', '_POST', false )
if ( 'product' !== $post->post_type ) {
if ( ! isset( $request['et_builder_wc_product_long_description'] ) ) {
$long_desc_content = $request['et_builder_wc_product_long_description'];
$is_updated = update_post_meta( $post_id, ET_BUILDER_WC_PRODUCT_LONG_DESC_META_KEY, wp_kses_post( $long_desc_content ) );
* Output Callback for Product long description metabox.
function et_builder_wc_long_description_metabox_render( $post ) {
'textarea_name' => 'et_builder_wc_product_long_description',
'quicktags' => array( 'buttons' => 'em,strong,link' ),
'theme_advanced_buttons1' => 'bold,italic,strikethrough,separator,bullist,numlist,separator,blockquote,separator,justifyleft,justifycenter,justifyright,separator,link,unlink,separator,undo,redo,separator',
'theme_advanced_buttons2' => '',
'editor_css' => '<style>#wp-et_builder_wc_product_long_description-editor-container .wp-editor-area{height:175px; width:100%;}</style>',
// Since we use $post_id in more than one place, use a variable.
// Long description metabox content. Default Empty.
$long_desc_content = get_post_meta( $post_id, ET_BUILDER_WC_PRODUCT_LONG_DESC_META_KEY, true );
$long_desc_content = ! empty( $long_desc_content ) ? $long_desc_content : '';
* Filters the wp_editor settings used in the Long description metabox.
* @param array $settings WP Editor settings.
$settings = apply_filters( 'et_builder_wc_product_long_description_editor_settings', $settings );
wp_nonce_field( '_et_bfb_long_description_nonce', 'et_bfb_long_description_nonce' );
'et_builder_wc_product_long_description',
* Adds the Long description metabox to Product post type.
* @param WP_Post $post WP Post.
function et_builder_wc_long_description_metabox_register( $post ) {
if ( 'on' !== get_post_meta( $post->ID, '_et_pb_use_builder', true ) ) {
add_meta_box( 'et_builder_wc_product_long_description_metabox',
__( 'Product long description', 'et_builder' ),
'et_builder_wc_long_description_metabox_render',
* Determine if WooCommerce's $product global need to be overwritten or not.
* IMPORTANT: make sure to reset it later
* @param string $product_id
function et_builder_wc_need_overwrite_global( $product_id = 'current' ) {
$is_current_product_page = 'current' === $product_id;
// There are three situation which requires global value overwrite: initial builder
// ajax request, computed callback jax request (all ajax request has faulty global variable),
// and if `product` attribute is not current page's product id (ie Woo Tabs being used
$need_overwrite_global = ! $is_current_product_page
|| et_fb_is_builder_ajax()
|| et_fb_is_computed_callback_ajax();
return $need_overwrite_global;
* Helper to render module template for module's front end and computed callback output
* @param string $function_name
* @param array $overwrite
function et_builder_wc_render_module_template( $function_name, $args = array(), $overwrite = array( 'product' ) ) {
// Shouldn't be fired in Backend to not break the BB loading.
if ( is_admin() && ! wp_doing_ajax() ) {
// Check if passed function name is allowlisted or not
$allowlisted_functions = array(
'woocommerce_breadcrumb',
'woocommerce_template_single_price',
'woocommerce_template_single_add_to_cart',
'woocommerce_product_additional_information_tab',
'woocommerce_template_single_meta',
'woocommerce_template_single_rating',
'woocommerce_show_product_images',
'woocommerce_output_related_products',
'woocommerce_upsell_display',
if ( ! in_array( $function_name, $allowlisted_functions ) ) {
global $product, $post, $wp_query;
$args = wp_parse_args( $args, $defaults );
$overwrite_global = et_builder_wc_need_overwrite_global( $args['product'] );
$overwrite_product = in_array( 'product', $overwrite );
$overwrite_post = in_array( 'post', $overwrite );
$overwrite_wp_query = in_array( 'wp_query', $overwrite );
$is_tb = et_builder_tb_enabled();
// global object needs to be set before output rendering. This needs to be performed on each
// module template rendering instead of once for all module template rendering because some
// module's template rendering uses `wp_reset_postdata()` which resets global query
et_theme_builder_wc_set_global_objects();
} else if ( $overwrite_global ) {
$is_latest_product = 'latest' === $args['product'];
$is_current_product_page = 'current' === $args['product'];
if ( $is_latest_product ) {
// Dynamic filter's product_id need to be translated into correct id
// @todo once `product_filter` has more options, this might change
$product_id = ET_Builder_Module_Helper_Woocommerce_Modules::get_product_id( $args['product'] );
} elseif ( $is_current_product_page && wp_doing_ajax() && class_exists( 'ET_Builder_Element' ) ) {
// $product global doesn't exist in ajax request; thus get the fallback post id
// this is likely happen in computed callback ajax request
$product_id = ET_Builder_Element::get_current_post_id();
// Besides two situation above, $product_id is current $args['product']
if ( false !== get_post_status( $args['product'] ) ) {
$product_id = $args['product'];
// Fallback to Latest product if saved product ID doesn't exist.
$product_id = ET_Builder_Module_Helper_Woocommerce_Modules::get_product_id( 'latest' );
if ( 'product' !== get_post_type( $product_id ) ) {
// We are in a Theme Builder layout and the current post is not a product - use the latest one instead.
$products = new WP_Query( array(
'post_type' => 'product',
'post_status' => 'publish',
if ( ! $products->have_posts() ) {
$product_id = $products->posts[0]->ID;
if ( $overwrite_product ) {
$original_product = $product;
$product = wc_get_product( $product_id );
$post = get_post( $product_id );
if ( $overwrite_wp_query ) {
$original_wp_query = $wp_query;
$wp_query = new WP_Query( array( 'p' => $product_id ) );
switch( $function_name ) {
case 'woocommerce_breadcrumb':
$breadcrumb_separator = et_()->array_get( $args, 'breadcrumb_separator', '' );