: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( is_product_category() ) {
$this->props['include_categories'] = (string) get_queried_object_id();
} else if ( is_product_tag() ) {
$product_tags = array( get_queried_object()->slug );
} else if ( is_product_taxonomy() ) {
$term = get_queried_object();
// Product attribute taxonomy slugs start with pa_
if ( et_()->starts_with( $term->taxonomy, 'pa_' ) ) {
$product_attribute = $term->taxonomy;
$product_terms[] = $term->slug;
if ( 'product_category' === $type || ( $use_current_loop && ! empty( $this->props['include_categories'] ) ) ) {
$all_shop_categories = et_builder_get_shop_categories();
$all_shop_categories_map = array();
$raw_product_categories = self::filter_include_categories( $this->props['include_categories'], $post_id, 'product_cat' );
foreach ( $all_shop_categories as $term ) {
if ( is_object( $term ) && is_a( $term, 'WP_Term' ) ) {
$all_shop_categories_map[ $term->term_id ] = $term->slug;
$product_categories = array_values( $all_shop_categories_map );
if ( ! empty( $raw_product_categories ) ) {
$product_categories = array_intersect_key(
$all_shop_categories_map,
array_flip( $raw_product_categories )
// Recent was the default option in Divi once, so it is added here for the websites created before the change
if ( 'default' === $orderby && ( 'default' === $type || 'recent' === $type ) ) {
// Leave the attribute empty to allow WooCommerce to take over and use the default sorting.
if ( 'latest' === $type ) {
if ( in_array( $orderby, array( 'price-desc', 'date-desc' ) ) ) {
// Supported orderby arguments (as defined by WC_Query->get_catalog_ordering_args() ):
// rand | date | price | popularity | rating | title
$orderby = str_replace( '-desc', '', $orderby );
// Switch to descending order if orderby is 'price-desc' or 'date-desc'
$wc_custom_views = array(
'sale' => array( 'on_sale', 'true' ),
'best_selling' => array( 'best_selling', 'true' ),
'top_rated' => array( 'top_rated', 'true' ),
'featured' => array( 'visibility', 'featured' ),
if ( et_()->includes( array_keys( $wc_custom_views ), $type ) ) {
$custom_view_data = $wc_custom_views[ $type ];
$wc_custom_view = sprintf( '%1$s="%2$s"', esc_attr( $custom_view_data[0] ), esc_attr( $custom_view_data[1] ) );
'[products %1$s limit="%2$s" orderby="%3$s" columns="%4$s" %5$s order="%6$s" %7$s %8$s %9$s %10$s %11$s]',
et_core_intentionally_unescaped( $wc_custom_view, 'fixed_string' ),
esc_attr( $posts_number ),
$product_categories ? sprintf( 'category="%s"', esc_attr( implode( ',', $product_categories ) ) ) : '',
$pagination ? 'paginate="true"' : '',
$ids ? sprintf( 'ids="%s"', esc_attr( implode( ',', $ids ) ) ) : '',
$product_tags ? sprintf( 'tag="%s"', esc_attr( implode( ',', $product_tags ) ) ) : '',
$product_attribute ? sprintf( 'attribute="%s"', esc_attr( $product_attribute ) ) : '',
$product_terms ? sprintf( 'terms="%s"', esc_attr( implode( ',', $product_terms ) ) ) : ''
do_action( 'et_pb_shop_before_print_shop' );
$query_backup = $wp_the_query;
if ( $use_current_loop ) {
add_filter( 'woocommerce_shortcode_products_query', array( $this, 'filter_products_query' ) );
add_action( 'pre_get_posts', array( $this, 'apply_woo_widget_filters' ), 0 );
$shop = do_shortcode( $shortcode );
if ( $use_current_loop ) {
remove_action( 'pre_get_posts', array( $this, 'apply_woo_widget_filters' ), 0 );
remove_filter( 'woocommerce_shortcode_products_query', array( $this, 'filter_products_query' ) );
$wp_the_query = $query_backup;
do_action( 'et_pb_shop_after_print_shop' );
if ( '<div class="woocommerce columns-0"></div>' === $shop || et_()->starts_with( $shop, $shortcode ) ) {
$shop = self::get_no_results_template();
* Get shop HTML for shop module
* @param array arguments that affect shop output
* @param array passed conditional tag for update process
* @param array passed current page params
* @return string HTML markup for shop module
static function get_shop_html( $args = array(), $conditional_tags = array(), $current_page = array() ) {
do_action( 'et_pb_get_shop_html_before' );
// Force product loop to have 'product' class name. It appears that 'product' class disappears
// when $this->get_shop() is being called for update / from admin-ajax.php
add_filter( 'post_class', array( $shop, 'add_product_class_name' ) );
$output = $shop->get_shop( array(), array(), $current_page );
// Remove 'product' class addition to product loop's post class
remove_filter( 'post_class', array( $shop, 'add_product_class_name' ) );
do_action( 'et_pb_get_shop_html_after' );
// WooCommerce changed the title tag from h3 to h2 in 3.0.0
function get_title_selector() {
$title_selector = 'li.product h3';
if ( class_exists( 'WooCommerce' ) ) {
if ( version_compare( $woocommerce->version, '3.0.0', '>=' ) ) {
$title_selector = 'li.product h2';
function render( $attrs, $content = null, $render_slug ) {
$type = $this->props['type'];
$include_categories = $this->props['include_categories'];
$posts_number = $this->props['posts_number'];
$orderby = $this->props['orderby'];
$columns = $this->props['columns_number'];
$video_background = $this->video_background();
$parallax_image_background = $this->get_parallax_image_background();
$sale_badge_color_hover = $this->get_hover_value( 'sale_badge_color' );
$sale_badge_color_values = et_pb_responsive_options()->get_property_values( $this->props, 'sale_badge_color' );
$icon_hover_color_values = et_pb_responsive_options()->get_property_values( $this->props, 'icon_hover_color' );
$hover_overlay_color_value = et_pb_responsive_options()->get_property_values( $this->props, 'hover_overlay_color' );
$hover_icon = $this->props['hover_icon'];
$hover_icon_values = et_pb_responsive_options()->get_property_values( $this->props, 'hover_icon' );
$hover_icon_tablet = isset( $hover_icon_values['tablet'] ) ? $hover_icon_values['tablet'] : '';
$hover_icon_phone = isset( $hover_icon_values['phone'] ) ? $hover_icon_values['phone'] : '';
et_pb_responsive_options()->generate_responsive_css( $sale_badge_color_values, '%%order_class%% span.onsale', 'background-color', $render_slug, ' !important;', 'color' );
if ( et_builder_is_hover_enabled( 'sale_badge_color', $this->props ) ) {
ET_Builder_Element::set_style( $render_slug, array(
'selector' => '%%order_class%%:hover span.onsale',
'declaration' => sprintf(
'background-color: %1$s !important;',
esc_html( $sale_badge_color_hover )
et_pb_responsive_options()->generate_responsive_css( $icon_hover_color_values, '%%order_class%% .et_overlay:before', 'color', $render_slug, ' !important;', 'color' );
et_pb_responsive_options()->generate_responsive_css( $hover_overlay_color_value, '%%order_class%% .et_overlay', array( 'background-color', 'border-color' ), $render_slug, ' !important;', 'color' );
// Images: Add CSS Filters and Mix Blend Mode rules (if set)
if ( array_key_exists( 'image', $this->advanced_fields ) && array_key_exists( 'css', $this->advanced_fields['image'] ) ) {
$this->add_classname( $this->generate_css_filters(
self::$data_utils->array_get( $this->advanced_fields['image']['css'], 'main', '%%order_class%%' )
$overlay_attributes = ET_Builder_Module_Helper_Overlay::render_attributes( array(
'icon_tablet' => $hover_icon_tablet,
'icon_phone' => $hover_icon_phone,
if ( class_exists( 'ET_Builder_Module_Helper_Woocommerce_Modules' ) ) {
ET_Builder_Module_Helper_Woocommerce_Modules::add_star_rating_style(
'%%order_class%% ul.products li.product .star-rating',
'%%order_class%% ul.products li.product:hover .star-rating'
$this->add_classname( array(
$this->get_text_orientation_classname(),
if ( '0' === $columns ) {
$this->add_classname( 'et_pb_shop_grid' );
$shop_order = self::_get_index( array( self::INDEX_MODULE_ORDER, $render_slug ) );
'<div%2$s class="%3$s" %6$s data-shortcode_index="%7$s">
$this->get_shop( array(), array(), array( 'id' => $this->get_the_ID() ) ),
$this->module_classname( $render_slug ),
$parallax_image_background,
et_core_esc_previously( $overlay_attributes ),
* Filter the products query arguments.
* @param array $query_args
public function filter_products_query( $query_args ) {
$query_args['s'] = get_search_query();
if ( function_exists( 'WC' ) ) {
$query_args['meta_query'] = WC()->query->get_meta_query( et_()->array_get( $query_args, 'meta_query', array() ), true );
$query_args['tax_query'] = WC()->query->get_tax_query( et_()->array_get( $query_args, 'tax_query', array() ), true );
// Add fake cache-busting arguments as the filtering is actually done in self::apply_woo_widget_filters().
$query_args['et_builder_filter_min_price'] = sanitize_text_field( et_()->array_get( $_GET, 'min_price', '' ) );
$query_args['et_builder_filter_max_price'] = sanitize_text_field( et_()->array_get( $_GET, 'max_price', '' ) );
* Filter the products shortcode query so Woo widget filters apply.
public function apply_woo_widget_filters( $query ) {
// Trick Woo filters into thinking the products shortcode query is the
// main page query as some widget filters have is_main_query checks.
// Set a flag to track that the main query is falsified.
$wp_the_query->et_pb_shop_query = true;
if ( function_exists( 'WC' ) ) {
add_filter( 'posts_clauses', array( WC()->query, 'price_filter_post_clauses' ), 10, 2 );
new ET_Builder_Module_Shop;