PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.6
ShopBuilder – WooCommerce Builder For Elementor v3.2.6
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
← All changes | app/Elementor/Helper/RenderHelpers.php +86 -326 3.4.23.2.6 View file →
@@ -11,8 +11,9 @@
11 11
12 12 use Elementor\Plugin;
13 13 use RadiusTheme\SB\Helpers\Cache;
14 14 use RadiusTheme\SB\Helpers\Fns;
15 +use RadiusTheme\SB\Helpers\BuilderFns;
15 16 use WC_Product_Query;
16 17 use WP_Term;
17 18
18 19 // Do not allow directly accessing this file.
@@ -136,10 +137,9 @@
136 137
137 138 // Pagination.
138 139 'pagination' => ! empty( $meta['show_pagination'] ),
139 140 'posts_loading_type' => self::get_data( $meta, 'pagination_type', 'pagination' ),
140 - 'posts_per_page' => self::get_data( $meta, 'pagination_per_page', '' ) ?: ( ! empty( $meta['posts_per_page'] ) ? absint( $meta['posts_per_page'] ) : '' ),
141 - 'visible_range' => max( 1, absint( self::get_data( $meta, 'pagination_visible_range', 5 ) ) ),
141 + 'posts_per_page' => self::get_data( $meta, 'pagination_per_page', '' ),
142 142
143 143 // Image.
144 144 'f_img' => ! empty( $meta['show_featured_image'] ),
145 145 'gallery_img' => ! empty( $meta['show_product_gallery'] ),
@@ -263,13 +263,11 @@
263 263
264 264 $queried_obj = get_queried_object();
265 265
266 266 if ( is_shop() || is_product_taxonomy() ) {
267 - if ( empty( $data['posts_per_page'] ) ) {
268 - $data['posts_per_page'] = self::get_products_per_page();
269 - }
270 - $data['order_by'] = self::get_data( $data, 'rtsb_orderby', $data['order_by'] );
271 - $data['order'] = self::get_data( $data, 'rtsb_order', $data['order'] );
267 + $data['posts_per_page'] = self::get_products_per_page();
268 + $data['order_by'] = self::get_data( $data, 'rtsb_orderby', $data['order_by'] );
269 + $data['order'] = self::get_data( $data, 'rtsb_order', $data['order'] );
272 270 }
273 271
274 272 if ( ! is_shop() && is_product_taxonomy() ) {
275 273 if ( ! empty( $queried_obj->taxonomy ) ) {
@@ -281,13 +279,9 @@
281 279 }
282 280 }
283 281
284 282 if ( is_search() ) {
285 - // Ask for the unescaped term: this dataset is data, not output.
286 - // get_search_query() defaults to esc_attr(), and pre-escaping here
287 - // would be encoded a second time at the output boundary and would
288 - // corrupt the JSON payload this array is encoded into.
289 - $search_query = get_search_query( false );
283 + $search_query = get_search_query();
290 284
291 285 if ( $search_query ) {
292 286 $data['search_query'] = sanitize_text_field( $search_query );
293 287 }
@@ -317,12 +311,8 @@
317 311 'comparison_button' => ! empty( $meta['comparison_button'] ),
318 312 'quick_view_button' => ! empty( $meta['quick_view_button'] ),
319 313 'show_rating' => ! empty( $meta['show_rating'] ),
320 314 'show_pagination' => ! empty( $meta['show_pagination'] ),
321 - // The base Product Archive widget has no pagination-type control: with filters it
322 - // uses Ajax Load More (see the widget's pagination notice). Custom-layout widgets
323 - // pass their own pagination_type and use a different dataset.
324 - 'posts_loading_type' => self::get_data( $meta, 'pagination_type', 'load_more' ),
325 315 'cart_icon' => self::get_data( $meta, 'cart_icon', [] ),
326 316 'wishlist_icon' => self::get_data( $meta, 'wishlist_icon', [] ),
327 317 'wishlist_icon_added' => self::get_data( $meta, 'wishlist_icon_added', [] ),
328 318 'comparison_icon' => self::get_data( $meta, 'comparison_icon', [] ),
@@ -339,13 +329,9 @@
339 329 );
340 330
341 331 $queried_obj = get_queried_object();
342 332
343 - // Use WooCommerce's own taxonomy check (covers product_cat, product_tag
344 - // and product_brand) so the queried term is recorded on every product
345 - // taxonomy archive — Ajax load-more / filters scope to it. Mirrors the
346 - // custom-layout dataset in self::meta_dataset().
347 - if ( ! is_shop() && is_product_taxonomy() ) {
333 + if ( ! is_shop() && BuilderFns::is_archive() ) {
348 334 if ( ! empty( $queried_obj->taxonomy ) ) {
349 335 $data['queried_tax'] = esc_html( $queried_obj->taxonomy );
350 336 }
351 337
@@ -354,13 +340,9 @@
354 340 }
355 341 }
356 342
357 343 if ( is_search() ) {
358 - // Ask for the unescaped term: this dataset is data, not output.
359 - // get_search_query() defaults to esc_attr(), and pre-escaping here
360 - // would be encoded a second time at the output boundary and would
361 - // corrupt the JSON payload this array is encoded into.
362 - $search_query = get_search_query( false );
344 + $search_query = get_search_query();
363 345
364 346 if ( $search_query ) {
365 347 $data['search_query'] = sanitize_text_field( $search_query );
366 348 }
@@ -543,18 +525,9 @@
543 525 */
544 526 public static function pagination_data( $metas, $template ) {
545 527 $el_data = self::meta_dataset( $metas, $template );
546 528
547 - // Returned as an attribute *value*; the caller escapes it for the
548 - // attribute context (Elementor's Utils::render_html_attributes() applies
549 - // esc_attr()). esc_js() here only double-encoded the payload and, via
550 - // addslashes(), corrupted the JSON escapes for apostrophes and
551 - // non-ASCII search terms. The HTML-safe flags keep the payload free of
552 - // characters esc_attr() would have to encode - see self::archive_data().
553 - return wp_json_encode(
554 - $el_data,
555 - JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
556 - );
529 + return esc_js( wp_json_encode( $el_data, JSON_UNESCAPED_UNICODE ) );
557 530 }
558 531
559 532 /**
560 533 * Container classes
@@ -576,9 +549,9 @@
576 549
577 550 $animation = $metas['hover_animation'];
578 551
579 552 if ( ! $is_cat ) {
580 - $cart_txt_class = $metas['show_cart_text'] ? ' show-cart-text' : ' no-cart-text';
553 + $cart_txt_class = $metas['show_cart_text'] ? ' has-cart-text' : ' no-cart-text';
581 554 $animation .= ' gallery-hover-' . ( ! empty( $settings['gallery_hover_animation'] ) ? $settings['gallery_hover_animation'] : 'fade' );
582 555 }
583 556
584 557 $badge_class = [
@@ -605,15 +578,11 @@
605 578 }
606 579
607 580 if ( ! $is_cat ) {
608 581 $action_btn_classes = [
609 - 'show_compare_text' => 'show-compare-text',
610 - 'show_quick_view_text' => 'show-quick-view-text',
611 - 'show_wishlist_text' => 'show-wishlist-text',
612 - // Quick checkout only had its icon entry here, so the "Show Button Text?"
613 - // switch had nothing to emit and the label stayed hidden whatever it was
614 - // set to.
615 - 'show_quick_checkout_text' => 'show-quick-checkout-text',
582 + 'show_compare_text' => 'has-compare-text',
583 + 'show_quick_view_text' => 'has-quick-view-text',
584 + 'show_wishlist_text' => 'has-wishlist-text',
616 585 'show_compare_icon' => 'no-compare-icon',
617 586 'show_quick_view_icon' => 'no-quick-view-icon',
618 587 'show_wishlist_icon' => 'no-wishlist-icon',
619 588 'show_quick_checkout_icon' => 'no-quick_checkout-icon',
@@ -659,53 +628,15 @@
659 628 if ( ! rtsb()->has_pro() && ! in_array( $layout, array_keys( Fns::free_layouts( $layout ) ), true ) ) {
660 629 $layout = self::set_default_layout( $layout );
661 630 }
662 631
663 - return self::resolve_existing_layout( $layout, $template );
632 + return $layout;
664 633 } else {
665 - return ! empty( $template ) ? self::resolve_existing_layout( self::set_default_layout( $layout ), $template ) : $default;
634 + return ! empty( $template ) ? self::set_default_layout( $layout ) : $default;
666 635 }
667 636 }
668 637
669 638 /**
670 - * Falls back to the default layout when the resolved template file cannot
671 - * be found.
672 - *
673 - * Layouts contributed by a theme or an addon (rtsb-*, toyup-*, zilly-*)
674 - * stay saved in the Elementor document after that theme or addon is gone.
675 - * Without this check the loop asks for a template that no longer exists,
676 - * so nothing renders and load_template() raises a "does not exist" notice
677 - * for every product in the loop.
678 - *
679 - * @param string $layout Resolved layout name.
680 - * @param string $template Template directory, e.g. "elementor/general/grid/".
681 - * Empty when the layout is only used for CSS classes.
682 - *
683 - * @return string
684 - */
685 - private static function resolve_existing_layout( $layout, $template ) {
686 - if ( empty( $template ) || empty( $layout ) ) {
687 - return $layout;
688 - }
689 -
690 - $cache_key = 'layout_exists_' . $template . $layout;
691 -
692 - if ( isset( self::$cache[ $cache_key ] ) ) {
693 - return self::$cache[ $cache_key ];
694 - }
695 -
696 - $resolved = $layout;
697 -
698 - if ( ! Fns::locate_template( $template . $layout ) ) {
699 - $resolved = self::set_default_layout( $layout );
700 - }
701 -
702 - self::$cache[ $cache_key ] = $resolved;
703 -
704 - return $resolved;
705 - }
706 -
707 - /**
708 639 * Row classes
709 640 *
710 641 * @param array $settings Settings array.
711 642 *
@@ -775,12 +706,11 @@
775 706 return $has_filters ? self::pagination_data( $settings, $template ) : '';
776 707 } elseif ( $is_cat ) {
777 708 return '';
778 709 } else {
779 - $ajax_pagination = ! empty( $settings['show_pagination'] ) && 'pagination' !== $settings['pagination_type'];
780 - $page = Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) );
781 - $is_shop_or_archive = is_shop() || is_product_taxonomy();
782 - $condition = rtsb()->has_pro() && $ajax && ( $ajax_pagination || $has_filters || $page || $is_shop_or_archive );
710 + $ajax_pagination = ! empty( $settings['show_pagination'] ) && 'pagination' !== $settings['pagination_type'];
711 + $page = Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) );
712 + $condition = rtsb()->has_pro() && $ajax && ( $ajax_pagination || $has_filters || $page );
783 713
784 714 return $condition ? self::pagination_data( $settings, $template ) : '';
785 715 }
786 716 }
@@ -795,23 +725,9 @@
795 725 */
796 726 public static function archive_data( $metas, $template ) {
797 727 $el_data = self::archive_meta_dataset( $metas, $template );
798 728
799 - // The return value is an HTML attribute, so it must be escaped for the
800 - // HTML attribute context. esc_js() is a JavaScript-string escaper: it
801 - // turns ' back into a bare apostrophe and relies on a backslash
802 - // that means nothing to an HTML parser, which let a search term close
803 - // the attribute and inject its own. esc_attr() encodes both quote
804 - // characters, so the value can never terminate the attribute.
805 - //
806 - // The JSON is additionally encoded with the HTML-safe flags so it holds
807 - // no <, >, &, ' or " at all. That keeps a search term that itself looks
808 - // like an entity (e.g. &quot;) intact: esc_attr() does not double-encode
809 - // an existing entity, so without this the browser would decode the
810 - // term back into a raw quote and break JSON.parse().
811 - $json = wp_json_encode( $el_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT );
812 -
813 - return sprintf( ' data-rtsb-ajax="%s"', esc_attr( $json ) );
729 + return ' data-rtsb-ajax=\'' . esc_js( wp_json_encode( $el_data ) ) . '\'';
814 730 }
815 731
816 732 /**
817 733 * Slider options.
@@ -1384,188 +1300,47 @@
1384 1300 'pad_counts' => true,
1385 1301 'hierarchical' => true,
1386 1302 ];
1387 1303
1388 - // Archive-scope only when the URL path itself is a term archive
1389 - // (e.g. /product-category/clothing/). On the shop page WordPress can
1390 - // resolve ?product_cat=X to a queried term, but in that case we want
1391 - // the full hierarchy to remain visible for additional filtering.
1392 - $archive_term = self::request_path_is_term_archive( $taxonomy ) ? self::resolve_archive_term_from_path( $taxonomy ) : null;
1304 + $cache_key = 'rtsb_get_terms_filter_products' . $taxonomy;
1305 + $taxonomy_terms = wp_cache_get( $cache_key, 'shopbuilder' );
1393 1306
1394 - // The cache key MUST include the archive term id. The scoped list is
1395 - // different for every term archive, so a taxonomy-only key leaks the
1396 - // first visited category's term list into every other category archive
1397 - // when a persistent object cache is active - the leaked terms are then
1398 - // counted as empty in the current archive and the whole filter renders
1399 - // blank.
1400 - $cache_key = $archive_term instanceof WP_Term
1401 - ? 'rtsb_get_terms_filter_products_archive_' . $taxonomy . '_' . $archive_term->term_id
1402 - : 'rtsb_get_terms_filter_products_full_' . $taxonomy;
1307 + if ( false === $taxonomy_terms ) {
1308 + $queried_object = get_queried_object();
1403 1309
1404 - $taxonomy_terms = wp_cache_get( $cache_key, Cache::group() );
1310 + if ( 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) && $queried_object instanceof WP_Term && $queried_object->taxonomy === $taxonomy ) {
1311 + $term_id = $queried_object->term_id;
1312 + $taxonomy = $queried_object->taxonomy;
1313 + $archive_cache_key = 'rtsb_get_terms_filter_products_' . $taxonomy . $term_id;
1314 + $taxonomy_terms = wp_cache_get( $archive_cache_key, 'shopbuilder' );
1315 + if ( false === $taxonomy_terms ) {
1316 + $taxonomy_terms = [ $queried_object ];
1317 + $child_args = [
1318 + 'taxonomy' => $taxonomy,
1319 + 'child_of' => $term_id,
1320 + 'hide_empty' => false,
1321 + ];
1405 1322
1406 - if ( false === $taxonomy_terms ) {
1407 - if ( $archive_term instanceof WP_Term ) {
1408 - // Treat the URL-derived term as the root of the visible
1409 - // hierarchy so the recursive renderer in
1410 - // get_product_default_filter_list_html() (which starts
1411 - // with $parent = 0) picks it up on child-category
1412 - // archives. Clone first to avoid mutating WP's term
1413 - // object.
1414 - $current_term = clone $archive_term;
1415 - $current_term->parent = 0;
1416 - $taxonomy_terms = [ $current_term ];
1417 - $child_args = [
1418 - 'taxonomy' => $taxonomy,
1419 - 'child_of' => $archive_term->term_id,
1420 - 'hide_empty' => false,
1421 - ];
1323 + $child_terms = get_terms( $child_args );
1422 1324
1423 - $child_terms = get_terms( $child_args );
1325 + if ( ! empty( $child_terms ) ) {
1326 + $taxonomy_terms = array_merge( $taxonomy_terms, $child_terms );
1327 + }
1424 1328
1425 - if ( ! empty( $child_terms ) && ! is_wp_error( $child_terms ) ) {
1426 - $taxonomy_terms = array_merge( $taxonomy_terms, $child_terms );
1329 + wp_cache_set( $archive_cache_key, $taxonomy_terms, 'shopbuilder' );
1330 + Cache::set_data_cache_key( $archive_cache_key );
1427 1331 }
1428 1332 } else {
1429 1333 $taxonomy_terms = get_terms( $args );
1430 1334 }
1431 1335
1432 - wp_cache_set( $cache_key, $taxonomy_terms, Cache::group(), 12 * HOUR_IN_SECONDS );
1336 + wp_cache_set( $cache_key, $taxonomy_terms, 'shopbuilder' );
1433 1337 Cache::set_data_cache_key( $cache_key );
1434 1338 }
1435 1339
1436 1340 return $taxonomy_terms;
1437 1341 }
1438 -
1439 1342 /**
1440 - * Resolve the archive term from the request URL path. Avoids relying on
1441 - * get_queried_object() which can return an unexpected term when
1442 - * ?product_cat=a,b,c is also present in the request.
1443 - *
1444 - * @param string $taxonomy Taxonomy slug.
1445 - *
1446 - * @return \WP_Term|null
1447 - */
1448 - private static function resolve_archive_term_from_path( $taxonomy ) {
1449 - $rewrite_slugs = self::get_taxonomy_archive_slugs( $taxonomy );
1450 -
1451 - if ( empty( $rewrite_slugs ) ) {
1452 - return null;
1453 - }
1454 -
1455 - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Path only used for slug extraction.
1456 - $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
1457 - // phpcs:enable
1458 - $path = (string) wp_parse_url( $request_uri, PHP_URL_PATH );
1459 -
1460 - if ( '' === $path ) {
1461 - return null;
1462 - }
1463 -
1464 - foreach ( $rewrite_slugs as $base ) {
1465 - $base = trim( $base, '/' );
1466 - $needle = '/' . $base . '/';
1467 -
1468 - $pos = strpos( $path, $needle );
1469 - if ( false === $pos ) {
1470 - continue;
1471 - }
1472 -
1473 - $remainder = substr( $path, $pos + strlen( $needle ) );
1474 - $segments = array_values(
1475 - array_filter(
1476 - explode( '/', $remainder ),
1477 - static function ( $segment ) {
1478 - return '' !== $segment;
1479 - }
1480 - )
1481 - );
1482 -
1483 - if ( empty( $segments ) ) {
1484 - continue;
1485 - }
1486 -
1487 - $slug = sanitize_title( end( $segments ) );
1488 - $term = get_term_by( 'slug', $slug, $taxonomy );
1489 -
1490 - if ( $term instanceof WP_Term ) {
1491 - return $term;
1492 - }
1493 - }
1494 -
1495 - return null;
1496 - }
1497 -
1498 - /**
1499 - * Decide whether the current REQUEST_URI path represents a real term
1500 - * archive page for the given taxonomy. URL-based so it ignores cases
1501 - * where WordPress merely resolves a query string into a queried term
1502 - * on the shop page.
1503 - *
1504 - * @param string $taxonomy Taxonomy slug.
1505 - *
1506 - * @return bool
1507 - */
1508 - private static function request_path_is_term_archive( $taxonomy ) {
1509 - $rewrite_slugs = self::get_taxonomy_archive_slugs( $taxonomy );
1510 -
1511 - if ( empty( $rewrite_slugs ) ) {
1512 - return false;
1513 - }
1514 -
1515 - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Path only used in match.
1516 - $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
1517 - // phpcs:enable
1518 - $path = (string) wp_parse_url( $request_uri, PHP_URL_PATH );
1519 -
1520 - if ( '' === $path ) {
1521 - return false;
1522 - }
1523 -
1524 - foreach ( $rewrite_slugs as $slug ) {
1525 - if ( '' === $slug ) {
1526 - continue;
1527 - }
1528 - if ( false !== strpos( $path, '/' . trim( $slug, '/' ) . '/' ) ) {
1529 - return true;
1530 - }
1531 - }
1532 -
1533 - return false;
1534 - }
1535 -
1536 - /**
1537 - * Resolve the URL slug(s) used by a taxonomy archive (e.g. product_cat
1538 - * maps to "product-category"). Falls back to common WooCommerce defaults
1539 - * when the taxonomy object is unavailable.
1540 - *
1541 - * @param string $taxonomy Taxonomy slug.
1542 - *
1543 - * @return array<string>
1544 - */
1545 - private static function get_taxonomy_archive_slugs( $taxonomy ) {
1546 - $slugs = [];
1547 -
1548 - if ( taxonomy_exists( $taxonomy ) ) {
1549 - $tax_obj = get_taxonomy( $taxonomy );
1550 - if ( $tax_obj && ! empty( $tax_obj->rewrite['slug'] ) ) {
1551 - $slugs[] = $tax_obj->rewrite['slug'];
1552 - }
1553 - }
1554 -
1555 - $defaults = [
1556 - 'product_cat' => 'product-category',
1557 - 'product_tag' => 'product-tag',
1558 - 'product_brand' => 'product-brand',
1559 - ];
1560 -
1561 - if ( isset( $defaults[ $taxonomy ] ) ) {
1562 - $slugs[] = $defaults[ $taxonomy ];
1563 - }
1564 -
1565 - return array_unique( array_filter( $slugs ) );
1566 - }
1567 - /**
1568 1343 * Generates an error message block.
1569 1344 *
1570 1345 * @param array $title The title array of the error message block.
1571 1346 * @param string $wrapper Additional CSS class for the wrapper div.
@@ -1659,14 +1434,10 @@
1659 1434 $term_children = in_array( $tax->taxonomy, [ 'product_cat', 'product_brand' ], true ) ? get_term_children( $tax->term_id, $tax->taxonomy ) : [];
1660 1435 $taxonomy = $tax->taxonomy;
1661 1436 $product_count = self::get_visible_term_count( $tax->taxonomy, $tax->term_id );
1662 1437
1663 - // On a product taxonomy archive (category/tag/attribute page) scope the
1664 - // count to the current archive - only products that are in THIS archive
1665 - // AND have this term. This makes the displayed count reflect the current
1666 - // category context and lets terms that are empty in this archive be
1667 - // dropped from the list.
1668 - if ( is_product_taxonomy() || 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) {
1438 + // Show count.
1439 + if ( 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) && ! empty( $data['count_display'] ) && 'block' === $data['count_display'] ) {
1669 1440 $product_count = self::count_tax_data( $tax, $product_count );
1670 1441 }
1671 1442
1672 1443 // Show Empty Categories.
@@ -1757,15 +1528,8 @@
1757 1528 */
1758 1529 public static function count_tax_data( $tax, $count ) {
1759 1530 $page = get_queried_object();
1760 1531
1761 - // Scoping only applies on a real taxonomy archive where the queried
1762 - // object is a term. On other contexts (e.g. the shop page) the queried
1763 - // object is not a term, so keep the original (global) count.
1764 - if ( ! $page instanceof \WP_Term ) {
1765 - return $count;
1766 - }
1767 -
1768 1532 if ( is_array( $tax ) && strpos( $tax['taxonomy'], 'attribute_' ) !== false ) {
1769 1533 $taxonomy = str_replace( 'attribute_', '', $tax['taxonomy'] );
1770 1534 $term_id = $tax['term_id'];
1771 1535 } else {
@@ -1772,20 +1536,8 @@
1772 1536 $taxonomy = $tax->taxonomy;
1773 1537 $term_id = $tax->term_id;
1774 1538 }
1775 1539
1776 - // A taxonomy selected through the query string is a removable choice,
1777 - // not a fixed archive context, and the filter ORs terms within one
1778 - // taxonomy. Scoping a term's count to that selection therefore
1779 - // advertises a result the filter never produces - on
1780 - // /shop/?product_cat=aliens the Movies count read 32 while choosing
1781 - // Movies returns 279 - and it hides every sibling term whose overlap
1782 - // with the selection is zero. Only a real term-archive URL
1783 - // (/product-category/aliens/) is a fixed context worth scoping to.
1784 - if ( $taxonomy === $page->taxonomy && ! self::request_path_is_term_archive( $page->taxonomy ) ) {
1785 - return $count;
1786 - }
1787 -
1788 1540 if ( strpos( $page->taxonomy, 'pa_' ) !== false ) {
1789 1541 $args['status'] = 'publish';
1790 1542 $args['limit'] = -1;
1791 1543 $args['return'] = 'ids';
@@ -1810,42 +1562,50 @@
1810 1562 'operator' => 'NOT IN',
1811 1563 ],
1812 1564 ];
1813 1565
1814 - return Fns::count_products_by_tax_query( $args['tax_query'] );
1566 + $query = new WC_Product_Query( $args );
1567 + $products = $query->get_products();
1568 +
1569 + return count( $products );
1815 1570 }
1816 1571
1817 - // Scope the count to the current archive term (category, brand, tag, etc.)
1818 - // so it reflects only products that are in this archive AND also have the
1819 - // filter term. Works for cross-taxonomy filters (e.g. brands on a tag
1820 - // archive) and same-taxonomy filters (e.g. the Tags filter on a tag
1821 - // archive shows the intersection with the current tag).
1822 - $args = [
1823 - 'limit' => -1,
1824 - 'return' => 'ids',
1825 - 'status' => 'publish',
1826 - 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1827 - 'relation' => 'AND',
1828 - [
1829 - 'taxonomy' => $taxonomy,
1830 - 'field' => 'term_id',
1831 - 'terms' => $term_id,
1572 + if ( $taxonomy !== $page->taxonomy ) {
1573 + $args = [
1574 + 'limit' => -1,
1575 + 'return' => 'ids',
1576 + 'status' => 'publish',
1577 + 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1578 + 'relation' => 'AND',
1579 + [
1580 + 'taxonomy' => $taxonomy,
1581 + 'field' => 'term_id',
1582 + 'terms' => $term_id,
1583 + ],
1584 + [
1585 + 'taxonomy' => 'product_visibility',
1586 + 'terms' => [ 'exclude-from-catalog' ],
1587 + 'field' => 'name',
1588 + 'operator' => 'NOT IN',
1589 + ],
1832 1590 ],
1833 - [
1834 - 'taxonomy' => $page->taxonomy,
1835 - 'field' => 'term_id',
1836 - 'terms' => $page->term_id,
1837 - ],
1838 - [
1839 - 'taxonomy' => 'product_visibility',
1840 - 'terms' => [ 'exclude-from-catalog' ],
1841 - 'field' => 'name',
1842 - 'operator' => 'NOT IN',
1843 - ],
1844 - ],
1845 - ];
1591 + ];
1846 1592
1847 - return Fns::count_products_by_tax_query( $args['tax_query'] );
1593 + if ( 'product_cat' === $page->taxonomy ) {
1594 + $args['category'] = [ $page->slug ];
1595 + }
1596 +
1597 + if ( 'product_tag' === $page->taxonomy ) {
1598 + $args['tag'] = [ $page->slug ];
1599 + }
1600 +
1601 + $query = new WC_Product_Query( $args );
1602 + $products = $query->get_products();
1603 +
1604 + $count = count( $products );
1605 + }
1606 +
1607 + return $count;
1848 1608 }
1849 1609
1850 1610 /**
1851 1611 * Get the count of visible (non-hidden) products for a given taxonomy term.
@@ -1886,9 +1646,10 @@
1886 1646 ],
1887 1647 ],
1888 1648 ];
1889 1649
1890 - $count = Fns::count_products_by_tax_query( $args['tax_query'] );
1650 + $query = new WC_Product_Query( $args );
1651 + $count = count( $query->get_products() );
1891 1652
1892 1653 $cache[ $cache_key ] = $count;
1893 1654
1894 1655 return $count;
@@ -2060,9 +1821,9 @@
2060 1821 }
2061 1822
2062 1823 $attachment_id = absint( get_term_meta( $args['term_id'], 'product_attribute_image', true ) );
2063 1824 $image_size = rtwpvs()->get_option( 'attribute_image_size' );
2064 - $image_url = wp_get_attachment_image_url( $attachment_id, apply_filters( 'rtsb_product_attribute_image_size', $image_size ) );
1825 + $image_url = wp_get_attachment_image_url( $attachment_id, apply_filters( 'rtwpvs_product_attribute_image_size', $image_size ) );
2065 1826
2066 1827 $html .= sprintf(
2067 1828 // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
2068 1829 '<input type="checkbox" id="' . esc_attr( $args['id'] ) . '" value="' . esc_attr( $args['term_slug'] ) . '" class="rtsb-attr-hidden-field" name="rtsb-filter-' . esc_attr( $args['attribute_name'] ) . '[]' . '" ' . esc_attr( $args['selected_item'] ) . '/><label for="%s" title="' . esc_attr( $args['name'] ) . '" class="rtsb-default-filter-trigger rtsb-image-filter rtsb-term-span rtsb-term-span-%s%s"><img class="rtsb-default-attr-filter" alt="%s" src="%s" /></label>',
@@ -2103,10 +1864,9 @@
2103 1864 $html = '';
2104 1865
2105 1866 ob_start();
2106 1867
2107 - $is_editor = ! empty( \Elementor\Plugin::$instance->editor ) && \Elementor\Plugin::$instance->editor->is_edit_mode();
2108 - $active = ( $is_editor || ! empty( $_SERVER['QUERY_STRING'] ) ) ? ' active' : '';
1868 + $active = ! empty( $_SERVER['QUERY_STRING'] ) ? ' active' : '';
2109 1869 $reset = ! empty( $settings['reset_btn'] );
2110 1870 $apply_text = ! empty( $settings['apply_filters_btn_text'] ) ? $settings['apply_filters_btn_text'] : esc_html__( 'Apply Filters', 'shopbuilder' );
2111 1871 $apply_icon = ! empty( $settings['apply_filters_btn_icon'] ) ? $settings['apply_filters_btn_icon'] : [];
2112 1872 echo '<div class="default-filter-btn-wrapper">';