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 +994 -38 2.1.33.2.6 View file →
@@ -8,10 +8,14 @@
8 8 */
9 9
10 10 namespace RadiusTheme\SB\Elementor\Helper;
11 11
12 +use Elementor\Plugin;
13 +use RadiusTheme\SB\Helpers\Cache;
14 +use RadiusTheme\SB\Helpers\Fns;
12 15 use RadiusTheme\SB\Helpers\BuilderFns;
13 -use RadiusTheme\SB\Helpers\Fns;
16 +use WC_Product_Query;
17 +use WP_Term;
14 18
15 19 // Do not allow directly accessing this file.
16 20 if ( ! defined( 'ABSPATH' ) ) {
17 21 exit( 'This script cannot be accessed directly.' );
@@ -21,8 +25,33 @@
21 25 * BuilderFns class
22 26 */
23 27 class RenderHelpers {
24 28 /**
29 + * @var array
30 + */
31 + private static $cache = [];
32 + /**
33 + * @param string $order_by value.
34 + * @return string
35 + */
36 + public static function escaping_product_orderby( $order_by ) {
37 + $catalog_orderby_options = apply_filters(
38 + 'woocommerce_catalog_orderby',
39 + [
40 + 'menu_order' => __( 'Default sorting', 'shopbuilder' ),
41 + 'popularity' => __( 'Sort by popularity', 'shopbuilder' ),
42 + 'rating' => __( 'Sort by average rating', 'shopbuilder' ),
43 + 'date' => __( 'Sort by latest', 'shopbuilder' ),
44 + 'price' => __( 'Sort by price: low to high', 'shopbuilder' ),
45 + 'price-desc' => __( 'Sort by price: high to low', 'shopbuilder' ),
46 + ]
47 + );
48 + if ( in_array( $order_by, array_keys( $catalog_orderby_options ), true ) ) {
49 + return $order_by;
50 + }
51 + return 'menu_order';
52 + }
53 + /**
25 54 * Get data.
26 55 *
27 56 * @param array|string $haystack The array or string to search for the value.
28 57 * @param string $needle The key to look for in the array or the string itself.
@@ -83,8 +112,9 @@
83 112 'order' => self::get_data( $meta, 'posts_order', 'DESC' ),
84 113 'author_in' => self::get_data( $meta, 'filter_author', [] ),
85 114 'display_by' => self::get_data( $meta, 'products_filter', 'date' ),
86 115 'categories' => self::get_data( $meta, 'filter_categories', [] ),
116 + 'brands' => self::get_data( $meta, 'filter_brands', [] ),
87 117 'tags' => self::get_data( $meta, 'filter_tags', [] ),
88 118 'attributes' => self::get_data( $meta, 'filter_attributes', [] ),
89 119 'relation' => self::get_data( $meta, 'tax_relation', 'OR' ),
90 120 'category_source' => self::get_data( $meta, 'select_source', 'product_cat' ),
@@ -178,8 +208,9 @@
178 208 'out_of_stock_badge_text' => self::get_data( $meta, 'stock_badges_text', esc_html__( 'Out of Stock', 'shopbuilder' ) ),
179 209 'badge_position' => self::get_data( $meta, 'badges_position', 'top' ),
180 210 'badge_alignment' => self::get_data( $meta, 'badges_alignment', 'left' ),
181 211 'badge_preset' => self::get_data( $meta, 'custom_badge_preset', 'preset-1' ),
212 + 'enable_badges_module' => self::get_data( $meta, 'enable_badges_module', '' ),
182 213
183 214 // Link.
184 215 'image_link' => ! empty( $meta['image_link'] ),
185 216 'title_link' => ! empty( $meta['title_link'] ),
@@ -195,8 +226,9 @@
195 226 'auto_play' => ! empty( $meta['slide_autoplay'] ),
196 227 'stop_on_hover' => ! empty( $meta['pause_hover'] ),
197 228 'slider_nav' => ! empty( $meta['slider_nav'] ),
198 229 'slider_dot' => ! empty( $meta['slider_pagi'] ),
230 + 'slider_dynamic_dot' => ! empty( $meta['slider_dynamic_pagi'] ),
199 231 'loop' => ! empty( $meta['slider_loop'] ),
200 232 'lazy_load' => ! empty( $meta['slider_lazy_load'] ),
201 233 'auto_height' => ! empty( $meta['slider_auto_height'] ),
202 234 'speed' => self::get_data( $meta, 'slide_speed', 2000 ),
@@ -246,8 +278,16 @@
246 278 $data['queried_term'] = esc_html( $queried_obj->term_id );
247 279 }
248 280 }
249 281
282 + if ( is_search() ) {
283 + $search_query = get_search_query();
284 +
285 + if ( $search_query ) {
286 + $data['search_query'] = sanitize_text_field( $search_query );
287 + }
288 + }
289 +
250 290 return apply_filters( 'rtsb/elementor/render/meta_dataset_final', $data, $meta, $raw_settings );
251 291 }
252 292
253 293 /**
@@ -299,8 +339,16 @@
299 339 $data['queried_term'] = esc_html( $queried_obj->term_id );
300 340 }
301 341 }
302 342
343 + if ( is_search() ) {
344 + $search_query = get_search_query();
345 +
346 + if ( $search_query ) {
347 + $data['search_query'] = sanitize_text_field( $search_query );
348 + }
349 + }
350 +
303 351 return apply_filters( 'rtsb/elementor/render/archive_meta_dataset_final', $data, $meta );
304 352 }
305 353
306 354 /**
@@ -339,9 +387,16 @@
339 387
340 388 if ( ! empty( $settings['single_category'] ) ) {
341 389 $visibility[] = 'single-cat';
342 390 }
391 + if ( ! empty( $settings['show_brands'] ) ) {
392 + $visibility[] = 'brands';
393 + }
343 394
395 + if ( ! empty( $settings['single_brand'] ) ) {
396 + $visibility[] = 'single-brand';
397 + }
398 +
344 399 if ( ! empty( $settings['show_swatches'] ) ) {
345 400 $visibility[] = 'swatches';
346 401 }
347 402
@@ -407,13 +462,23 @@
407 462 * @return int
408 463 */
409 464 public static function default_columns( $layout ) {
410 465 switch ( $layout ) {
466 + case 'rtsb-post-grid-layout1':
467 + case 'rtsb-post-grid-layout2':
468 + case 'rtsb-post-grid-layout3':
469 + case 'rtsb-team-layout1':
470 + case 'rtsb-team-layout2':
471 + case 'rtsb-testimonial-layout2':
411 472 case 'grid-layout2':
412 473 case 'slider-layout2':
474 + case 'rtsb-coupon-layout1':
475 + case 'rtsb-coupon-layout2':
476 + case 'rtsb-coupon-layout3':
413 477 $columns = 3;
414 478 break;
415 -
479 + case 'rtsb-post-list-layout1':
480 + case 'rtsb-post-list-layout2':
416 481 case 'list-layout1':
417 482 case 'list-layout2':
418 483 case 'list-layout3':
419 484 case 'list-layout7':
@@ -419,9 +484,13 @@
419 484 case 'list-layout7':
420 485 case 'list-layout5':
421 486 $columns = 1;
422 487 break;
423 -
488 + case 'rtsb-team-layout4':
489 + case 'rtsb-testimonial-layout1':
490 + case 'rtsb-testimonial-layout3':
491 + case 'rtsb-testimonial-layout4':
492 + case 'rtsb-testimonial-layout5':
424 493 case 'list-layout4':
425 494 case 'list-layout6':
426 495 case 'slider-layout5':
427 496 case 'slider-layout8':
@@ -426,10 +495,12 @@
426 495 case 'slider-layout5':
427 496 case 'slider-layout8':
428 497 $columns = 2;
429 498 break;
430 -
499 + case 'rtsb-logo-layout1':
500 + case 'rtsb-logo-layout2':
431 501 case 'grid-layout6':
502 + case 'grid-layout9':
432 503 $columns = 5;
433 504 break;
434 505
435 506 case 'category-layout1':
@@ -470,9 +541,10 @@
470 541 public static function prepare_container_classes( $metas = [], $settings = [], $custom_class = '' ) {
471 542 $classes = 'rtsb-elementor-container products rtsb-pos-r ';
472 543 $classes .= $custom_class;
473 544
474 - $layout = $metas['layout'];
545 + $raw_layout = esc_html( $metas['layout'] );
546 + $layout = self::filter_layout( $raw_layout );
475 547 $is_cat = preg_match( '/category/', $layout );
476 548 $cart_txt_class = '';
477 549
478 550 $animation = $metas['hover_animation'];
@@ -500,8 +572,11 @@
500 572
501 573 if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
502 574 $classes .= ' show-single-cat';
503 575 }
576 + if ( in_array( 'single-brand', $metas['visibility'], true ) ) {
577 + $classes .= ' show-single-brand';
578 + }
504 579
505 580 if ( ! $is_cat ) {
506 581 $action_btn_classes = [
507 582 'show_compare_text' => 'has-compare-text',
@@ -532,8 +607,36 @@
532 607 return apply_filters( 'rtsb/product/container/class', $classes, $settings );
533 608 }
534 609
535 610 /**
611 + * Function to filter and validate the layout against allowed patterns.
612 + *
613 + * @param string $layout The layout string to be filtered.
614 + * @param string $template The template to be checked.
615 + *
616 + * @return string
617 + */
618 + public static function filter_layout( $layout, $template = '' ) {
619 + $allowed_patterns = apply_filters( 'rtsb/elementor/custom_layout', [ 'grid', 'list', 'slider', 'category', 'toyup', 'zilly', 'rtsb' ] );
620 + $layout_part = explode( '-', $layout );
621 + $default = 'grid-layout1';
622 +
623 + if ( empty( $layout_part[0] ) ) {
624 + return $default;
625 + }
626 +
627 + if ( in_array( $layout_part[0], $allowed_patterns, true ) ) {
628 + if ( ! rtsb()->has_pro() && ! in_array( $layout, array_keys( Fns::free_layouts( $layout ) ), true ) ) {
629 + $layout = self::set_default_layout( $layout );
630 + }
631 +
632 + return $layout;
633 + } else {
634 + return ! empty( $template ) ? self::set_default_layout( $layout ) : $default;
635 + }
636 + }
637 +
638 + /**
536 639 * Row classes
537 640 *
538 641 * @param array $settings Settings array.
539 642 *
@@ -541,11 +644,15 @@
541 644 */
542 645 public static function prepare_row_classes( $settings = [] ) {
543 646 $masonry_class = null;
544 647 $classes = 'rtsb-row rtsb-content-loader element-loading';
545 - $loader_class = ' rtsb-pre-loader';
546 648
547 - $layout = $settings['layout'];
649 + $loader_class = '';
650 + if ( Fns::enable_loader() ) {
651 + $loader_class = ' rtsb-pre-loader';
652 + }
653 + $raw_layout = esc_html( $settings['layout'] );
654 + $layout = self::filter_layout( $raw_layout );
548 655 $grid_type = $settings['grid_type'];
549 656 $is_carousel = preg_match( '/slider/', $layout );
550 657
551 658 if ( preg_match( '/category/', $layout ) && ! empty( $settings['active_cat_slider'] ) ) {
@@ -551,12 +658,14 @@
551 658 if ( preg_match( '/category/', $layout ) && ! empty( $settings['active_cat_slider'] ) ) {
552 659 $classes .= ' rtsb-slider-layout';
553 660 }
554 661
555 - if ( 'even' === $grid_type ) {
556 - $masonry_class = ' rtsb-even';
662 + if ( 'equal' === $grid_type ) {
663 + $masonry_class = ' rtsb-equal';
557 664 } elseif ( 'masonry' === $grid_type ) {
558 665 $masonry_class = ' rtsb-masonry';
666 + } else {
667 + $masonry_class = ' rtsb-even';
559 668 }
560 669
561 670 if ( ! rtsb()->has_pro() || $is_carousel ) {
562 671 $masonry_class = ' rtsb-even';
@@ -561,8 +670,19 @@
561 670 if ( ! rtsb()->has_pro() || $is_carousel ) {
562 671 $masonry_class = ' rtsb-even';
563 672 }
564 673
674 + if ( $is_carousel && ! empty( $settings['raw_settings']['always_show_nav'] ) ) {
675 + $classes .= ' always-show-nav';
676 + }
677 +
678 + if ( ! empty( $settings['raw_settings']['inner_slider_always_show_nav'] ) ) {
679 + $classes .= ' inner-slider-always-show-nav';
680 + }
681 + if ( empty( $settings['raw_settings']['cols_mobile'] ) ) {
682 + $classes .= ' rtsb-mobile-flex-row';
683 + }
684 +
565 685 $classes .= ' rtsb-' . $layout . $masonry_class . $loader_class;
566 686
567 687 return apply_filters( 'rtsb/elementor/row/classes', $classes, $settings );
568 688 }
@@ -569,13 +689,13 @@
569 689
570 690 /**
571 691 * Prepare pagination attributes.
572 692 *
573 - * @param string $layout The layout type.
693 + * @param string $layout The layout type.
574 694 * @param bool $has_filters Whether the widget has filters.
575 - * @param string $template The template name.
576 - * @param array $settings The widget settings.
577 - * @param bool $ajax Whether to enable AJAX pagination.
695 + * @param string $template The template name.
696 + * @param array $settings The widget settings.
697 + * @param bool $ajax Whether to enable AJAX pagination.
578 698 *
579 699 * @return string
580 700 */
581 701 public static function prepare_pagination_attr( $layout, $has_filters, $template, $settings, $ajax ) {
@@ -587,9 +707,9 @@
587 707 } elseif ( $is_cat ) {
588 708 return '';
589 709 } else {
590 710 $ajax_pagination = ! empty( $settings['show_pagination'] ) && 'pagination' !== $settings['pagination_type'];
591 - $page = Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' );
711 + $page = Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) );
592 712 $condition = rtsb()->has_pro() && $ajax && ( $ajax_pagination || $has_filters || $page );
593 713
594 714 return $condition ? self::pagination_data( $settings, $template ) : '';
595 715 }
@@ -617,15 +737,16 @@
617 737 *
618 738 * @return array
619 739 */
620 740 public static function slider_data( array $meta, $settings = [] ) {
621 - $has_dots = $meta['slider_dot'] ? ' has-dot' : ' no-dot';
622 - $has_dots .= $meta['slider_nav'] ? ' has-nav' : ' no-nav';
623 - $d_col = 0 === $meta['d_cols'] ? self::default_columns( $meta['layout'] ) : $meta['d_cols'];
624 - $t_col = 0 === $meta['t_cols'] ? 2 : $meta['t_cols'];
625 - $m_col = 0 === $meta['m_cols'] ? 1 : $meta['m_cols'];
741 + $has_dots = $meta['slider_dot'] ? ' has-dot' : ' no-dot';
742 + $has_dots .= $meta['slider_nav'] ? ' has-nav' : ' no-nav';
743 + $has_dynamic_dots = $meta['slider_dynamic_dot'] ? true : false;
744 + $d_col = 0 === $meta['d_cols'] ? self::default_columns( $meta['layout'] ) : $meta['d_cols'];
745 + $t_col = 0 === $meta['t_cols'] ? 2 : $meta['t_cols'];
746 + $m_col = 0 === $meta['m_cols'] ? 1 : $meta['m_cols'];
626 747
627 - if ( \Elementor\Plugin::$instance->breakpoints->has_custom_breakpoints() ) {
748 + if ( Plugin::$instance->breakpoints->has_custom_breakpoints() ) {
628 749 // Widescreen.
629 750 $w_col = self::get_data( $settings, 'cols_widescreen' );
630 751 $w_col = 0 === $w_col ? self::default_columns( $meta['layout'] ) : $w_col;
631 752 $w_group = self::get_data( $settings, 'cols_group_widescreen', 1 );
@@ -656,10 +777,13 @@
656 777 $breakpoints = [
657 778 0 => [
658 779 'slidesPerView' => absint( $m_col ),
659 780 'slidesPerGroup' => absint( $meta['m_group'] ),
781 + 'grid' => [
782 + 'rows' => 1,
783 + ],
660 784 'pagination' => [
661 - 'dynamicBullets' => true,
785 + 'dynamicBullets' => $has_dynamic_dots,
662 786 ],
663 787 ],
664 788 ];
665 789
@@ -684,16 +808,16 @@
684 808 }
685 809
686 810 foreach ( $el_breakpoints as $el_breakpoint => $value ) {
687 811 $breakpoint_value = ! empty( $value['value'] ) ? $value['value'] : $value['default_value'];
688 - $dynamic_bullets = false;
812 + $dynamic_bullets = $has_dynamic_dots;
689 813 $slides_per_view = $d_col;
690 814 $slides_per_group = $meta['d_group'];
691 815
692 816 switch ( $el_breakpoint ) {
693 817 case 'widescreen':
694 - $slides_per_view = $w_col;
695 - $slides_per_group = $w_group;
818 + $slides_per_view = $w_col;
819 + $slides_per_group = $w_group;
696 820
697 821 break;
698 822
699 823 case 'laptop':
@@ -702,10 +826,10 @@
702 826
703 827 break;
704 828
705 829 case 'tablet_extra':
706 - $slides_per_view = $l_col;
707 - $slides_per_group = $l_group;
830 + $slides_per_view = $l_col;
831 + $slides_per_group = $l_group;
708 832
709 833 if ( empty( $el_breakpoints['laptop'] ) ) {
710 834 $slides_per_view = $d_col;
711 835 $slides_per_group = $meta['d_group'];
@@ -730,11 +854,11 @@
730 854
731 855 break;
732 856
733 857 case 'mobile_extra':
734 - $slides_per_view = $t_col;
735 - $slides_per_group = $meta['t_group'];
736 - $dynamic_bullets = true;
858 + $slides_per_view = $t_col;
859 + $slides_per_group = $meta['t_group'];
860 + $dynamic_bullets = $has_dynamic_dots;
737 861
738 862 break;
739 863
740 864 case 'mobile':
@@ -739,9 +863,9 @@
739 863
740 864 case 'mobile':
741 865 $slides_per_view = $t_col;
742 866 $slides_per_group = $meta['t_group'];
743 - $dynamic_bullets = true;
867 + $dynamic_bullets = $has_dynamic_dots;
744 868
745 869 if ( ! empty( $el_breakpoints['mobile_extra'] ) ) {
746 870 $slides_per_view = $me_col;
747 871 $slides_per_group = $me_group;
@@ -767,9 +891,9 @@
767 891 0 => [
768 892 'slidesPerView' => absint( $m_col ),
769 893 'slidesPerGroup' => absint( $meta['m_group'] ),
770 894 'pagination' => [
771 - 'dynamicBullets' => true,
895 + 'dynamicBullets' => $has_dynamic_dots,
772 896 ],
773 897 ],
774 898 768 => [
775 899 'slidesPerView' => absint( $t_col ),
@@ -774,9 +898,9 @@
774 898 768 => [
775 899 'slidesPerView' => absint( $t_col ),
776 900 'slidesPerGroup' => absint( $meta['t_group'] ),
777 901 'pagination' => [
778 - 'dynamicBullets' => false,
902 + 'dynamicBullets' => $has_dynamic_dots,
779 903 ],
780 904 ],
781 905 1025 => [
782 906 'slidesPerView' => absint( $d_col ),
@@ -781,9 +905,9 @@
781 905 1025 => [
782 906 'slidesPerView' => absint( $d_col ),
783 907 'slidesPerGroup' => absint( $meta['d_group'] ),
784 908 'pagination' => [
785 - 'dynamicBullets' => false,
909 + 'dynamicBullets' => $has_dynamic_dots,
786 910 ],
787 911 ],
788 912 ];
789 913 }
@@ -833,9 +957,11 @@
833 957
834 958 if ( $is_carousel ) {
835 959 $loader_class = ' full-op';
836 960 }
837 -
961 + if ( ! Fns::enable_loader() ) {
962 + return '';
963 + }
838 964 return '<div class="rtsb-elements-loading rtsb-ball-clip-rotate"><div></div></div>';
839 965 }
840 966
841 967 /**
@@ -876,10 +1002,11 @@
876 1002 *
877 1003 * @return void
878 1004 */
879 1005 public static function register_scripts( $scripts ) {
880 - $caro = false;
881 - $script = [];
1006 + $caro = false;
1007 + $masonry = false;
1008 + $script = [];
882 1009
883 1010 $script[] = 'jquery';
884 1011
885 1012 foreach ( $scripts as $sc => $value ) {
@@ -886,8 +1013,12 @@
886 1013 if ( ! empty( $sc ) ) {
887 1014 if ( 'isCarousel' === $sc ) {
888 1015 $caro = $value;
889 1016 }
1017 +
1018 + if ( 'isMasonry' === $sc ) {
1019 + $masonry = $value;
1020 + }
890 1021 }
891 1022 }
892 1023
893 1024 if ( count( $scripts ) ) {
@@ -897,11 +1028,15 @@
897 1028 if ( $caro ) {
898 1029 $script[] = 'swiper';
899 1030 }
900 1031
901 - $script[] = 'rtsb-imagesloaded';
902 - $script[] = 'rtsb-public';
1032 + if ( $masonry ) {
1033 + $script[] = 'masonry';
1034 + }
903 1035
1036 + $script[] = 'imagesloaded';
1037 + $script[] = Fns::optimized_handle( 'rtsb-public' );
1038 +
904 1039 foreach ( $script as $sc ) {
905 1040 wp_enqueue_script( $sc );
906 1041 }
907 1042 }
@@ -943,6 +1078,827 @@
943 1078 $products_col = absint( get_option( 'woocommerce_catalog_columns', 4 ) );
944 1079 $products_per_page = apply_filters( 'rtsb/elementor/archive/products_per_page', $products_row * $products_col );
945 1080
946 1081 return ! empty( $products_per_page ) ? $products_per_page : 12;
1082 + }
1083 + /**
1084 + * Render Filters View.
1085 + *
1086 + * @param array $templates Template name.
1087 + * @param array $settings Control settings.
1088 + *
1089 + * @return string
1090 + */
1091 + public static function filters_view( $templates, $settings ) {
1092 + global $wp;
1093 + $filters = [];
1094 + $html = null;
1095 + $reset = ! empty( $settings['reset_btn'] );
1096 + $reset_mode = 'show' === $settings['reset_btn_behavior'] ? ' show-reset' : ' ondemand-reset';
1097 + $reset_text = ! empty( $settings['reset_btn_text'] ) ? $settings['reset_btn_text'] : esc_html__( 'Reset Filter', 'shopbuilder' );
1098 + $scroll_mode = ! empty( $settings['enable_scroll'] ) ? ' has-scroll' : ' no-scroll';
1099 + $scroll_height = ! empty( $settings['enable_scroll'] ) ? $settings['scroll_height']['size'] : 300;
1100 + $has_mobile_toggle = ! empty( $settings['filter_mobile_toggle'] );
1101 + $toggle_class = $has_mobile_toggle ? ' default-filter-has-toggle' : ' default-filter-no-toggle';
1102 + $mobile_toggle_text = ! empty( $settings['filter_mobile_toggle_text'] ) ? $settings['filter_mobile_toggle_text'] : esc_html__( 'Click to Filter', 'shopbuilder' );
1103 + $scroll_attr = '';
1104 +
1105 + if ( ! empty( $settings['enable_scroll'] ) ) {
1106 + $scroll_attr = ' style="--rtsb-filter-scroll-height: ' . absint( $scroll_height ) . 'px;"';
1107 + }
1108 +
1109 + foreach ( $settings['filter_types'] as $key => $item ) {
1110 + if ( ! defined( 'RTWPVS_PLUGIN_FILE' ) && in_array( $item['input_type_all'], [ 'color', 'button', 'image' ], true ) ) {
1111 + $item['input_type_all'] = 'checkbox';
1112 + }
1113 + $filters[] = [
1114 + 'filter_title' => $item['filter_title'],
1115 + 'filter_items' => $item['filter_items'],
1116 + 'filter_attr' => $item['filter_attr'],
1117 + 'input_type' => 'product_attr' === $item['filter_items'] ? $item['input_type_all'] : $item['input_type'],
1118 + ];
1119 +
1120 + if ( 'rating_filter' === $item['filter_items'] ) {
1121 + $filters[ $key ]['template'] = $templates['rating'];
1122 + } elseif ( 'price_filter' === $item['filter_items'] ) {
1123 +
1124 + $filters[ $key ]['input_type'] = 'slider';
1125 + $filters[ $key ]['template'] = $templates['price'];
1126 + } else {
1127 + $filters[ $key ]['template'] = 'product_attr' === $item['filter_items'] ? $templates[ $item['input_type_all'] ] : $templates[ $item['input_type'] ];
1128 + }
1129 + }
1130 +
1131 + if ( '' === get_option( 'permalink_structure' ) ) {
1132 + $form_action = remove_query_arg( [ 'page', 'paged', 'product-page' ], add_query_arg( $wp->query_string, '', home_url( $wp->request ) ) );
1133 + } else {
1134 + $form_action = preg_replace( '%\/page/[0-9]+%', '', home_url( trailingslashit( $wp->request ) ) );
1135 + }
1136 + $args = [
1137 + 'filters' => $filters,
1138 + 'scroll_mode' => $scroll_mode,
1139 + 'reset_mode' => $reset_mode,
1140 + 'reset' => $reset,
1141 + 'scroll_attr' => $scroll_attr,
1142 + 'toggle_class' => $toggle_class,
1143 + 'reset_text' => $reset_text,
1144 + 'settings' => $settings,
1145 + 'form_action' => $form_action,
1146 + ];
1147 +
1148 + $html .= '<div class="rtsb-default-archive-filters' . esc_attr( $toggle_class ) . '">';
1149 +
1150 + if ( $has_mobile_toggle ) {
1151 + $html .= '<div class="rtsb-filter-mobile-toggle">
1152 + <button class="product-filter-toggle">
1153 + <span class="icon"><i aria-hidden="true" class="toggle-icon rtsb-icon rtsb-icon-filter"></i></span>
1154 + <span class="text reset-text">' . esc_html( $mobile_toggle_text ) . '</span>
1155 + <span></span>
1156 + </button>
1157 + </div>';
1158 + }
1159 +
1160 + $html .= Fns::load_template( $templates['layout'], $args, true );
1161 + $html .= '</div>';
1162 + return $html;
1163 + }
1164 + /**
1165 + * Get taxonomy type.
1166 + *
1167 + * @param string $tax_type Taxonomy type.
1168 + * @param string $attr_type Attribute type.
1169 + *
1170 + * @return string
1171 + */
1172 + public static function get_product_filters_tax_type( $tax_type, $attr_type = '' ) {
1173 + if ( 'product_attr' !== $tax_type ) {
1174 + return $tax_type;
1175 + }
1176 +
1177 + if ( empty( $attr_type ) ) {
1178 + return $tax_type;
1179 + }
1180 +
1181 + return $attr_type;
1182 + }
1183 + /**
1184 + * Get attribute terms.
1185 + *
1186 + * @param string $tax Taxonomy type.
1187 + * @return int[]|string|string[]|\WP_Error|\WP_Term[]
1188 + */
1189 + public static function get_product_filters_attribute_terms( $tax ) {
1190 + $cache_key = 'get_default_filter_attribute_terms_' . $tax;
1191 + if ( isset( self::$cache[ $cache_key ] ) ) {
1192 + return self::$cache[ $cache_key ];
1193 + }
1194 +
1195 + $attributes = wc_get_attribute_taxonomies();
1196 + $att_name = str_replace( 'pa_', '', $tax );
1197 + $exist = array_search( $att_name, array_column( $attributes, 'attribute_name' ), true );
1198 + if ( false === $exist ) {
1199 + self::$cache[ $cache_key ] = [];
1200 + return self::$cache[ $cache_key ];
1201 + }
1202 + $attribute = $tax;
1203 +
1204 + if ( ! is_shop() && is_product_taxonomy() ) {
1205 + $term = get_queried_object();
1206 +
1207 + if ( ! $term instanceof WP_Term ) {
1208 + return [];
1209 + }
1210 +
1211 + $args = [
1212 + 'status' => 'publish',
1213 + 'limit' => -1,
1214 + 'return' => 'ids',
1215 + ];
1216 +
1217 + if ( 'product_cat' === $term->taxonomy ) {
1218 + $args['category'] = [ $term->slug ];
1219 + }
1220 +
1221 + if ( 'product_tag' === $term->taxonomy ) {
1222 + $args['tag'] = [ $term->slug ];
1223 + }
1224 +
1225 + if ( strpos( $term->taxonomy, 'pa_' ) !== false ) {
1226 + $args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1227 + [
1228 + 'taxonomy' => $term->taxonomy,
1229 + 'field' => 'term_id',
1230 + 'terms' => $term->term_id,
1231 + 'operator' => 'IN',
1232 + ],
1233 + ];
1234 + }
1235 +
1236 + $query = new WC_Product_Query( $args );
1237 + $products = $query->get_products();
1238 + $attr_terms = [];
1239 +
1240 + foreach ( $products as $product_id ) {
1241 + $product_attr = wp_get_post_terms( $product_id, $attribute );
1242 +
1243 + foreach ( $product_attr as $attr ) {
1244 + $attr_terms[] = $attr;
1245 + }
1246 + }
1247 + self::$cache[ $cache_key ] = self::filter_products_array_unique( $attr_terms );
1248 + return self::$cache[ $cache_key ];
1249 + } else {
1250 + self::$cache[ $cache_key ] = get_terms(
1251 + [
1252 + 'taxonomy' => $attribute,
1253 + 'hide_empty' => false,
1254 + ]
1255 + );
1256 + return self::$cache[ $cache_key ];
1257 + }
1258 + }
1259 + /**
1260 + * Remove duplicate values from an array.
1261 + *
1262 + * @param array $array The input array.
1263 + * @param bool $keep_key_assoc Whether to keep the key associations after removing duplicates.
1264 + * @return array The array with duplicates removed.
1265 + */
1266 + public static function filter_products_array_unique( $array, $keep_key_assoc = false ) {
1267 + $duplicate_keys = [];
1268 + $tmp = [];
1269 +
1270 + foreach ( $array as $key => $val ) {
1271 + // convert objects to arrays, in_array() does not support objects.
1272 + if ( is_object( $val ) ) {
1273 + $val = (array) $val;
1274 + }
1275 +
1276 + if ( ! in_array( $val, $tmp, true ) ) {
1277 + $tmp[] = $val;
1278 + } else {
1279 + $duplicate_keys[] = $key;
1280 + }
1281 + }
1282 +
1283 + foreach ( $duplicate_keys as $key ) {
1284 + unset( $array[ $key ] );
1285 + }
1286 +
1287 + return $keep_key_assoc ? $array : array_values( $array );
1288 + }
1289 + /**
1290 + * Retrieve product categories (including hierarchical support)
1291 + *
1292 + * @param string $taxonomy Taxonomy name.
1293 + * @return int[]|string|string[]|\WP_Error|\WP_Term[]
1294 + */
1295 + public static function get_all_terms( $taxonomy ) {
1296 + $args = [
1297 + 'taxonomy' => $taxonomy,
1298 + 'suppress_filter' => true,
1299 + 'hide_empty' => false,
1300 + 'pad_counts' => true,
1301 + 'hierarchical' => true,
1302 + ];
1303 +
1304 + $cache_key = 'rtsb_get_terms_filter_products' . $taxonomy;
1305 + $taxonomy_terms = wp_cache_get( $cache_key, 'shopbuilder' );
1306 +
1307 + if ( false === $taxonomy_terms ) {
1308 + $queried_object = get_queried_object();
1309 +
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 + ];
1322 +
1323 + $child_terms = get_terms( $child_args );
1324 +
1325 + if ( ! empty( $child_terms ) ) {
1326 + $taxonomy_terms = array_merge( $taxonomy_terms, $child_terms );
1327 + }
1328 +
1329 + wp_cache_set( $archive_cache_key, $taxonomy_terms, 'shopbuilder' );
1330 + Cache::set_data_cache_key( $archive_cache_key );
1331 + }
1332 + } else {
1333 + $taxonomy_terms = get_terms( $args );
1334 + }
1335 +
1336 + wp_cache_set( $cache_key, $taxonomy_terms, 'shopbuilder' );
1337 + Cache::set_data_cache_key( $cache_key );
1338 + }
1339 +
1340 + return $taxonomy_terms;
1341 + }
1342 + /**
1343 + * Generates an error message block.
1344 + *
1345 + * @param array $title The title array of the error message block.
1346 + * @param string $wrapper Additional CSS class for the wrapper div.
1347 + *
1348 + * @return string The HTML representation of the error message block.
1349 + */
1350 + public static function filter_error_message( $title, $wrapper ) {
1351 + $html = '<div class="rtsb-product-default-filters ' . esc_attr( $wrapper ) . '">';
1352 + $html .= self::get_default_filter_title( $title );
1353 + $html .= '<div class="default-filter-content">';
1354 + $html .= '<p class="no-filter">' . esc_html__( 'Nothing found.', 'shopbuilder' ) . '</p>';
1355 + $html .= '</div>';
1356 + $html .= '</div>';
1357 +
1358 + return $html;
1359 + }
1360 + /**
1361 + * Get filter title.
1362 + *
1363 + * @param array $title Filter title.
1364 + * @return string
1365 + */
1366 + public static function get_default_filter_title( $title ) {
1367 + $html = '';
1368 +
1369 + if ( empty( $title['title'] ) ) {
1370 + return $html;
1371 + }
1372 +
1373 + $html .= '<div class="default-filter-title-wrapper">';
1374 +
1375 + $html .= '<h3 class="widget-title rtsb-d-flex rtsb-align-items-center"><span class="title">' . $title['title'] . '</span></h3>';
1376 +
1377 + $html .= '</div>';
1378 +
1379 + return apply_filters( 'rtsb/elementor/default/filter/title', $html, $title );
1380 + }
1381 + /**
1382 + * Recursive function to generate the filter list with hierarchy.
1383 + *
1384 + * @param array $data Array of required data.
1385 + * @param string $input Type of input field for the filter (e.g., checkbox).
1386 + * @param array $additional_data Data for additional filtering.
1387 + * @param int $parent ID of the parent term (default: 0).
1388 + *
1389 + * @return string The generated HTML for the product filter list.
1390 + */
1391 + public static function get_product_default_filter_list_html( $data, $input = 'checkbox', $additional_data = [], $parent = 0 ) {
1392 +
1393 + $html = '';
1394 + $default_tax = [];
1395 + $is_attribute = ! empty( $data['is_attribute'] );
1396 +
1397 + $param_key = isset( $additional_data['taxonomy'] ) ? $additional_data['taxonomy'] : '';
1398 + if ( $param_key && 0 === strpos( $param_key, 'pa_' ) ) {
1399 + $param_key = 'filter_' . substr( $param_key, 3 );
1400 + }
1401 +
1402 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1403 + $raw_values = ( $param_key && isset( $_GET[ $param_key ] ) ) ? wc_clean( wp_unslash( $_GET[ $param_key ] ) ) : '';
1404 + $active_option = $raw_values ? array_map( 'urldecode', explode( ',', $raw_values ) ) : [];
1405 + $counter = 0;
1406 +
1407 + if ( $is_attribute ) {
1408 + list(
1409 + 'filter_name' => $filter_name,
1410 + 'base_link' => $base_link
1411 + ) = self::get_product_filters_attribute_term_info( $additional_data['taxonomy'] );
1412 + }
1413 +
1414 + $html .= '<ul class="product-default-filters input-type-' . esc_attr( $input ) . '">';
1415 +
1416 + foreach ( $data['taxonomy'] as $tax ) {
1417 + if ( $tax->parent !== $parent ) {
1418 + continue;
1419 + }
1420 +
1421 + if ( $is_attribute ) {
1422 + $tax_link = remove_query_arg( $filter_name, $base_link );
1423 + $tax_link = add_query_arg( $filter_name, $tax->slug, $tax_link );
1424 + } else {
1425 + $tax_link = get_term_link( $tax );
1426 + }
1427 +
1428 + $unique_id = substr( uniqid(), -4 );
1429 +
1430 + $decoded_slug = urldecode( $tax->slug );
1431 + $is_active = in_array( $decoded_slug, $active_option, true ) || in_array( $tax->slug, $active_option, true );
1432 + $active_tax = $is_active ? ' checked' : '';
1433 + $active_tax_parent = $is_active ? ' active' : '';
1434 + $term_children = in_array( $tax->taxonomy, [ 'product_cat', 'product_brand' ], true ) ? get_term_children( $tax->term_id, $tax->taxonomy ) : [];
1435 + $taxonomy = $tax->taxonomy;
1436 + $product_count = self::get_visible_term_count( $tax->taxonomy, $tax->term_id );
1437 +
1438 + // Show count.
1439 + if ( 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) && ! empty( $data['count_display'] ) && 'block' === $data['count_display'] ) {
1440 + $product_count = self::count_tax_data( $tax, $product_count );
1441 + }
1442 +
1443 + // Show Empty Categories.
1444 + if ( empty( $data['show_empty_terms'] ) && 0 === $product_count ) {
1445 + continue;
1446 + }
1447 +
1448 + $html .= '<li class="rtsb-default-filter-term-item term-item-' . absint( $tax->term_id ) . esc_attr( $term_children ? ' term-has-children' : '' ) . ( ! $data['show_child'] ? ' child-terms-hidden' : '' ) . '">
1449 + <div class="rtsb-default-filter-group' . esc_attr( $active_tax_parent ) . '">
1450 + <input type="' . esc_attr( $input ) . '" class="rtsb-default-filter-trigger rtsb-' . esc_attr( $input . '-filter rtsb-term-' . $tax->slug . $active_tax ) . '" name="rtsb-filter-' . esc_attr( $taxonomy ) . '[]" id="rtsb-default-term-filter-' . $unique_id . absint( $tax->term_id ) . '" value="' . esc_attr( $tax->slug ) . '" ' . esc_attr( $active_tax ) . '>
1451 + <label class="rtsb-default-' . esc_attr( $input ) . '-filter-label" for="rtsb-default-term-filter-' . $unique_id . absint( $tax->term_id ) . '">
1452 + <span class="name">' . esc_html( $tax->name ) . '</span>
1453 + </label>
1454 + <div class="rtsb-product-count">(' . esc_html( $product_count ) . ')</div>
1455 + ' . ( ! empty( $term_children ) && $data['show_child'] ? '<i class="rtsb-plus-icon"></i>' : '' ) . '
1456 + </div>';
1457 +
1458 + // Show Sub-categories.
1459 + if ( $data['show_child'] ) {
1460 + $children = self::get_product_default_filter_list_html( $data, $input, $additional_data, $tax->term_id );
1461 +
1462 + if ( ! empty( $children ) ) {
1463 + $html .= '<div class="filter-child">' . $children . '</div>';
1464 + }
1465 + }
1466 +
1467 + $counter++;
1468 +
1469 + $html .= '</li>';
1470 + }
1471 +
1472 + $html .= '</ul>';
1473 +
1474 + // Check if the output contains any child elements.
1475 + $has_children = strpos( $html, '<li class="rtsb-default-filter-term-item' ) !== false;
1476 +
1477 + if ( ! $has_children ) {
1478 + // Remove the outer <ul> if there are no child elements.
1479 + $html = '';
1480 + }
1481 + return $html;
1482 + }
1483 + /**
1484 + * Get attribute term info.
1485 + *
1486 + * @param string $tax Taxonomy type.
1487 + * @return array
1488 + */
1489 + public static function get_product_filters_attribute_term_info( $tax ) {
1490 + $result = [];
1491 + $attributes = wc_get_attribute_taxonomies();
1492 +
1493 + foreach ( $attributes as $attr ) {
1494 + if ( str_replace( 'pa_', '', $tax ) === $attr->attribute_name ) {
1495 + $term_type = $attr->attribute_type;
1496 + $attribute_slug = $attr->attribute_name;
1497 + $attribute = wc_attribute_taxonomy_name( $attr->attribute_name );
1498 + break;
1499 + }
1500 + }
1501 +
1502 + $filter_name = 'filter_' . wc_attribute_taxonomy_slug( $attribute_slug );
1503 +
1504 + $result['attribute'] = $attribute ?? '';
1505 + $result['term_type'] = $term_type ?? '';
1506 + $result['attribute_slug'] = $attribute_slug ?? '';
1507 + $result['filter_name'] = $filter_name;
1508 + $result['base_link'] = self::get_base_url();
1509 +
1510 + return $result;
1511 + }
1512 + /**
1513 + * Get base URL.
1514 + *
1515 + * @return string|null
1516 + */
1517 + public static function get_base_url() {
1518 + global $wp;
1519 + return home_url( add_query_arg( [], $wp->request ) );
1520 + }
1521 + /**
1522 + * Count the number of occurrences for a specific term in a taxonomy for the current queried object.
1523 + *
1524 + * @param object|array $tax The term object for the taxonomy.
1525 + * @param int $count The initial count value.
1526 + *
1527 + * @return int The updated count value.
1528 + */
1529 + public static function count_tax_data( $tax, $count ) {
1530 + $page = get_queried_object();
1531 +
1532 + if ( is_array( $tax ) && strpos( $tax['taxonomy'], 'attribute_' ) !== false ) {
1533 + $taxonomy = str_replace( 'attribute_', '', $tax['taxonomy'] );
1534 + $term_id = $tax['term_id'];
1535 + } else {
1536 + $taxonomy = $tax->taxonomy;
1537 + $term_id = $tax->term_id;
1538 + }
1539 +
1540 + if ( strpos( $page->taxonomy, 'pa_' ) !== false ) {
1541 + $args['status'] = 'publish';
1542 + $args['limit'] = -1;
1543 + $args['return'] = 'ids';
1544 + $args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1545 + 'relation' => 'AND',
1546 + [
1547 + 'taxonomy' => $page->taxonomy,
1548 + 'field' => 'term_id',
1549 + 'terms' => $page->term_id,
1550 + 'operator' => 'IN',
1551 + ],
1552 + [
1553 + 'taxonomy' => $tax->taxonomy,
1554 + 'field' => 'term_id',
1555 + 'terms' => $tax->term_id,
1556 + 'operator' => 'IN',
1557 + ],
1558 + [
1559 + 'taxonomy' => 'product_visibility',
1560 + 'terms' => [ 'exclude-from-catalog' ],
1561 + 'field' => 'name',
1562 + 'operator' => 'NOT IN',
1563 + ],
1564 + ];
1565 +
1566 + $query = new WC_Product_Query( $args );
1567 + $products = $query->get_products();
1568 +
1569 + return count( $products );
1570 + }
1571 +
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 + ],
1590 + ],
1591 + ];
1592 +
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;
1608 + }
1609 +
1610 + /**
1611 + * Get the count of visible (non-hidden) products for a given taxonomy term.
1612 + *
1613 + * Excludes products with WooCommerce visibility set to 'Hidden'
1614 + * (products assigned the 'exclude-from-catalog' term).
1615 + *
1616 + * @param string $taxonomy The taxonomy name.
1617 + * @param int $term_id The term ID.
1618 + *
1619 + * @return int Visible product count.
1620 + */
1621 + public static function get_visible_term_count( $taxonomy, $term_id ) {
1622 + static $cache = [];
1623 +
1624 + $cache_key = $taxonomy . '_' . $term_id;
1625 +
1626 + if ( isset( $cache[ $cache_key ] ) ) {
1627 + return $cache[ $cache_key ];
1628 + }
1629 +
1630 + $args = [
1631 + 'limit' => -1,
1632 + 'return' => 'ids',
1633 + 'status' => 'publish',
1634 + 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1635 + 'relation' => 'AND',
1636 + [
1637 + 'taxonomy' => $taxonomy,
1638 + 'field' => 'term_id',
1639 + 'terms' => $term_id,
1640 + ],
1641 + [
1642 + 'taxonomy' => 'product_visibility',
1643 + 'terms' => [ 'exclude-from-catalog' ],
1644 + 'field' => 'name',
1645 + 'operator' => 'NOT IN',
1646 + ],
1647 + ],
1648 + ];
1649 +
1650 + $query = new WC_Product_Query( $args );
1651 + $count = count( $query->get_products() );
1652 +
1653 + $cache[ $cache_key ] = $count;
1654 +
1655 + return $count;
1656 + }
1657 +
1658 + /**
1659 + * Get filter HTML.
1660 + *
1661 + * @param string $filter_type Filter type.
1662 + * @param array $options Options.
1663 + * @param array $additional_data Data for additional filtering.
1664 + * @param string $input Input type.
1665 + *
1666 + * @return string The generated HTML for the attribute filter list.
1667 + */
1668 + public static function get_default_filter_html( $filter_type, $options, $additional_data = [], $input = 'checkbox' ) {
1669 + $html = '<ul class="product-filters input-type-' . esc_attr( $input ) . '" ';
1670 + $active_option = isset( $_GET[ $filter_type ] ) ? explode( ',', wc_clean( wp_unslash( $_GET[ $filter_type ] ) ) ) : []; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1671 + $option_link = self::get_base_url() . '/' . $filter_type . '/';
1672 +
1673 + foreach ( $options as $option => $option_name ) {
1674 + $unique_id = substr( uniqid(), -6 );
1675 + $active_tax = in_array( $option, $active_option, true ) ? ' checked' : '';
1676 + $active_group = in_array( $option, $active_option, true ) ? ' active' : '';
1677 +
1678 + $html .= '
1679 + <li class="rtsb-term-item">
1680 + <div class="rtsb-default-filter-group' . esc_attr( $active_group ) . '">
1681 + <input type="' . esc_attr( $input ) . '" class="rtsb-default-filter-trigger rtsb-' . esc_attr( $input ) . '-filter rtsb-term-' . esc_attr( $option ) . esc_attr( $active_tax ) . '" name="rtsb-filter-' . esc_attr( $filter_type ) . '[]" id="rtsb-term-default-filter-' . $unique_id . '" value="' . esc_attr( $option ) . '" ' . $active_tax . '>
1682 + <label class="rtsb-default-' . esc_attr( $input ) . '-filter-label" for="rtsb-term-default-filter-' . $unique_id . '">
1683 + <span class="name">' . esc_html( $option_name ) . '</span>
1684 + </label>
1685 + </div>
1686 + </li>';
1687 + }
1688 +
1689 + $html .= '</ul>';
1690 +
1691 + return $html;
1692 + }
1693 + /**
1694 + * Get the count of products with a specific star rating.
1695 + *
1696 + * @param int $star_rating The star rating value.
1697 + *
1698 + * @return int
1699 + */
1700 + public static function get_product_rating_count( $star_rating ) {
1701 + $args = [
1702 + 'status' => 'publish',
1703 + 'limit' => -1,
1704 + 'product_rating' => sprintf( '%.1f', $star_rating ),
1705 + ];
1706 +
1707 + if ( is_product_taxonomy() ) {
1708 + $term = get_queried_object();
1709 + $term_type = 'product_cat' === $term->taxonomy ? 'product_category_id' : 'product_tag_id';
1710 +
1711 + if ( $term instanceof WP_Term ) {
1712 + $args[ $term_type ] = [ $term->term_id ];
1713 + }
1714 + }
1715 +
1716 + $query = new WC_Product_Query( $args );
1717 + $products = $query->get_products();
1718 +
1719 + return count( $products );
1720 + }
1721 + /**
1722 + * Get attribute filter HTML.
1723 + *
1724 + * @param array $data Array of required data.
1725 + * @param string $input Type of input field for the filter (e.g., color).
1726 + *
1727 + * @return string The generated HTML for the attribute filter list.
1728 + */
1729 + public static function get_attribute_filter_list_html( $data, $input = 'color' ) {
1730 + if ( ! function_exists( 'rtwpvs' ) ) {
1731 + return '';
1732 + }
1733 +
1734 + $terms = $data['terms'];
1735 + $term_info = $data['term_info'];
1736 + $show_tooltips = $data['show_tooltips'];
1737 + $show_empty_terms = $data['show_empty_terms'];
1738 + $counter = 0;
1739 +
1740 + $html = '<ul class="product-default-filters rtsb-terms-wrapper ' . esc_attr( $term_info['type'] ) . '-variable-wrapper' . esc_attr( $data['show_label'] ) . '">';
1741 +
1742 + foreach ( $terms as $attr_term ) {
1743 + $count = self::get_visible_term_count( $attr_term->taxonomy, $attr_term->term_id );
1744 + $term_link = remove_query_arg( $term_info['filter_name'], $term_info['base_link'] );
1745 + $term_link = add_query_arg( $term_info['filter_name'], $attr_term->slug, $term_link );
1746 + $unique_id = substr( uniqid(), -6 );
1747 + $name = 'term-' . wc_variation_attribute_name( $term_info['attribute'] ) . '-' . $unique_id;
1748 + $decoded_slug = urldecode( $attr_term->slug );
1749 + $is_selected = in_array( $decoded_slug, $term_info['current_filter'], true ) || in_array( $attr_term->slug, $term_info['current_filter'], true );
1750 + $selected_class = $is_selected ? ' selected' : '';
1751 + $selected_item = $is_selected ? ' checked' : '';
1752 + $active_class = $is_selected ? ' active' : '';
1753 +
1754 + $args = [
1755 + 'id' => $name,
1756 + 'name' => $attr_term->name,
1757 + 'link' => $term_link,
1758 + 'type' => $term_info['type'],
1759 + 'term_id' => $attr_term->term_id,
1760 + 'term_slug' => $attr_term->slug,
1761 + 'taxonomy' => wc_variation_attribute_name( $term_info['attribute'] ),
1762 + 'tooltips' => $show_tooltips,
1763 + 'attribute_name' => $term_info['attribute'],
1764 + 'selected_item' => $selected_item,
1765 + ];
1766 +
1767 + if ( 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) && ! empty( $data['count_display'] ) && 'block' === $data['count_display'] ) {
1768 + $count = self::count_tax_data( $args, $count );
1769 + }
1770 +
1771 + $args['count'] = $count;
1772 +
1773 + if ( ! $show_empty_terms && 0 === $count ) {
1774 + continue;
1775 + }
1776 +
1777 + $html .= '<li class="rtsb-default-filter-term-item term-item-' . esc_attr( $attr_term->term_id ) . ' rtsb-term rtsb-default-' . esc_attr( $input ) . '-term ' . esc_attr( $term_info['type'] ) . '-variable-term-' . esc_attr( $attr_term->slug ) . esc_attr( $selected_class ) . '">';
1778 + $html .= '<div class="rtsb-default-filter-group' . esc_attr( $active_class ) . '">';
1779 + $html .= self::get_input_type_html( $input, $args );
1780 +
1781 + $counter++;
1782 +
1783 + $html .= '</div>';
1784 + $html .= '</li>';
1785 + }
1786 +
1787 + $html .= '</ul>';
1788 +
1789 + return $html;
1790 + }
1791 + /**
1792 + * Get input type HTML.
1793 + *
1794 + * @param string $input Input type.
1795 + * @param array $args Args.
1796 + * @return mixed|string|null
1797 + */
1798 + public static function get_input_type_html( $input, $args ) {
1799 + $html = '';
1800 + $count = $args['count'];
1801 +
1802 + switch ( $input ) {
1803 + case 'color':
1804 + $color = sanitize_hex_color( get_term_meta( $args['term_id'], 'product_attribute_color', true ) );
1805 +
1806 + $html .= sprintf(
1807 + // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
1808 + '<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-attr-filter rtsb-color-filter rtsb-term-span rtsb-term-span-%s%s"><span class="default-filter-attr-color" style="background-color:%s;"></span><span class="default-filter-attr-name">%s</span></label><div class="rtsb-count">(%s)</div>',
1809 + esc_attr( $args['id'] ),
1810 + esc_attr( $args['type'] ),
1811 + $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
1812 + esc_attr( $color ),
1813 + esc_html( $args['name'] ),
1814 + absint( $count )
1815 + );
1816 + break;
1817 +
1818 + case 'image':
1819 + if ( ! function_exists( 'rtwpvs' ) ) {
1820 + return $html;
1821 + }
1822 +
1823 + $attachment_id = absint( get_term_meta( $args['term_id'], 'product_attribute_image', true ) );
1824 + $image_size = rtwpvs()->get_option( 'attribute_image_size' );
1825 + $image_url = wp_get_attachment_image_url( $attachment_id, apply_filters( 'rtwpvs_product_attribute_image_size', $image_size ) );
1826 +
1827 + $html .= sprintf(
1828 + // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
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>',
1830 + esc_attr( $args['id'] ),
1831 + esc_attr( $args['type'] ),
1832 + $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
1833 + esc_attr( $args['name'] ),
1834 + esc_url( $image_url )
1835 + );
1836 + break;
1837 +
1838 + case 'button':
1839 + $html .= sprintf(
1840 + // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
1841 + '<input id="' . esc_attr( $args['id'] ) . '" type="checkbox" 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-attr-filter rtsb-button-filter rtsb-term-span rtsb-term-span-%s%s"><span class="default-filter-attr-name">%s</span><div class="rtsb-count">(%s)</div></label>',
1842 + esc_attr( $args['id'] ),
1843 + esc_attr( $args['type'] ),
1844 + $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
1845 + esc_html( $args['name'] ),
1846 + absint( $count )
1847 + );
1848 + break;
1849 +
1850 + default:
1851 + }
1852 +
1853 + return apply_filters( 'rtsb/elementor/default/filter/get_input_type_html', $html );
1854 + }
1855 + /**
1856 + * Get reset button.
1857 + *
1858 + * @param string $btn_text Button text.
1859 + * @param array $settings Settings array.
1860 + *
1861 + * @return string
1862 + */
1863 + public static function get_default_filter_reset_button( $btn_text, $settings ) {
1864 + $html = '';
1865 +
1866 + ob_start();
1867 +
1868 + $active = ! empty( $_SERVER['QUERY_STRING'] ) ? ' active' : '';
1869 + $reset = ! empty( $settings['reset_btn'] );
1870 + $apply_text = ! empty( $settings['apply_filters_btn_text'] ) ? $settings['apply_filters_btn_text'] : esc_html__( 'Apply Filters', 'shopbuilder' );
1871 + $apply_icon = ! empty( $settings['apply_filters_btn_icon'] ) ? $settings['apply_filters_btn_icon'] : [];
1872 + echo '<div class="default-filter-btn-wrapper">';
1873 + ?>
1874 + <?php if ( $settings['show_filter_btn'] ) { ?>
1875 + <div class="rtsb-product-default-filters rtsb-apply-filters-btn">
1876 + <div class="reset-btn-item">
1877 + <button class="rtsb-apply-filters init">
1878 + <span class="icon"><?php Fns::print_html( Fns::icons_manager( $apply_icon, 'icon-default' ) ); ?></span>
1879 + <span class="text reset-text"><?php echo esc_html( $apply_text ); ?></span>
1880 + <span></span>
1881 + </button>
1882 + </div>
1883 + </div>
1884 + <?php } ?>
1885 + <?php
1886 + if ( $reset ) {
1887 + ?>
1888 + <div class="rtsb-product-default-filters rtsb-reset<?php echo esc_attr( $active ); ?>">
1889 + <div class="reset-btn-item">
1890 + <button class="product-default-filter-reset">
1891 + <span class="text reset-text"><?php echo esc_html( $btn_text ); ?></span>
1892 + <span></span>
1893 + </button>
1894 + </div>
1895 + </div>
1896 + <?php
1897 + }
1898 + echo '</div>';
1899 +
1900 + $html .= ob_get_clean();
1901 +
1902 + return $html;
947 1903 }
948 1904 }