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