PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.4.0
ShopBuilder – WooCommerce Builder For Elementor v3.4.0
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 +48 -124 3.4.23.4.0 View file →
@@ -137,9 +137,8 @@
137 137 // Pagination.
138 138 'pagination' => ! empty( $meta['show_pagination'] ),
139 139 'posts_loading_type' => self::get_data( $meta, 'pagination_type', 'pagination' ),
140 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 ) ) ),
142 141
143 142 // Image.
144 143 'f_img' => ! empty( $meta['show_featured_image'] ),
145 144 'gallery_img' => ! empty( $meta['show_product_gallery'] ),
@@ -281,13 +280,9 @@
281 280 }
282 281 }
283 282
284 283 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 );
284 + $search_query = get_search_query();
290 285
291 286 if ( $search_query ) {
292 287 $data['search_query'] = sanitize_text_field( $search_query );
293 288 }
@@ -354,13 +349,9 @@
354 349 }
355 350 }
356 351
357 352 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 );
353 + $search_query = get_search_query();
363 354
364 355 if ( $search_query ) {
365 356 $data['search_query'] = sanitize_text_field( $search_query );
366 357 }
@@ -543,18 +534,9 @@
543 534 */
544 535 public static function pagination_data( $metas, $template ) {
545 536 $el_data = self::meta_dataset( $metas, $template );
546 537
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 - );
538 + return esc_js( wp_json_encode( $el_data, JSON_UNESCAPED_UNICODE ) );
557 539 }
558 540
559 541 /**
560 542 * Container classes
@@ -608,12 +590,8 @@
608 590 $action_btn_classes = [
609 591 'show_compare_text' => 'show-compare-text',
610 592 'show_quick_view_text' => 'show-quick-view-text',
611 593 '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',
616 594 'show_compare_icon' => 'no-compare-icon',
617 595 'show_quick_view_icon' => 'no-quick-view-icon',
618 596 'show_wishlist_icon' => 'no-wishlist-icon',
619 597 'show_quick_checkout_icon' => 'no-quick_checkout-icon',
@@ -659,53 +637,15 @@
659 637 if ( ! rtsb()->has_pro() && ! in_array( $layout, array_keys( Fns::free_layouts( $layout ) ), true ) ) {
660 638 $layout = self::set_default_layout( $layout );
661 639 }
662 640
663 - return self::resolve_existing_layout( $layout, $template );
641 + return $layout;
664 642 } else {
665 - return ! empty( $template ) ? self::resolve_existing_layout( self::set_default_layout( $layout ), $template ) : $default;
643 + return ! empty( $template ) ? self::set_default_layout( $layout ) : $default;
666 644 }
667 645 }
668 646
669 647 /**
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 648 * Row classes
709 649 *
710 650 * @param array $settings Settings array.
711 651 *
@@ -795,23 +735,9 @@
795 735 */
796 736 public static function archive_data( $metas, $template ) {
797 737 $el_data = self::archive_meta_dataset( $metas, $template );
798 738
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 ) );
739 + return ' data-rtsb-ajax=\'' . esc_js( wp_json_encode( $el_data ) ) . '\'';
814 740 }
815 741
816 742 /**
817 743 * Slider options.
@@ -1388,49 +1314,52 @@
1388 1314 // Archive-scope only when the URL path itself is a term archive
1389 1315 // (e.g. /product-category/clothing/). On the shop page WordPress can
1390 1316 // resolve ?product_cat=X to a queried term, but in that case we want
1391 1317 // 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;
1318 + $is_term_archive_url = self::request_path_is_term_archive( $taxonomy );
1393 1319
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
1320 + $cache_key = $is_term_archive_url
1321 + ? 'rtsb_get_terms_filter_products_archive_' . $taxonomy
1402 1322 : 'rtsb_get_terms_filter_products_full_' . $taxonomy;
1323 + $taxonomy_terms = wp_cache_get( $cache_key, 'shopbuilder' );
1403 1324
1404 - $taxonomy_terms = wp_cache_get( $cache_key, Cache::group() );
1325 + if ( false === $taxonomy_terms ) {
1326 + $archive_term = $is_term_archive_url ? self::resolve_archive_term_from_path( $taxonomy ) : null;
1405 1327
1406 - if ( false === $taxonomy_terms ) {
1407 1328 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 - ];
1329 + $term_id = $archive_term->term_id;
1330 + $archive_cache_key = 'rtsb_get_terms_filter_products_' . $taxonomy . $term_id;
1331 + $taxonomy_terms = wp_cache_get( $archive_cache_key, 'shopbuilder' );
1332 + if ( false === $taxonomy_terms ) {
1333 + // Treat the URL-derived term as the root of the visible
1334 + // hierarchy so the recursive renderer in
1335 + // get_product_default_filter_list_html() (which starts
1336 + // with $parent = 0) picks it up on child-category
1337 + // archives. Clone first to avoid mutating WP's term
1338 + // object.
1339 + $current_term = clone $archive_term;
1340 + $current_term->parent = 0;
1341 + $taxonomy_terms = [ $current_term ];
1342 + $child_args = [
1343 + 'taxonomy' => $taxonomy,
1344 + 'child_of' => $term_id,
1345 + 'hide_empty' => false,
1346 + ];
1422 1347
1423 - $child_terms = get_terms( $child_args );
1348 + $child_terms = get_terms( $child_args );
1424 1349
1425 - if ( ! empty( $child_terms ) && ! is_wp_error( $child_terms ) ) {
1426 - $taxonomy_terms = array_merge( $taxonomy_terms, $child_terms );
1350 + if ( ! empty( $child_terms ) ) {
1351 + $taxonomy_terms = array_merge( $taxonomy_terms, $child_terms );
1352 + }
1353 +
1354 + wp_cache_set( $archive_cache_key, $taxonomy_terms, 'shopbuilder' );
1355 + Cache::set_data_cache_key( $archive_cache_key );
1427 1356 }
1428 1357 } else {
1429 1358 $taxonomy_terms = get_terms( $args );
1430 1359 }
1431 1360
1432 - wp_cache_set( $cache_key, $taxonomy_terms, Cache::group(), 12 * HOUR_IN_SECONDS );
1361 + wp_cache_set( $cache_key, $taxonomy_terms, 'shopbuilder' );
1433 1362 Cache::set_data_cache_key( $cache_key );
1434 1363 }
1435 1364
1436 1365 return $taxonomy_terms;
@@ -1772,20 +1701,8 @@
1772 1701 $taxonomy = $tax->taxonomy;
1773 1702 $term_id = $tax->term_id;
1774 1703 }
1775 1704
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 1705 if ( strpos( $page->taxonomy, 'pa_' ) !== false ) {
1789 1706 $args['status'] = 'publish';
1790 1707 $args['limit'] = -1;
1791 1708 $args['return'] = 'ids';
@@ -1810,9 +1727,12 @@
1810 1727 'operator' => 'NOT IN',
1811 1728 ],
1812 1729 ];
1813 1730
1814 - return Fns::count_products_by_tax_query( $args['tax_query'] );
1731 + $query = new WC_Product_Query( $args );
1732 + $products = $query->get_products();
1733 +
1734 + return count( $products );
1815 1735 }
1816 1736
1817 1737 // Scope the count to the current archive term (category, brand, tag, etc.)
1818 1738 // so it reflects only products that are in this archive AND also have the
@@ -1843,9 +1763,12 @@
1843 1763 ],
1844 1764 ],
1845 1765 ];
1846 1766
1847 - return Fns::count_products_by_tax_query( $args['tax_query'] );
1767 + $query = new WC_Product_Query( $args );
1768 + $products = $query->get_products();
1769 +
1770 + return count( $products );
1848 1771 }
1849 1772
1850 1773 /**
1851 1774 * Get the count of visible (non-hidden) products for a given taxonomy term.
@@ -1886,9 +1809,10 @@
1886 1809 ],
1887 1810 ],
1888 1811 ];
1889 1812
1890 - $count = Fns::count_products_by_tax_query( $args['tax_query'] );
1813 + $query = new WC_Product_Query( $args );
1814 + $count = count( $query->get_products() );
1891 1815
1892 1816 $cache[ $cache_key ] = $count;
1893 1817
1894 1818 return $count;