PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.1.3
ShopBuilder – WooCommerce Builder For Elementor v3.1.3
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 +826 -34 2.1.13 → 3.1.3 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,12 @@
21 25 * BuilderFns class
22 26 */
23 27 class RenderHelpers {
24 28 /**
29 + * @var array
30 + */
31 + private static $cache = [];
32 + /**
25 33 * Get data.
26 34 *
27 35 * @param array|string $haystack The array or string to search for the value.
28 36 * @param string $needle The key to look for in the array or the string itself.
@@ -83,8 +91,9 @@
83 91 'order' => self::get_data( $meta, 'posts_order', 'DESC' ),
84 92 'author_in' => self::get_data( $meta, 'filter_author', [] ),
85 93 'display_by' => self::get_data( $meta, 'products_filter', 'date' ),
86 94 'categories' => self::get_data( $meta, 'filter_categories', [] ),
95 + 'brands' => self::get_data( $meta, 'filter_brands', [] ),
87 96 'tags' => self::get_data( $meta, 'filter_tags', [] ),
88 97 'attributes' => self::get_data( $meta, 'filter_attributes', [] ),
89 98 'relation' => self::get_data( $meta, 'tax_relation', 'OR' ),
90 99 'category_source' => self::get_data( $meta, 'select_source', 'product_cat' ),
@@ -341,9 +350,16 @@
341 350
342 351 if ( ! empty( $settings['single_category'] ) ) {
343 352 $visibility[] = 'single-cat';
344 353 }
354 + if ( ! empty( $settings['show_brands'] ) ) {
355 + $visibility[] = 'brands';
356 + }
345 357
358 + if ( ! empty( $settings['single_brand'] ) ) {
359 + $visibility[] = 'single-brand';
360 + }
361 +
346 362 if ( ! empty( $settings['show_swatches'] ) ) {
347 363 $visibility[] = 'swatches';
348 364 }
349 365
@@ -409,13 +425,23 @@
409 425 * @return int
410 426 */
411 427 public static function default_columns( $layout ) {
412 428 switch ( $layout ) {
429 + case 'rtsb-post-grid-layout1':
430 + case 'rtsb-post-grid-layout2':
431 + case 'rtsb-post-grid-layout3':
432 + case 'rtsb-team-layout1':
433 + case 'rtsb-team-layout2':
434 + case 'rtsb-testimonial-layout2':
413 435 case 'grid-layout2':
414 436 case 'slider-layout2':
437 + case 'rtsb-coupon-layout1':
438 + case 'rtsb-coupon-layout2':
439 + case 'rtsb-coupon-layout3':
415 440 $columns = 3;
416 441 break;
417 -
442 + case 'rtsb-post-list-layout1':
443 + case 'rtsb-post-list-layout2':
418 444 case 'list-layout1':
419 445 case 'list-layout2':
420 446 case 'list-layout3':
421 447 case 'list-layout7':
@@ -421,9 +447,13 @@
421 447 case 'list-layout7':
422 448 case 'list-layout5':
423 449 $columns = 1;
424 450 break;
425 -
451 + case 'rtsb-team-layout4':
452 + case 'rtsb-testimonial-layout1':
453 + case 'rtsb-testimonial-layout3':
454 + case 'rtsb-testimonial-layout4':
455 + case 'rtsb-testimonial-layout5':
426 456 case 'list-layout4':
427 457 case 'list-layout6':
428 458 case 'slider-layout5':
429 459 case 'slider-layout8':
@@ -428,10 +458,12 @@
428 458 case 'slider-layout5':
429 459 case 'slider-layout8':
430 460 $columns = 2;
431 461 break;
432 -
462 + case 'rtsb-logo-layout1':
463 + case 'rtsb-logo-layout2':
433 464 case 'grid-layout6':
465 + case 'grid-layout9':
434 466 $columns = 5;
435 467 break;
436 468
437 469 case 'category-layout1':
@@ -503,8 +535,11 @@
503 535
504 536 if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
505 537 $classes .= ' show-single-cat';
506 538 }
539 + if ( in_array( 'single-brand', $metas['visibility'], true ) ) {
540 + $classes .= ' show-single-brand';
541 + }
507 542
508 543 if ( ! $is_cat ) {
509 544 $action_btn_classes = [
510 545 'show_compare_text' => 'has-compare-text',
@@ -543,9 +578,9 @@
543 578 *
544 579 * @return string
545 580 */
546 581 public static function filter_layout( $layout, $template = '' ) {
547 - $allowed_patterns = [ 'grid', 'list', 'slider', 'category' ];
582 + $allowed_patterns = apply_filters( 'rtsb/elementor/custom_layout', [ 'grid', 'list', 'slider', 'category', 'toyup', 'zilly', 'rtsb' ] );
548 583 $layout_part = explode( '-', $layout );
549 584 $default = 'grid-layout1';
550 585
551 586 if ( empty( $layout_part[0] ) ) {
@@ -552,30 +587,15 @@
552 587 return $default;
553 588 }
554 589
555 590 if ( in_array( $layout_part[0], $allowed_patterns, true ) ) {
591 + if ( ! rtsb()->has_pro() && ! in_array( $layout, array_keys( Fns::free_layouts( $layout ) ), true ) ) {
592 + $layout = self::set_default_layout( $layout );
593 + }
594 +
556 595 return $layout;
557 596 } else {
558 - if ( ! empty( $template ) ) {
559 - $grid = preg_match( '/grid/', $template );
560 - $list = preg_match( '/list/', $template );
561 - $slider = preg_match( '/slider/', $template );
562 - $category = preg_match( '/category/', $template );
563 -
564 - if ( $grid ) {
565 - return $default;
566 - } elseif ( $list ) {
567 - return 'list-layout1';
568 - } elseif ( $slider ) {
569 - return 'slider-layout1';
570 - } elseif ( $category ) {
571 - return 'category-layout1';
572 - } else {
573 - return $default;
574 - }
575 - } else {
576 - return $default;
577 - }
597 + return ! empty( $template ) ? self::set_default_layout( $layout ) : $default;
578 598 }
579 599 }
580 600
581 601 /**
@@ -587,10 +607,13 @@
587 607 */
588 608 public static function prepare_row_classes( $settings = [] ) {
589 609 $masonry_class = null;
590 610 $classes = 'rtsb-row rtsb-content-loader element-loading';
591 - $loader_class = ' rtsb-pre-loader';
592 611
612 + $loader_class = '';
613 + if ( Fns::enable_loader() ) {
614 + $loader_class = ' rtsb-pre-loader';
615 + }
593 616 $raw_layout = esc_html( $settings['layout'] );
594 617 $layout = self::filter_layout( $raw_layout );
595 618 $grid_type = $settings['grid_type'];
596 619 $is_carousel = preg_match( '/slider/', $layout );
@@ -598,23 +621,30 @@
598 621 if ( preg_match( '/category/', $layout ) && ! empty( $settings['active_cat_slider'] ) ) {
599 622 $classes .= ' rtsb-slider-layout';
600 623 }
601 624
602 - if ( 'even' === $grid_type ) {
603 - $masonry_class = ' rtsb-even';
625 + if ( 'equal' === $grid_type ) {
626 + $masonry_class = ' rtsb-equal';
604 627 } elseif ( 'masonry' === $grid_type ) {
605 628 $masonry_class = ' rtsb-masonry';
629 + } else {
630 + $masonry_class = ' rtsb-even';
606 631 }
607 632
608 633 if ( ! rtsb()->has_pro() || $is_carousel ) {
609 634 $masonry_class = ' rtsb-even';
610 635 }
636 +
611 637 if ( $is_carousel && ! empty( $settings['raw_settings']['always_show_nav'] ) ) {
612 638 $classes .= ' always-show-nav';
613 639 }
640 +
614 641 if ( ! empty( $settings['raw_settings']['inner_slider_always_show_nav'] ) ) {
615 642 $classes .= ' inner-slider-always-show-nav';
616 643 }
644 + if ( empty( $settings['raw_settings']['cols_mobile'] ) ) {
645 + $classes .= ' rtsb-mobile-flex-row';
646 + }
617 647
618 648 $classes .= ' rtsb-' . $layout . $masonry_class . $loader_class;
619 649
620 650 return apply_filters( 'rtsb/elementor/row/classes', $classes, $settings );
@@ -677,9 +707,9 @@
677 707 $d_col = 0 === $meta['d_cols'] ? self::default_columns( $meta['layout'] ) : $meta['d_cols'];
678 708 $t_col = 0 === $meta['t_cols'] ? 2 : $meta['t_cols'];
679 709 $m_col = 0 === $meta['m_cols'] ? 1 : $meta['m_cols'];
680 710
681 - if ( \Elementor\Plugin::$instance->breakpoints->has_custom_breakpoints() ) {
711 + if ( Plugin::$instance->breakpoints->has_custom_breakpoints() ) {
682 712 // Widescreen.
683 713 $w_col = self::get_data( $settings, 'cols_widescreen' );
684 714 $w_col = 0 === $w_col ? self::default_columns( $meta['layout'] ) : $w_col;
685 715 $w_group = self::get_data( $settings, 'cols_group_widescreen', 1 );
@@ -710,8 +740,11 @@
710 740 $breakpoints = [
711 741 0 => [
712 742 'slidesPerView' => absint( $m_col ),
713 743 'slidesPerGroup' => absint( $meta['m_group'] ),
744 + 'grid' => [
745 + 'rows' => 1,
746 + ],
714 747 'pagination' => [
715 748 'dynamicBullets' => $has_dynamic_dots,
716 749 ],
717 750 ],
@@ -887,9 +920,11 @@
887 920
888 921 if ( $is_carousel ) {
889 922 $loader_class = ' full-op';
890 923 }
891 -
924 + if ( ! Fns::enable_loader() ) {
925 + return '';
926 + }
892 927 return '<div class="rtsb-elements-loading rtsb-ball-clip-rotate"><div></div></div>';
893 928 }
894 929
895 930 /**
@@ -930,10 +965,11 @@
930 965 *
931 966 * @return void
932 967 */
933 968 public static function register_scripts( $scripts ) {
934 - $caro = false;
935 - $script = [];
969 + $caro = false;
970 + $masonry = false;
971 + $script = [];
936 972
937 973 $script[] = 'jquery';
938 974
939 975 foreach ( $scripts as $sc => $value ) {
@@ -940,8 +976,12 @@
940 976 if ( ! empty( $sc ) ) {
941 977 if ( 'isCarousel' === $sc ) {
942 978 $caro = $value;
943 979 }
980 +
981 + if ( 'isMasonry' === $sc ) {
982 + $masonry = $value;
983 + }
944 984 }
945 985 }
946 986
947 987 if ( count( $scripts ) ) {
@@ -951,11 +991,15 @@
951 991 if ( $caro ) {
952 992 $script[] = 'swiper';
953 993 }
954 994
955 - $script[] = 'rtsb-imagesloaded';
956 - $script[] = 'rtsb-public';
995 + if ( $masonry ) {
996 + $script[] = 'masonry';
997 + }
957 998
999 + $script[] = 'imagesloaded';
1000 + $script[] = Fns::optimized_handle( 'rtsb-public' );
1001 +
958 1002 foreach ( $script as $sc ) {
959 1003 wp_enqueue_script( $sc );
960 1004 }
961 1005 }
@@ -997,6 +1041,754 @@
997 1041 $products_col = absint( get_option( 'woocommerce_catalog_columns', 4 ) );
998 1042 $products_per_page = apply_filters( 'rtsb/elementor/archive/products_per_page', $products_row * $products_col );
999 1043
1000 1044 return ! empty( $products_per_page ) ? $products_per_page : 12;
1045 + }
1046 + /**
1047 + * Render Filters View.
1048 + *
1049 + * @param array $templates Template name.
1050 + * @param array $settings Control settings.
1051 + *
1052 + * @return string
1053 + */
1054 + public static function filters_view( $templates, $settings ) {
1055 + global $wp;
1056 + $filters = [];
1057 + $html = null;
1058 + $reset = ! empty( $settings['reset_btn'] );
1059 + $reset_mode = 'show' === $settings['reset_btn_behavior'] ? ' show-reset' : ' ondemand-reset';
1060 + $reset_text = ! empty( $settings['reset_btn_text'] ) ? $settings['reset_btn_text'] : esc_html__( 'Reset Filter', 'shopbuilder' );
1061 + $scroll_mode = ! empty( $settings['enable_scroll'] ) ? ' has-scroll' : ' no-scroll';
1062 + $scroll_height = ! empty( $settings['enable_scroll'] ) ? $settings['scroll_height']['size'] : 300;
1063 + $has_mobile_toggle = ! empty( $settings['filter_mobile_toggle'] );
1064 + $toggle_class = $has_mobile_toggle ? ' default-filter-has-toggle' : ' default-filter-no-toggle';
1065 + $mobile_toggle_text = ! empty( $settings['filter_mobile_toggle_text'] ) ? $settings['filter_mobile_toggle_text'] : esc_html__( 'Click to Filter', 'shopbuilder' );
1066 + $scroll_attr = '';
1067 +
1068 + if ( ! empty( $settings['enable_scroll'] ) ) {
1069 + $scroll_attr = ' style="--rtsb-filter-scroll-height: ' . absint( $scroll_height ) . 'px;"';
1070 + }
1071 +
1072 + foreach ( $settings['filter_types'] as $key => $item ) {
1073 + if ( ! defined( 'RTWPVS_PLUGIN_FILE' ) && in_array( $item['input_type_all'], [ 'color', 'button', 'image' ], true ) ) {
1074 + $item['input_type_all'] = 'checkbox';
1075 + }
1076 + $filters[] = [
1077 + 'filter_title' => $item['filter_title'],
1078 + 'filter_items' => $item['filter_items'],
1079 + 'filter_attr' => $item['filter_attr'],
1080 + 'input_type' => 'product_attr' === $item['filter_items'] ? $item['input_type_all'] : $item['input_type'],
1081 + ];
1082 +
1083 + if ( 'rating_filter' === $item['filter_items'] ) {
1084 + $filters[ $key ]['template'] = $templates['rating'];
1085 + } elseif ( 'price_filter' === $item['filter_items'] ) {
1086 +
1087 + $filters[ $key ]['input_type'] = 'slider';
1088 + $filters[ $key ]['template'] = $templates['price'];
1089 + } else {
1090 + $filters[ $key ]['template'] = 'product_attr' === $item['filter_items'] ? $templates[ $item['input_type_all'] ] : $templates[ $item['input_type'] ];
1091 + }
1092 + }
1093 +
1094 + if ( '' === get_option( 'permalink_structure' ) ) {
1095 + $form_action = remove_query_arg( [ 'page', 'paged', 'product-page' ], add_query_arg( $wp->query_string, '', home_url( $wp->request ) ) );
1096 + } else {
1097 + $form_action = preg_replace( '%\/page/[0-9]+%', '', home_url( trailingslashit( $wp->request ) ) );
1098 + }
1099 + $args = [
1100 + 'filters' => $filters,
1101 + 'scroll_mode' => $scroll_mode,
1102 + 'reset_mode' => $reset_mode,
1103 + 'reset' => $reset,
1104 + 'scroll_attr' => $scroll_attr,
1105 + 'toggle_class' => $toggle_class,
1106 + 'reset_text' => $reset_text,
1107 + 'settings' => $settings,
1108 + 'form_action' => $form_action,
1109 + ];
1110 +
1111 + $html .= '<div class="rtsb-default-archive-filters' . esc_attr( $toggle_class ) . '">';
1112 +
1113 + if ( $has_mobile_toggle ) {
1114 + $html .= '<div class="rtsb-filter-mobile-toggle">
1115 + <button class="product-filter-toggle">
1116 + <span class="icon"><i aria-hidden="true" class="toggle-icon rtsb-icon rtsb-icon-filter"></i></span>
1117 + <span class="text reset-text">' . esc_html( $mobile_toggle_text ) . '</span>
1118 + <span></span>
1119 + </button>
1120 + </div>';
1121 + }
1122 +
1123 + $html .= Fns::load_template( $templates['layout'], $args, true );
1124 + $html .= '</div>';
1125 + return $html;
1126 + }
1127 + /**
1128 + * Get taxonomy type.
1129 + *
1130 + * @param string $tax_type Taxonomy type.
1131 + * @param string $attr_type Attribute type.
1132 + *
1133 + * @return string
1134 + */
1135 + public static function get_product_filters_tax_type( $tax_type, $attr_type = '' ) {
1136 + if ( 'product_attr' !== $tax_type ) {
1137 + return $tax_type;
1138 + }
1139 +
1140 + if ( empty( $attr_type ) ) {
1141 + return $tax_type;
1142 + }
1143 +
1144 + return $attr_type;
1145 + }
1146 + /**
1147 + * Get attribute terms.
1148 + *
1149 + * @param string $tax Taxonomy type.
1150 + * @return int[]|string|string[]|\WP_Error|\WP_Term[]
1151 + */
1152 + public static function get_product_filters_attribute_terms( $tax ) {
1153 + $cache_key = 'get_default_filter_attribute_terms_' . $tax;
1154 + if ( isset( self::$cache[ $cache_key ] ) ) {
1155 + return self::$cache[ $cache_key ];
1156 + }
1157 +
1158 + $attributes = wc_get_attribute_taxonomies();
1159 + $att_name = str_replace( 'pa_', '', $tax );
1160 + $exist = array_search( $att_name, array_column( $attributes, 'attribute_name' ), true );
1161 + if ( false === $exist ) {
1162 + self::$cache[ $cache_key ] = [];
1163 + return self::$cache[ $cache_key ];
1164 + }
1165 + $attribute = $tax;
1166 +
1167 + if ( ! is_shop() && is_product_taxonomy() ) {
1168 + $term = get_queried_object();
1169 +
1170 + if ( ! $term instanceof WP_Term ) {
1171 + return [];
1172 + }
1173 +
1174 + $args = [
1175 + 'status' => 'publish',
1176 + 'limit' => -1,
1177 + 'return' => 'ids',
1178 + ];
1179 +
1180 + if ( 'product_cat' === $term->taxonomy ) {
1181 + $args['category'] = [ $term->slug ];
1182 + }
1183 +
1184 + if ( 'product_tag' === $term->taxonomy ) {
1185 + $args['tag'] = [ $term->slug ];
1186 + }
1187 +
1188 + if ( strpos( $term->taxonomy, 'pa_' ) !== false ) {
1189 + $args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1190 + [
1191 + 'taxonomy' => $term->taxonomy,
1192 + 'field' => 'term_id',
1193 + 'terms' => $term->term_id,
1194 + 'operator' => 'IN',
1195 + ],
1196 + ];
1197 + }
1198 +
1199 + $query = new WC_Product_Query( $args );
1200 + $products = $query->get_products();
1201 + $attr_terms = [];
1202 +
1203 + foreach ( $products as $product_id ) {
1204 + $product_attr = wp_get_post_terms( $product_id, $attribute );
1205 +
1206 + foreach ( $product_attr as $attr ) {
1207 + $attr_terms[] = $attr;
1208 + }
1209 + }
1210 + self::$cache[ $cache_key ] = self::filter_products_array_unique( $attr_terms );
1211 + return self::$cache[ $cache_key ];
1212 + } else {
1213 + self::$cache[ $cache_key ] = get_terms(
1214 + [
1215 + 'taxonomy' => $attribute,
1216 + 'hide_empty' => false,
1217 + ]
1218 + );
1219 + return self::$cache[ $cache_key ];
1220 + }
1221 + }
1222 + /**
1223 + * Remove duplicate values from an array.
1224 + *
1225 + * @param array $array The input array.
1226 + * @param bool $keep_key_assoc Whether to keep the key associations after removing duplicates.
1227 + * @return array The array with duplicates removed.
1228 + */
1229 + public static function filter_products_array_unique( $array, $keep_key_assoc = false ) {
1230 + $duplicate_keys = [];
1231 + $tmp = [];
1232 +
1233 + foreach ( $array as $key => $val ) {
1234 + // convert objects to arrays, in_array() does not support objects.
1235 + if ( is_object( $val ) ) {
1236 + $val = (array) $val;
1237 + }
1238 +
1239 + if ( ! in_array( $val, $tmp, true ) ) {
1240 + $tmp[] = $val;
1241 + } else {
1242 + $duplicate_keys[] = $key;
1243 + }
1244 + }
1245 +
1246 + foreach ( $duplicate_keys as $key ) {
1247 + unset( $array[ $key ] );
1248 + }
1249 +
1250 + return $keep_key_assoc ? $array : array_values( $array );
1251 + }
1252 + /**
1253 + * Retrieve product categories (including hierarchical support)
1254 + *
1255 + * @param string $taxonomy Taxonomy name.
1256 + * @return int[]|string|string[]|\WP_Error|\WP_Term[]
1257 + */
1258 + public static function get_all_terms( $taxonomy ) {
1259 + $args = [
1260 + 'taxonomy' => $taxonomy,
1261 + 'suppress_filter' => true,
1262 + 'hide_empty' => false,
1263 + 'pad_counts' => true,
1264 + 'hierarchical' => true,
1265 + ];
1266 +
1267 + $cache_key = 'rtsb_get_terms_filter_products' . $taxonomy;
1268 + $taxonomy_terms = wp_cache_get( $cache_key, 'shopbuilder' );
1269 +
1270 + if ( false === $taxonomy_terms ) {
1271 + $queried_object = get_queried_object();
1272 +
1273 + if ( 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) && $queried_object instanceof WP_Term && $queried_object->taxonomy === $taxonomy ) {
1274 + $term_id = $queried_object->term_id;
1275 + $taxonomy = $queried_object->taxonomy;
1276 + $archive_cache_key = 'rtsb_get_terms_filter_products_' . $taxonomy . $term_id;
1277 + $taxonomy_terms = wp_cache_get( $archive_cache_key, 'shopbuilder' );
1278 + if ( false === $taxonomy_terms ) {
1279 + $taxonomy_terms = [ $queried_object ];
1280 + $child_args = [
1281 + 'taxonomy' => $taxonomy,
1282 + 'child_of' => $term_id,
1283 + 'hide_empty' => false,
1284 + ];
1285 +
1286 + $child_terms = get_terms( $child_args );
1287 +
1288 + if ( ! empty( $child_terms ) ) {
1289 + $taxonomy_terms = array_merge( $taxonomy_terms, $child_terms );
1290 + }
1291 +
1292 + wp_cache_set( $archive_cache_key, $taxonomy_terms, 'shopbuilder' );
1293 + Cache::set_data_cache_key( $archive_cache_key );
1294 + }
1295 + } else {
1296 + $taxonomy_terms = get_terms( $args );
1297 + }
1298 +
1299 + wp_cache_set( $cache_key, $taxonomy_terms, 'shopbuilder' );
1300 + Cache::set_data_cache_key( $cache_key );
1301 + }
1302 +
1303 + return $taxonomy_terms;
1304 + }
1305 + /**
1306 + * Generates an error message block.
1307 + *
1308 + * @param array $title The title array of the error message block.
1309 + * @param string $wrapper Additional CSS class for the wrapper div.
1310 + *
1311 + * @return string The HTML representation of the error message block.
1312 + */
1313 + public static function filter_error_message( $title, $wrapper ) {
1314 + $html = '<div class="rtsb-product-default-filters ' . esc_attr( $wrapper ) . '">';
1315 + $html .= self::get_default_filter_title( $title );
1316 + $html .= '<div class="default-filter-content">';
1317 + $html .= '<p class="no-filter">' . esc_html__( 'Nothing found.', 'shopbuilder' ) . '</p>';
1318 + $html .= '</div>';
1319 + $html .= '</div>';
1320 +
1321 + return $html;
1322 + }
1323 + /**
1324 + * Get filter title.
1325 + *
1326 + * @param array $title Filter title.
1327 + * @return string
1328 + */
1329 + public static function get_default_filter_title( $title ) {
1330 + $html = '';
1331 +
1332 + if ( empty( $title['title'] ) ) {
1333 + return $html;
1334 + }
1335 +
1336 + $html .= '<div class="default-filter-title-wrapper">';
1337 +
1338 + $html .= '<h3 class="widget-title rtsb-d-flex rtsb-align-items-center"><span class="title">' . $title['title'] . '</span></h3>';
1339 +
1340 + $html .= '</div>';
1341 +
1342 + return apply_filters( 'rtsb/elementor/default/filter/title', $html, $title );
1343 + }
1344 + /**
1345 + * Recursive function to generate the filter list with hierarchy.
1346 + *
1347 + * @param array $data Array of required data.
1348 + * @param string $input Type of input field for the filter (e.g., checkbox).
1349 + * @param array $additional_data Data for additional filtering.
1350 + * @param int $parent ID of the parent term (default: 0).
1351 + *
1352 + * @return string The generated HTML for the product filter list.
1353 + */
1354 + public static function get_product_default_filter_list_html( $data, $input = 'checkbox', $additional_data = [], $parent = 0 ) {
1355 +
1356 + $html = '';
1357 + $default_tax = [];
1358 + $is_attribute = ! empty( $data['is_attribute'] );
1359 + $active_option = isset( $_GET[ $additional_data['taxonomy'] ] ) ? explode( ',', wc_clean( wp_unslash( $_GET[ $additional_data['taxonomy'] ] ) ) ) : []; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1360 + $counter = 0;
1361 +
1362 + if ( $is_attribute ) {
1363 + list(
1364 + 'filter_name' => $filter_name,
1365 + 'base_link' => $base_link
1366 + ) = self::get_product_filters_attribute_term_info( $additional_data['taxonomy'] );
1367 + }
1368 +
1369 + $html .= '<ul class="product-default-filters input-type-' . esc_attr( $input ) . '">';
1370 +
1371 + foreach ( $data['taxonomy'] as $tax ) {
1372 + if ( $tax->parent !== $parent ) {
1373 + continue;
1374 + }
1375 +
1376 + if ( $is_attribute ) {
1377 + $tax_link = remove_query_arg( $filter_name, $base_link );
1378 + $tax_link = add_query_arg( $filter_name, $tax->slug, $tax_link );
1379 + $active_option = isset( $_GET[ $filter_name ] ) ? explode( ',', sanitize_text_field( wp_unslash( $_GET[ $filter_name ] ) ) ) : []; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1380 + } else {
1381 + $tax_link = get_term_link( $tax );
1382 + }
1383 +
1384 + $unique_id = substr( uniqid(), -4 );
1385 +
1386 + $active_tax = in_array( $tax->slug, $active_option, true ) ? ' checked' : '';
1387 + $active_tax_parent = in_array( $tax->slug, $active_option, true ) ? ' active' : '';
1388 + $term_children = in_array( $tax->taxonomy, [ 'product_cat', 'product_brand' ], true ) ? get_term_children( $tax->term_id, $tax->taxonomy ) : [];
1389 + $taxonomy = $tax->taxonomy;
1390 + $product_count = $tax->count;
1391 +
1392 + // Show count.
1393 + if ( 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) && ! empty( $data['count_display'] ) && 'block' === $data['count_display'] ) {
1394 + $product_count = self::count_tax_data( $tax, $tax->count );
1395 + }
1396 +
1397 + // Show Empty Categories.
1398 + if ( empty( $data['show_empty_terms'] ) && 0 === $product_count ) {
1399 + continue;
1400 + }
1401 +
1402 + $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' : '' ) . '">
1403 + <div class="rtsb-default-filter-group' . esc_attr( $active_tax_parent ) . '">
1404 + <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 ) . '>
1405 + <label class="rtsb-default-' . esc_attr( $input ) . '-filter-label" for="rtsb-default-term-filter-' . $unique_id . absint( $tax->term_id ) . '">
1406 + <span class="name">' . esc_html( $tax->name ) . '</span>
1407 + </label>
1408 + <div class="rtsb-product-count">(' . esc_html( $product_count ) . ')</div>
1409 + ' . ( ! empty( $term_children ) && $data['show_child'] ? '<i class="rtsb-plus-icon"></i>' : '' ) . '
1410 + </div>';
1411 +
1412 + // Show Sub-categories.
1413 + if ( $data['show_child'] ) {
1414 + $children = self::get_product_default_filter_list_html( $data, $input, $additional_data, $tax->term_id );
1415 +
1416 + if ( ! empty( $children ) ) {
1417 + $html .= '<div class="filter-child">' . $children . '</div>';
1418 + }
1419 + }
1420 +
1421 + $counter++;
1422 +
1423 + $html .= '</li>';
1424 + }
1425 +
1426 + $html .= '</ul>';
1427 +
1428 + // Check if the output contains any child elements.
1429 + $has_children = strpos( $html, '<li class="rtsb-default-filter-term-item' ) !== false;
1430 +
1431 + if ( ! $has_children ) {
1432 + // Remove the outer <ul> if there are no child elements.
1433 + $html = '';
1434 + }
1435 + return $html;
1436 + }
1437 + /**
1438 + * Get attribute term info.
1439 + *
1440 + * @param string $tax Taxonomy type.
1441 + * @return array
1442 + */
1443 + public static function get_product_filters_attribute_term_info( $tax ) {
1444 + $result = [];
1445 + $attributes = wc_get_attribute_taxonomies();
1446 +
1447 + foreach ( $attributes as $attr ) {
1448 + if ( str_replace( 'pa_', '', $tax ) === $attr->attribute_name ) {
1449 + $term_type = $attr->attribute_type;
1450 + $attribute_slug = $attr->attribute_name;
1451 + $attribute = wc_attribute_taxonomy_name( $attr->attribute_name );
1452 + break;
1453 + }
1454 + }
1455 +
1456 + $filter_name = 'filter_' . wc_attribute_taxonomy_slug( $attribute_slug );
1457 +
1458 + $result['attribute'] = $attribute ?? '';
1459 + $result['term_type'] = $term_type ?? '';
1460 + $result['attribute_slug'] = $attribute_slug ?? '';
1461 + $result['filter_name'] = $filter_name;
1462 + $result['base_link'] = self::get_base_url();
1463 +
1464 + return $result;
1465 + }
1466 + /**
1467 + * Get base URL.
1468 + *
1469 + * @return string|null
1470 + */
1471 + public static function get_base_url() {
1472 + global $wp;
1473 + return home_url( add_query_arg( [], $wp->request ) );
1474 + }
1475 + /**
1476 + * Count the number of occurrences for a specific term in a taxonomy for the current queried object.
1477 + *
1478 + * @param object|array $tax The term object for the taxonomy.
1479 + * @param int $count The initial count value.
1480 + *
1481 + * @return int The updated count value.
1482 + */
1483 + public static function count_tax_data( $tax, $count ) {
1484 + $page = get_queried_object();
1485 +
1486 + if ( is_array( $tax ) && strpos( $tax['taxonomy'], 'attribute_' ) !== false ) {
1487 + $taxonomy = str_replace( 'attribute_', '', $tax['taxonomy'] );
1488 + $term_id = $tax['term_id'];
1489 + } else {
1490 + $taxonomy = $tax->taxonomy;
1491 + $term_id = $tax->term_id;
1492 + }
1493 +
1494 + if ( strpos( $page->taxonomy, 'pa_' ) !== false ) {
1495 + $args['status'] = 'publish';
1496 + $args['limit'] = -1;
1497 + $args['return'] = 'ids';
1498 + $args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1499 + 'relation' => 'AND',
1500 + [
1501 + 'taxonomy' => $page->taxonomy,
1502 + 'field' => 'term_id',
1503 + 'terms' => $page->term_id,
1504 + 'operator' => 'IN',
1505 + ],
1506 + [
1507 + 'taxonomy' => $tax->taxonomy,
1508 + 'field' => 'term_id',
1509 + 'terms' => $tax->term_id,
1510 + 'operator' => 'IN',
1511 + ],
1512 + ];
1513 +
1514 + $query = new WC_Product_Query( $args );
1515 + $products = $query->get_products();
1516 +
1517 + return count( $products );
1518 + }
1519 +
1520 + if ( $taxonomy !== $page->taxonomy ) {
1521 + $args = [
1522 + 'limit' => -1,
1523 + 'return' => 'ids',
1524 + 'status' => 'publish',
1525 + 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1526 + [
1527 + 'taxonomy' => $taxonomy,
1528 + 'field' => 'term_id',
1529 + 'terms' => $term_id,
1530 + ],
1531 + ],
1532 + ];
1533 +
1534 + if ( 'product_cat' === $page->taxonomy ) {
1535 + $args['category'] = [ $page->slug ];
1536 + }
1537 +
1538 + if ( 'product_tag' === $page->taxonomy ) {
1539 + $args['tag'] = [ $page->slug ];
1540 + }
1541 +
1542 + $query = new WC_Product_Query( $args );
1543 + $products = $query->get_products();
1544 +
1545 + $count = count( $products );
1546 + }
1547 +
1548 + return $count;
1549 + }
1550 + /**
1551 + * Get filter HTML.
1552 + *
1553 + * @param string $filter_type Filter type.
1554 + * @param array $options Options.
1555 + * @param array $additional_data Data for additional filtering.
1556 + * @param string $input Input type.
1557 + *
1558 + * @return string The generated HTML for the attribute filter list.
1559 + */
1560 + public static function get_default_filter_html( $filter_type, $options, $additional_data = [], $input = 'checkbox' ) {
1561 + $html = '<ul class="product-filters input-type-' . esc_attr( $input ) . '" ';
1562 + $active_option = isset( $_GET[ $filter_type ] ) ? explode( ',', wc_clean( wp_unslash( $_GET[ $filter_type ] ) ) ) : []; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1563 + $option_link = self::get_base_url() . '/' . $filter_type . '/';
1564 +
1565 + foreach ( $options as $option => $option_name ) {
1566 + $unique_id = substr( uniqid(), -6 );
1567 + $active_tax = in_array( $option, $active_option, true ) ? ' checked' : '';
1568 + $active_group = in_array( $option, $active_option, true ) ? ' active' : '';
1569 +
1570 + $html .= '
1571 + <li class="rtsb-term-item">
1572 + <div class="rtsb-default-filter-group' . esc_attr( $active_group ) . '">
1573 + <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 . '>
1574 + <label class="rtsb-default-' . esc_attr( $input ) . '-filter-label" for="rtsb-term-default-filter-' . $unique_id . '">
1575 + <span class="name">' . esc_html( $option_name ) . '</span>
1576 + </label>
1577 + </div>
1578 + </li>';
1579 + }
1580 +
1581 + $html .= '</ul>';
1582 +
1583 + return $html;
1584 + }
1585 + /**
1586 + * Get the count of products with a specific star rating.
1587 + *
1588 + * @param int $star_rating The star rating value.
1589 + *
1590 + * @return int
1591 + */
1592 + public static function get_product_rating_count( $star_rating ) {
1593 + $args = [
1594 + 'status' => 'publish',
1595 + 'limit' => -1,
1596 + 'product_rating' => sprintf( '%.1f', $star_rating ),
1597 + ];
1598 +
1599 + if ( is_product_taxonomy() ) {
1600 + $term = get_queried_object();
1601 + $term_type = 'product_cat' === $term->taxonomy ? 'product_category_id' : 'product_tag_id';
1602 +
1603 + if ( $term instanceof WP_Term ) {
1604 + $args[ $term_type ] = [ $term->term_id ];
1605 + }
1606 + }
1607 +
1608 + $query = new WC_Product_Query( $args );
1609 + $products = $query->get_products();
1610 +
1611 + return count( $products );
1612 + }
1613 + /**
1614 + * Get attribute filter HTML.
1615 + *
1616 + * @param array $data Array of required data.
1617 + * @param string $input Type of input field for the filter (e.g., color).
1618 + *
1619 + * @return string The generated HTML for the attribute filter list.
1620 + */
1621 + public static function get_attribute_filter_list_html( $data, $input = 'color' ) {
1622 + if ( ! function_exists( 'rtwpvs' ) ) {
1623 + return '';
1624 + }
1625 +
1626 + $terms = $data['terms'];
1627 + $term_info = $data['term_info'];
1628 + $show_tooltips = $data['show_tooltips'];
1629 + $show_empty_terms = $data['show_empty_terms'];
1630 + $counter = 0;
1631 +
1632 + $html = '<ul class="product-default-filters rtsb-terms-wrapper ' . esc_attr( $term_info['type'] ) . '-variable-wrapper' . esc_attr( $data['show_label'] ) . '">';
1633 +
1634 + foreach ( $terms as $attr_term ) {
1635 + $count = $attr_term->count;
1636 + $term_link = remove_query_arg( $term_info['filter_name'], $term_info['base_link'] );
1637 + $term_link = add_query_arg( $term_info['filter_name'], $attr_term->slug, $term_link );
1638 + $unique_id = substr( uniqid(), -6 );
1639 + $name = 'term-' . wc_variation_attribute_name( $term_info['attribute'] ) . '-' . $unique_id;
1640 + $selected_class = in_array( $attr_term->slug, $term_info['current_filter'], true ) ? ' selected' : '';
1641 + $selected_item = in_array( $attr_term->slug, $term_info['current_filter'], true ) ? ' checked' : '';
1642 + $active_class = in_array( $attr_term->slug, $term_info['current_filter'], true ) ? ' active' : '';
1643 +
1644 + $args = [
1645 + 'id' => $name,
1646 + 'name' => $attr_term->name,
1647 + 'link' => $term_link,
1648 + 'type' => $term_info['type'],
1649 + 'term_id' => $attr_term->term_id,
1650 + 'term_slug' => $attr_term->slug,
1651 + 'taxonomy' => wc_variation_attribute_name( $term_info['attribute'] ),
1652 + 'tooltips' => $show_tooltips,
1653 + 'attribute_name' => $term_info['attribute'],
1654 + 'selected_item' => $selected_item,
1655 + ];
1656 +
1657 + if ( 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) && ! empty( $data['count_display'] ) && 'block' === $data['count_display'] ) {
1658 + $count = self::count_tax_data( $args, $attr_term->count );
1659 + }
1660 +
1661 + $args['count'] = $count;
1662 +
1663 + if ( ! $show_empty_terms && 0 === $count ) {
1664 + continue;
1665 + }
1666 +
1667 + $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 ) . '">';
1668 + $html .= '<div class="rtsb-default-filter-group' . esc_attr( $active_class ) . '">';
1669 + $html .= self::get_input_type_html( $input, $args );
1670 +
1671 + $counter++;
1672 +
1673 + $html .= '</div>';
1674 + $html .= '</li>';
1675 + }
1676 +
1677 + $html .= '</ul>';
1678 +
1679 + return $html;
1680 + }
1681 + /**
1682 + * Get input type HTML.
1683 + *
1684 + * @param string $input Input type.
1685 + * @param array $args Args.
1686 + * @return mixed|string|null
1687 + */
1688 + public static function get_input_type_html( $input, $args ) {
1689 + $html = '';
1690 + $count = $args['count'];
1691 +
1692 + switch ( $input ) {
1693 + case 'color':
1694 + $color = sanitize_hex_color( get_term_meta( $args['term_id'], 'product_attribute_color', true ) );
1695 +
1696 + $html .= sprintf(
1697 + // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
1698 + '<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>',
1699 + esc_attr( $args['id'] ),
1700 + esc_attr( $args['type'] ),
1701 + $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
1702 + esc_attr( $color ),
1703 + esc_html( $args['name'] ),
1704 + absint( $count )
1705 + );
1706 + break;
1707 +
1708 + case 'image':
1709 + if ( ! function_exists( 'rtwpvs' ) ) {
1710 + return $html;
1711 + }
1712 +
1713 + $attachment_id = absint( get_term_meta( $args['term_id'], 'product_attribute_image', true ) );
1714 + $image_size = rtwpvs()->get_option( 'attribute_image_size' );
1715 + $image_url = wp_get_attachment_image_url( $attachment_id, apply_filters( 'rtwpvs_product_attribute_image_size', $image_size ) );
1716 +
1717 + $html .= sprintf(
1718 + // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
1719 + '<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>',
1720 + esc_attr( $args['id'] ),
1721 + esc_attr( $args['type'] ),
1722 + $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
1723 + esc_attr( $args['name'] ),
1724 + esc_url( $image_url )
1725 + );
1726 + break;
1727 +
1728 + case 'button':
1729 + $html .= sprintf(
1730 + // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
1731 + '<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>',
1732 + esc_attr( $args['id'] ),
1733 + esc_attr( $args['type'] ),
1734 + $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
1735 + esc_html( $args['name'] ),
1736 + absint( $count )
1737 + );
1738 + break;
1739 +
1740 + default:
1741 + }
1742 +
1743 + return apply_filters( 'rtsb/elementor/default/filter/get_input_type_html', $html );
1744 + }
1745 + /**
1746 + * Get reset button.
1747 + *
1748 + * @param string $btn_text Button text.
1749 + * @param array $settings Settings array.
1750 + *
1751 + * @return string
1752 + */
1753 + public static function get_default_filter_reset_button( $btn_text, $settings ) {
1754 + $html = '';
1755 +
1756 + ob_start();
1757 +
1758 + $active = ! empty( $_SERVER['QUERY_STRING'] ) ? ' active' : '';
1759 + $reset = ! empty( $settings['reset_btn'] );
1760 + $apply_text = ! empty( $settings['apply_filters_btn_text'] ) ? $settings['apply_filters_btn_text'] : esc_html__( 'Apply Filters', 'shopbuilder' );
1761 + $apply_icon = ! empty( $settings['apply_filters_btn_icon'] ) ? $settings['apply_filters_btn_icon'] : [];
1762 + echo '<div class="default-filter-btn-wrapper">';
1763 + ?>
1764 + <?php if ( $settings['show_filter_btn'] ) { ?>
1765 + <div class="rtsb-product-default-filters rtsb-apply-filters-btn">
1766 + <div class="reset-btn-item">
1767 + <button class="rtsb-apply-filters init">
1768 + <span class="icon"><?php Fns::print_html( Fns::icons_manager( $apply_icon, 'icon-default' ) ); ?></span>
1769 + <span class="text reset-text"><?php echo esc_html( $apply_text ); ?></span>
1770 + <span></span>
1771 + </button>
1772 + </div>
1773 + </div>
1774 + <?php } ?>
1775 + <?php
1776 + if ( $reset ) {
1777 + ?>
1778 + <div class="rtsb-product-default-filters rtsb-reset<?php echo esc_attr( $active ); ?>">
1779 + <div class="reset-btn-item">
1780 + <button class="product-default-filter-reset">
1781 + <span class="text reset-text"><?php echo esc_html( $btn_text ); ?></span>
1782 + <span></span>
1783 + </button>
1784 + </div>
1785 + </div>
1786 + <?php
1787 + }
1788 + echo '</div>';
1789 +
1790 + $html .= ob_get_clean();
1791 +
1792 + return $html;
1001 1793 }
1002 1794 }