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 +1005 -42 2.1.23.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 ),
@@ -208,8 +240,12 @@
208 240 ],
209 241 $meta
210 242 );
211 243
244 + if ( ! empty( $meta['active_cat_slider'] ) ) {
245 + $data['active_cat_slider'] = true;
246 + }
247 +
212 248 if ( ! empty( $meta['cols_widescreen'] ) ) {
213 249 $data['w_cols'] = $meta['cols_widescreen'];
214 250 }
215 251
@@ -242,8 +278,16 @@
242 278 $data['queried_term'] = esc_html( $queried_obj->term_id );
243 279 }
244 280 }
245 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 +
246 290 return apply_filters( 'rtsb/elementor/render/meta_dataset_final', $data, $meta, $raw_settings );
247 291 }
248 292
249 293 /**
@@ -295,8 +339,16 @@
295 339 $data['queried_term'] = esc_html( $queried_obj->term_id );
296 340 }
297 341 }
298 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 +
299 351 return apply_filters( 'rtsb/elementor/render/archive_meta_dataset_final', $data, $meta );
300 352 }
301 353
302 354 /**
@@ -335,9 +387,16 @@
335 387
336 388 if ( ! empty( $settings['single_category'] ) ) {
337 389 $visibility[] = 'single-cat';
338 390 }
391 + if ( ! empty( $settings['show_brands'] ) ) {
392 + $visibility[] = 'brands';
393 + }
339 394
395 + if ( ! empty( $settings['single_brand'] ) ) {
396 + $visibility[] = 'single-brand';
397 + }
398 +
340 399 if ( ! empty( $settings['show_swatches'] ) ) {
341 400 $visibility[] = 'swatches';
342 401 }
343 402
@@ -403,13 +462,23 @@
403 462 * @return int
404 463 */
405 464 public static function default_columns( $layout ) {
406 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':
407 472 case 'grid-layout2':
408 473 case 'slider-layout2':
474 + case 'rtsb-coupon-layout1':
475 + case 'rtsb-coupon-layout2':
476 + case 'rtsb-coupon-layout3':
409 477 $columns = 3;
410 478 break;
411 -
479 + case 'rtsb-post-list-layout1':
480 + case 'rtsb-post-list-layout2':
412 481 case 'list-layout1':
413 482 case 'list-layout2':
414 483 case 'list-layout3':
415 484 case 'list-layout7':
@@ -415,9 +484,13 @@
415 484 case 'list-layout7':
416 485 case 'list-layout5':
417 486 $columns = 1;
418 487 break;
419 -
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':
420 493 case 'list-layout4':
421 494 case 'list-layout6':
422 495 case 'slider-layout5':
423 496 case 'slider-layout8':
@@ -422,10 +495,12 @@
422 495 case 'slider-layout5':
423 496 case 'slider-layout8':
424 497 $columns = 2;
425 498 break;
426 -
499 + case 'rtsb-logo-layout1':
500 + case 'rtsb-logo-layout2':
427 501 case 'grid-layout6':
502 + case 'grid-layout9':
428 503 $columns = 5;
429 504 break;
430 505
431 506 case 'category-layout1':
@@ -466,9 +541,10 @@
466 541 public static function prepare_container_classes( $metas = [], $settings = [], $custom_class = '' ) {
467 542 $classes = 'rtsb-elementor-container products rtsb-pos-r ';
468 543 $classes .= $custom_class;
469 544
470 - $layout = $metas['layout'];
545 + $raw_layout = esc_html( $metas['layout'] );
546 + $layout = self::filter_layout( $raw_layout );
471 547 $is_cat = preg_match( '/category/', $layout );
472 548 $cart_txt_class = '';
473 549
474 550 $animation = $metas['hover_animation'];
@@ -496,8 +572,11 @@
496 572
497 573 if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
498 574 $classes .= ' show-single-cat';
499 575 }
576 + if ( in_array( 'single-brand', $metas['visibility'], true ) ) {
577 + $classes .= ' show-single-brand';
578 + }
500 579
501 580 if ( ! $is_cat ) {
502 581 $action_btn_classes = [
503 582 'show_compare_text' => 'has-compare-text',
@@ -519,15 +598,42 @@
519 598
520 599 if ( rtsb()->has_pro() && ! empty( $settings['custom_ordering'] ) ) {
521 600 $classes .= ' has-custom-ordering';
522 601 }
602 + }
603 + if ( rtsb()->has_pro() && ! empty( $settings['slider_slide_animation'] ) ) {
604 + $classes .= ' has-slide-animation';
605 + }
523 606
524 - if ( rtsb()->has_pro() && ! empty( $settings['slider_slide_animation'] ) ) {
525 - $classes .= ' has-slide-animation';
607 + return apply_filters( 'rtsb/product/container/class', $classes, $settings );
608 + }
609 +
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 );
526 630 }
631 +
632 + return $layout;
633 + } else {
634 + return ! empty( $template ) ? self::set_default_layout( $layout ) : $default;
527 635 }
528 -
529 - return apply_filters( 'rtsb/product/container/class', $classes, $settings );
530 636 }
531 637
532 638 /**
533 639 * Row classes
@@ -538,18 +644,28 @@
538 644 */
539 645 public static function prepare_row_classes( $settings = [] ) {
540 646 $masonry_class = null;
541 647 $classes = 'rtsb-row rtsb-content-loader element-loading';
542 - $loader_class = ' rtsb-pre-loader';
543 648
544 - $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 );
545 655 $grid_type = $settings['grid_type'];
546 656 $is_carousel = preg_match( '/slider/', $layout );
547 657
548 - if ( 'even' === $grid_type ) {
549 - $masonry_class = ' rtsb-even';
658 + if ( preg_match( '/category/', $layout ) && ! empty( $settings['active_cat_slider'] ) ) {
659 + $classes .= ' rtsb-slider-layout';
660 + }
661 +
662 + if ( 'equal' === $grid_type ) {
663 + $masonry_class = ' rtsb-equal';
550 664 } elseif ( 'masonry' === $grid_type ) {
551 665 $masonry_class = ' rtsb-masonry';
666 + } else {
667 + $masonry_class = ' rtsb-even';
552 668 }
553 669
554 670 if ( ! rtsb()->has_pro() || $is_carousel ) {
555 671 $masonry_class = ' rtsb-even';
@@ -554,8 +670,19 @@
554 670 if ( ! rtsb()->has_pro() || $is_carousel ) {
555 671 $masonry_class = ' rtsb-even';
556 672 }
557 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 +
558 685 $classes .= ' rtsb-' . $layout . $masonry_class . $loader_class;
559 686
560 687 return apply_filters( 'rtsb/elementor/row/classes', $classes, $settings );
561 688 }
@@ -562,13 +689,13 @@
562 689
563 690 /**
564 691 * Prepare pagination attributes.
565 692 *
566 - * @param string $layout The layout type.
693 + * @param string $layout The layout type.
567 694 * @param bool $has_filters Whether the widget has filters.
568 - * @param string $template The template name.
569 - * @param array $settings The widget settings.
570 - * @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.
571 698 *
572 699 * @return string
573 700 */
574 701 public static function prepare_pagination_attr( $layout, $has_filters, $template, $settings, $ajax ) {
@@ -580,9 +707,9 @@
580 707 } elseif ( $is_cat ) {
581 708 return '';
582 709 } else {
583 710 $ajax_pagination = ! empty( $settings['show_pagination'] ) && 'pagination' !== $settings['pagination_type'];
584 - $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', '' ) );
585 712 $condition = rtsb()->has_pro() && $ajax && ( $ajax_pagination || $has_filters || $page );
586 713
587 714 return $condition ? self::pagination_data( $settings, $template ) : '';
588 715 }
@@ -610,15 +737,16 @@
610 737 *
611 738 * @return array
612 739 */
613 740 public static function slider_data( array $meta, $settings = [] ) {
614 - $has_dots = $meta['slider_dot'] ? ' has-dot' : ' no-dot';
615 - $has_dots .= $meta['slider_nav'] ? ' has-nav' : ' no-nav';
616 - $d_col = 0 === $meta['d_cols'] ? self::default_columns( $meta['layout'] ) : $meta['d_cols'];
617 - $t_col = 0 === $meta['t_cols'] ? 2 : $meta['t_cols'];
618 - $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'];
619 747
620 - if ( \Elementor\Plugin::$instance->breakpoints->has_custom_breakpoints() ) {
748 + if ( Plugin::$instance->breakpoints->has_custom_breakpoints() ) {
621 749 // Widescreen.
622 750 $w_col = self::get_data( $settings, 'cols_widescreen' );
623 751 $w_col = 0 === $w_col ? self::default_columns( $meta['layout'] ) : $w_col;
624 752 $w_group = self::get_data( $settings, 'cols_group_widescreen', 1 );
@@ -649,10 +777,13 @@
649 777 $breakpoints = [
650 778 0 => [
651 779 'slidesPerView' => absint( $m_col ),
652 780 'slidesPerGroup' => absint( $meta['m_group'] ),
781 + 'grid' => [
782 + 'rows' => 1,
783 + ],
653 784 'pagination' => [
654 - 'dynamicBullets' => true,
785 + 'dynamicBullets' => $has_dynamic_dots,
655 786 ],
656 787 ],
657 788 ];
658 789
@@ -677,16 +808,16 @@
677 808 }
678 809
679 810 foreach ( $el_breakpoints as $el_breakpoint => $value ) {
680 811 $breakpoint_value = ! empty( $value['value'] ) ? $value['value'] : $value['default_value'];
681 - $dynamic_bullets = false;
812 + $dynamic_bullets = $has_dynamic_dots;
682 813 $slides_per_view = $d_col;
683 814 $slides_per_group = $meta['d_group'];
684 815
685 816 switch ( $el_breakpoint ) {
686 817 case 'widescreen':
687 - $slides_per_view = $w_col;
688 - $slides_per_group = $w_group;
818 + $slides_per_view = $w_col;
819 + $slides_per_group = $w_group;
689 820
690 821 break;
691 822
692 823 case 'laptop':
@@ -695,10 +826,10 @@
695 826
696 827 break;
697 828
698 829 case 'tablet_extra':
699 - $slides_per_view = $l_col;
700 - $slides_per_group = $l_group;
830 + $slides_per_view = $l_col;
831 + $slides_per_group = $l_group;
701 832
702 833 if ( empty( $el_breakpoints['laptop'] ) ) {
703 834 $slides_per_view = $d_col;
704 835 $slides_per_group = $meta['d_group'];
@@ -723,11 +854,11 @@
723 854
724 855 break;
725 856
726 857 case 'mobile_extra':
727 - $slides_per_view = $t_col;
728 - $slides_per_group = $meta['t_group'];
729 - $dynamic_bullets = true;
858 + $slides_per_view = $t_col;
859 + $slides_per_group = $meta['t_group'];
860 + $dynamic_bullets = $has_dynamic_dots;
730 861
731 862 break;
732 863
733 864 case 'mobile':
@@ -732,9 +863,9 @@
732 863
733 864 case 'mobile':
734 865 $slides_per_view = $t_col;
735 866 $slides_per_group = $meta['t_group'];
736 - $dynamic_bullets = true;
867 + $dynamic_bullets = $has_dynamic_dots;
737 868
738 869 if ( ! empty( $el_breakpoints['mobile_extra'] ) ) {
739 870 $slides_per_view = $me_col;
740 871 $slides_per_group = $me_group;
@@ -760,9 +891,9 @@
760 891 0 => [
761 892 'slidesPerView' => absint( $m_col ),
762 893 'slidesPerGroup' => absint( $meta['m_group'] ),
763 894 'pagination' => [
764 - 'dynamicBullets' => true,
895 + 'dynamicBullets' => $has_dynamic_dots,
765 896 ],
766 897 ],
767 898 768 => [
768 899 'slidesPerView' => absint( $t_col ),
@@ -767,9 +898,9 @@
767 898 768 => [
768 899 'slidesPerView' => absint( $t_col ),
769 900 'slidesPerGroup' => absint( $meta['t_group'] ),
770 901 'pagination' => [
771 - 'dynamicBullets' => false,
902 + 'dynamicBullets' => $has_dynamic_dots,
772 903 ],
773 904 ],
774 905 1025 => [
775 906 'slidesPerView' => absint( $d_col ),
@@ -774,9 +905,9 @@
774 905 1025 => [
775 906 'slidesPerView' => absint( $d_col ),
776 907 'slidesPerGroup' => absint( $meta['d_group'] ),
777 908 'pagination' => [
778 - 'dynamicBullets' => false,
909 + 'dynamicBullets' => $has_dynamic_dots,
779 910 ],
780 911 ],
781 912 ];
782 913 }
@@ -826,9 +957,11 @@
826 957
827 958 if ( $is_carousel ) {
828 959 $loader_class = ' full-op';
829 960 }
830 -
961 + if ( ! Fns::enable_loader() ) {
962 + return '';
963 + }
831 964 return '<div class="rtsb-elements-loading rtsb-ball-clip-rotate"><div></div></div>';
832 965 }
833 966
834 967 /**
@@ -869,10 +1002,11 @@
869 1002 *
870 1003 * @return void
871 1004 */
872 1005 public static function register_scripts( $scripts ) {
873 - $caro = false;
874 - $script = [];
1006 + $caro = false;
1007 + $masonry = false;
1008 + $script = [];
875 1009
876 1010 $script[] = 'jquery';
877 1011
878 1012 foreach ( $scripts as $sc => $value ) {
@@ -879,8 +1013,12 @@
879 1013 if ( ! empty( $sc ) ) {
880 1014 if ( 'isCarousel' === $sc ) {
881 1015 $caro = $value;
882 1016 }
1017 +
1018 + if ( 'isMasonry' === $sc ) {
1019 + $masonry = $value;
1020 + }
883 1021 }
884 1022 }
885 1023
886 1024 if ( count( $scripts ) ) {
@@ -890,11 +1028,15 @@
890 1028 if ( $caro ) {
891 1029 $script[] = 'swiper';
892 1030 }
893 1031
894 - $script[] = 'rtsb-imagesloaded';
895 - $script[] = 'rtsb-public';
1032 + if ( $masonry ) {
1033 + $script[] = 'masonry';
1034 + }
896 1035
1036 + $script[] = 'imagesloaded';
1037 + $script[] = Fns::optimized_handle( 'rtsb-public' );
1038 +
897 1039 foreach ( $script as $sc ) {
898 1040 wp_enqueue_script( $sc );
899 1041 }
900 1042 }
@@ -936,6 +1078,827 @@
936 1078 $products_col = absint( get_option( 'woocommerce_catalog_columns', 4 ) );
937 1079 $products_per_page = apply_filters( 'rtsb/elementor/archive/products_per_page', $products_row * $products_col );
938 1080
939 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;
940 1903 }
941 1904 }