PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 2.0.2
ShopBuilder – WooCommerce Builder For Elementor v2.0.2
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 +205 -1531 3.4.2 → 2.0.2 View file →
@@ -8,13 +8,10 @@
8 8 */
9 9
10 10 namespace RadiusTheme\SB\Elementor\Helper;
11 11
12 -use Elementor\Plugin;
13 -use RadiusTheme\SB\Helpers\Cache;
12 +use RadiusTheme\SB\Helpers\BuilderFns;
14 13 use RadiusTheme\SB\Helpers\Fns;
15 -use WC_Product_Query;
16 -use WP_Term;
17 14
18 15 // Do not allow directly accessing this file.
19 16 if ( ! defined( 'ABSPATH' ) ) {
20 17 exit( 'This script cannot be accessed directly.' );
@@ -23,52 +20,11 @@
23 20 /**
24 21 * BuilderFns class
25 22 */
26 23 class RenderHelpers {
27 - /**
28 - * @var array
29 - */
30 - private static $cache = [];
31 - /**
32 - * @param string $order_by value.
33 - * @return string
34 - */
35 - public static function escaping_product_orderby( $order_by ) {
36 - $catalog_orderby_options = apply_filters(
37 - 'woocommerce_catalog_orderby',
38 - [
39 - 'menu_order' => __( 'Default sorting', 'shopbuilder' ),
40 - 'popularity' => __( 'Sort by popularity', 'shopbuilder' ),
41 - 'rating' => __( 'Sort by average rating', 'shopbuilder' ),
42 - 'date' => __( 'Sort by latest', 'shopbuilder' ),
43 - 'price' => __( 'Sort by price: low to high', 'shopbuilder' ),
44 - 'price-desc' => __( 'Sort by price: high to low', 'shopbuilder' ),
45 - ]
46 - );
47 - if ( in_array( $order_by, array_keys( $catalog_orderby_options ), true ) ) {
48 - return $order_by;
49 - }
50 - return 'menu_order';
51 - }
52 - /**
53 - * Get data.
54 - *
55 - * @param array|string $haystack The array or string to search for the value.
56 - * @param string $needle The key to look for in the array or the string itself.
57 - * @param mixed $default The default value to return if the key is not.
58 - *
59 - * @return mixed
60 - */
61 - public static function get_data( $haystack, $needle, $default = null ) {
62 - if ( is_array( $haystack ) && ! empty( $haystack[ $needle ] ) ) {
63 - return $haystack[ $needle ];
64 - } elseif ( is_string( $haystack ) && ! empty( $haystack ) ) {
65 - return $haystack;
66 - } else {
67 - return $default;
68 - }
69 - }
70 24
25 +
26 +
71 27 /**
72 28 * Builds an array with field values.
73 29 *
74 30 * @param array $meta Field values.
@@ -77,15 +33,15 @@
77 33 *
78 34 * @return array
79 35 */
80 36 public static function meta_dataset( array $meta, $template = '', $raw_settings = [] ) {
81 - $c_image_size = self::get_data( $meta, 'image_custom_dimension', [] );
82 - $c_image_size['crop'] = self::get_data( $meta, 'image_crop', [] );
37 + $c_image_size = ! empty( $meta['image_custom_dimension'] ) ? $meta['image_custom_dimension'] : [];
38 + $c_image_size['crop'] = ! empty( $meta['image_crop'] ) ? $meta['image_crop'] : '';
83 39
84 - if ( ( ! empty( $_GET['displayview'] ) && 'list' === $_GET['displayview'] ) || ( empty( $_GET['displayview'] ) && ( ! empty( $meta['view_mode'] ) && 'list' === $meta['view_mode'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
85 - $meta['cols'] = self::get_data( $meta, 'list_cols', 1 );
86 - $meta['cols_tablet'] = self::get_data( $meta, 'list_cols_tablet', $meta['cols'] );
87 - $meta['cols_mobile'] = self::get_data( $meta, 'list_cols_mobile', $meta['cols'] );
40 + if ( ( ! empty( $_GET['displayview'] ) && 'list' === $_GET['displayview'] ) || ( empty( $_GET['displayview'] ) && ( ! empty( $meta['view_mode'] ) && 'list' === $meta['view_mode'] ) ) ) {
41 + $meta['cols'] = ! empty( $meta['list_cols'] ) ? $meta['list_cols'] : 1;
42 + $meta['cols_tablet'] = ! empty( $meta['list_cols_tablet'] ) ? $meta['list_cols_tablet'] : $meta['cols'];
43 + $meta['cols_mobile'] = ! empty( $meta['list_cols_mobile'] ) ? $meta['list_cols_mobile'] : $meta['cols'];
88 44 }
89 45
90 46 $data = apply_filters(
91 47 'rtsb/elementor/render/meta_dataset',
@@ -91,55 +47,53 @@
91 47 'rtsb/elementor/render/meta_dataset',
92 48 [
93 49 // Layout.
94 50 'widget' => 'custom',
95 - 'template' => self::get_data( $template, '', '' ),
96 - 'raw_settings' => $raw_settings,
97 - 'layout' => self::get_data( $meta, 'layout', 'layout1' ),
98 - 'grid_layout' => self::get_data( $raw_settings, 'layout', 'grid-layout1' ),
99 - 'list_layout' => self::get_data( $raw_settings, 'list_layout', 'list-layout1' ),
100 - 'd_cols' => self::get_data( $meta, 'cols', 0 ),
101 - 't_cols' => self::get_data( $meta, 'cols_tablet', 2 ),
102 - 'm_cols' => self::get_data( $meta, 'cols_mobile', 1 ),
103 - 'grid_type' => self::get_data( $meta, 'grid_style', 'even' ),
51 + 'template' => ! empty( $template ) ? $template : '',
52 + 'raw_settings' => ! empty( $raw_settings ) ? $raw_settings : [],
53 + 'layout' => ! empty( $meta['layout'] ) ? $meta['layout'] : 'layout1',
54 + 'grid_layout' => ! empty( $raw_settings['layout'] ) ? $raw_settings['layout'] : 'grid-layout1',
55 + 'list_layout' => ! empty( $raw_settings['list_layout'] ) ? $raw_settings['list_layout'] : 'list-layout1',
56 + 'd_cols' => ! empty( $meta['cols'] ) ? $meta['cols'] : 0,
57 + 't_cols' => ! empty( $meta['cols_tablet'] ) ? $meta['cols_tablet'] : 2,
58 + 'm_cols' => ! empty( $meta['cols_mobile'] ) ? $meta['cols_mobile'] : 1,
59 + 'grid_type' => ! empty( $meta['grid_style'] ) ? $meta['grid_style'] : 'even',
104 60
105 61 // Query.
106 - 'post_in' => self::get_data( $meta, 'include_posts', [] ),
107 - 'post_not_in' => self::get_data( $meta, 'exclude_posts', [] ),
62 + 'post_in' => ! empty( $meta['include_posts'] ) && is_array( $meta['include_posts'] ) ? $meta['include_posts'] : [],
63 + 'post_not_in' => ! empty( $meta['exclude_posts'] ) && is_array( $meta['exclude_posts'] ) ? $meta['exclude_posts'] : [],
108 64 'limit' => ( empty( $meta['posts_limit'] ) || '-1' === $meta['posts_limit'] ) ? 10000000 : $meta['posts_limit'],
109 - 'offset' => self::get_data( $meta, 'posts_offset', 0 ),
110 - 'order_by' => self::get_data( $meta, 'posts_order_by', 'date' ),
111 - 'order' => self::get_data( $meta, 'posts_order', 'DESC' ),
112 - 'author_in' => self::get_data( $meta, 'filter_author', [] ),
113 - 'display_by' => self::get_data( $meta, 'products_filter', 'date' ),
114 - 'categories' => self::get_data( $meta, 'filter_categories', [] ),
115 - 'brands' => self::get_data( $meta, 'filter_brands', [] ),
116 - 'tags' => self::get_data( $meta, 'filter_tags', [] ),
117 - 'attributes' => self::get_data( $meta, 'filter_attributes', [] ),
118 - 'relation' => self::get_data( $meta, 'tax_relation', 'OR' ),
119 - 'category_source' => self::get_data( $meta, 'select_source', 'product_cat' ),
120 - 'category_term' => self::get_data( $meta, 'select_cat' ),
121 - 'display_cat_by' => self::get_data( $meta, 'display_cat_by' ),
122 - 'include_cats' => self::get_data( $meta, 'include_cats', [] ),
123 - 'exclude_cats' => self::get_data( $meta, 'exclude_cats', [] ),
124 - 'select_parent_cat' => self::get_data( $meta, 'select_parent_cat' ),
125 - 'select_cats' => self::get_data( $meta, 'select_cats', [] ),
126 - 'select_cat_ids' => self::get_data( $meta, 'include_cat_ids', '' ),
65 + 'offset' => ! empty( $meta['posts_offset'] ) ? $meta['posts_offset'] : 0,
66 + 'order_by' => ! empty( $meta['posts_order_by'] ) ? $meta['posts_order_by'] : 'date',
67 + 'order' => ! empty( $meta['posts_order'] ) ? $meta['posts_order'] : 'DESC',
68 + 'author_in' => ! empty( $meta['filter_author'] ) && is_array( $meta['filter_author'] ) ? $meta['filter_author'] : [],
69 + 'display_by' => ! empty( $meta['products_filter'] ) ? $meta['products_filter'] : 'date',
70 + 'categories' => ! empty( $meta['filter_categories'] ) && is_array( $meta['filter_categories'] ) ? $meta['filter_categories'] : [],
71 + 'tags' => ! empty( $meta['filter_tags'] ) && is_array( $meta['filter_tags'] ) ? $meta['filter_tags'] : [],
72 + 'attributes' => ! empty( $meta['filter_attributes'] ) && is_array( $meta['filter_attributes'] ) ? $meta['filter_attributes'] : [],
73 + 'relation' => ! empty( $meta['tax_relation'] ) ? $meta['tax_relation'] : 'OR',
74 + 'category_source' => ! empty( $meta['select_source'] ) ? $meta['select_source'] : 'product_cat',
75 + 'category_term' => ! empty( $meta['select_cat'] ) ? $meta['select_cat'] : null,
76 + 'display_cat_by' => ! empty( $meta['display_cat_by'] ) ? $meta['display_cat_by'] : null,
77 + 'include_cats' => ! empty( $meta['include_cats'] ) && is_array( $meta['include_cats'] ) ? $meta['include_cats'] : [],
78 + 'exclude_cats' => ! empty( $meta['exclude_cats'] ) && is_array( $meta['exclude_cats'] ) ? $meta['exclude_cats'] : [],
79 + 'select_parent_cat' => ! empty( $meta['select_parent_cat'] ) ? $meta['select_parent_cat'] : null,
80 + 'select_cats' => ! empty( $meta['select_cats'] ) && is_array( $meta['select_cats'] ) ? $meta['select_cats'] : [],
81 + 'select_cat_ids' => ! empty( $meta['include_cat_ids'] ) ? $meta['include_cat_ids'] : '',
127 82 'cats_limit' => ( empty( $meta['cats_limit'] ) || '-1' === $meta['cats_limit'] ) ? 10000000 : $meta['cats_limit'],
128 83 'show_empty' => ! empty( $meta['show_empty'] ),
129 84 'show_uncategorized' => ! empty( $meta['show_uncategorized'] ),
130 85 'show_subcats' => ! empty( $meta['show_subcats'] ),
131 86 'show_top_level_cats' => ! empty( $meta['show_top_level_cats'] ),
132 - 'cats_order_by' => self::get_data( $meta, 'cats_order_by', 'name' ),
133 - 'cats_order' => self::get_data( $meta, 'cats_order', 'DESC' ),
134 - 'rtsb_order' => self::get_data( $meta, 'rtsb_order', 'ASC' ),
135 - 'rtsb_orderby' => self::get_data( $meta, 'rtsb_orderby', 'menu_order' ),
87 + 'cats_order_by' => ! empty( $meta['cats_order_by'] ) ? $meta['cats_order_by'] : 'name',
88 + 'cats_order' => ! empty( $meta['cats_order'] ) ? $meta['cats_order'] : 'DESC',
89 + 'rtsb_order' => ! empty( $meta['rtsb_order'] ) ? $meta['rtsb_order'] : 'ASC',
90 + 'rtsb_orderby' => ! empty( $meta['rtsb_orderby'] ) ? $meta['rtsb_orderby'] : 'menu_order',
136 91
137 92 // Pagination.
138 93 'pagination' => ! empty( $meta['show_pagination'] ),
139 - 'posts_loading_type' => self::get_data( $meta, 'pagination_type', 'pagination' ),
140 - 'posts_per_page' => self::get_data( $meta, 'pagination_per_page', '' ) ?: ( ! empty( $meta['posts_per_page'] ) ? absint( $meta['posts_per_page'] ) : '' ),
141 - 'visible_range' => max( 1, absint( self::get_data( $meta, 'pagination_visible_range', 5 ) ) ),
94 + 'posts_loading_type' => ! empty( $meta['pagination_type'] ) ? $meta['pagination_type'] : 'pagination',
95 + 'posts_per_page' => ! empty( $meta['pagination_per_page'] ) ? $meta['pagination_per_page'] : '',
142 96
143 97 // Image.
144 98 'f_img' => ! empty( $meta['show_featured_image'] ),
145 99 'gallery_img' => ! empty( $meta['show_product_gallery'] ),
@@ -144,13 +98,13 @@
144 98 'f_img' => ! empty( $meta['show_featured_image'] ),
145 99 'gallery_img' => ! empty( $meta['show_product_gallery'] ),
146 100 'c_img' => ! empty( $meta['show_cat_image'] ),
147 101 'hover_img' => ! empty( $meta['show_hover_image'] ),
148 - 'f_img_size' => self::get_data( $meta, 'image', 'medium' ),
102 + 'f_img_size' => ! empty( $meta['image'] ) ? $meta['image'] : 'medium',
149 103 'custom_img_size' => ! empty( $c_image_size ) && is_array( $c_image_size ) ? $c_image_size : [],
150 104 'default_img_id' => ! empty( $meta['default_preview_image']['id'] ) ? $meta['default_preview_image']['id'] : null,
151 105 'show_overlay' => ! empty( $meta['show_overlay'] ),
152 - 'hover_animation' => self::get_data( $meta, 'image_hover_animation', 'none' ),
106 + 'hover_animation' => ! empty( $meta['image_hover_animation'] ) ? $meta['image_hover_animation'] : 'none',
153 107 'show_custom_image' => ! empty( $meta['show_custom_image'] ),
154 108 'custom_image' => ! empty( $meta['custom_image']['id'] ) ? $meta['custom_image']['id'] : null,
155 109
156 110 // Visibility.
@@ -156,96 +110,90 @@
156 110 // Visibility.
157 111 'visibility' => ! empty( self::content_visibility( $meta ) ) ? self::content_visibility( $meta ) : [],
158 112
159 113 // Action Buttons.
160 - 'btn_preset' => self::get_data( $meta, 'action_btn_preset', 'preset1' ),
161 - 'btn_position' => self::get_data( $meta, 'action_btn_position', 'above' ),
162 - 'tooltip_position' => self::get_data( $meta, 'action_btn_tooltip_position', 'top' ),
114 + 'btn_preset' => ! empty( $meta['action_btn_preset'] ) ? $meta['action_btn_preset'] : 'preset1',
115 + 'btn_position' => ! empty( $meta['action_btn_position'] ) ? $meta['action_btn_position'] : 'above',
116 + 'tooltip_position' => ! empty( $meta['action_btn_tooltip_position'] ) ? $meta['action_btn_tooltip_position'] : 'top',
163 117
164 118 // Product Title.
165 - 'title_tag' => self::get_data( $meta, 'title_tag', 'h3' ),
119 + 'title_tag' => ! empty( $meta['title_tag'] ) ? $meta['title_tag'] : 'h3',
166 120 'title_hover' => ! empty( $meta['title_hover'] ),
167 - 'title_limit' => self::get_data( $meta, 'title_limit', 'default' ),
168 - 'title_limit_custom' => self::get_data( $meta, 'title_limit_custom' ),
121 + 'title_limit' => ! empty( $meta['title_limit'] ) ? $meta['title_limit'] : 'default',
122 + 'title_limit_custom' => ! empty( $meta['title_limit_custom'] ) ? $meta['title_limit_custom'] : null,
169 123 'show_custom_title' => ! empty( $meta['show_custom_title'] ),
170 - 'cat_custom_title' => self::get_data( $meta, 'custom_title', '' ),
124 + 'cat_custom_title' => ! empty( $meta['custom_title'] ) ? $meta['custom_title'] : '',
171 125
172 126 // Variation Swatches.
173 - 'swatch_position' => self::get_data( $meta, 'swatch_position', 'top' ),
174 - 'swatch_type' => self::get_data( $meta, 'swatch_type', 'circle' ),
127 + 'swatch_position' => ! empty( $meta['swatch_position'] ) ? $meta['swatch_position'] : 'top',
128 + 'swatch_type' => ! empty( $meta['swatch_type'] ) ? $meta['swatch_type'] : 'circle',
175 129
176 130 // Short Description.
177 131 'show_custom_excerpt' => ! empty( $meta['show_custom_description'] ),
178 - 'cat_custom_excerpt' => self::get_data( $meta, 'custom_description', '' ),
179 - 'cat_excerpt_position' => self::get_data( $meta, 'excerpt_position', 'below' ),
180 - 'excerpt_limit' => self::get_data( $meta, 'excerpt_limit', 'default' ),
181 - 'excerpt_limit_custom' => self::get_data( $meta, 'excerpt_limit_custom', 200 ),
132 + 'cat_custom_excerpt' => ! empty( $meta['custom_description'] ) ? $meta['custom_description'] : '',
133 + 'cat_excerpt_position' => ! empty( $meta['excerpt_position'] ) ? $meta['excerpt_position'] : 'below',
134 + 'excerpt_limit' => ! empty( $meta['excerpt_limit'] ) ? $meta['excerpt_limit'] : 'default',
135 + 'excerpt_limit_custom' => ! empty( $meta['excerpt_limit_custom'] ) ? $meta['excerpt_limit_custom'] : 200,
182 136
183 137 // Count.
184 - 'count_position' => self::get_data( $meta, 'count_display_type', 'flex' ),
185 - 'before_count' => self::get_data( $meta, 'before_count', '' ),
186 - 'after_count' => self::get_data( $meta, 'after_count', '' ),
138 + 'count_position' => ! empty( $meta['count_display_type'] ) ? $meta['count_display_type'] : 'flex',
139 + 'before_count' => ! empty( $meta['before_count'] ) ? $meta['before_count'] : '',
140 + 'after_count' => ! empty( $meta['after_count'] ) ? $meta['after_count'] : '',
187 141
188 142 // Add to cart.
189 143 'show_cart_icon' => ! empty( $meta['show_cart_icon'] ),
190 144 'show_cart_text' => ! empty( $meta['show_cart_text'] ),
191 - 'add_to_cart_icon' => self::get_data( $meta, 'add_to_cart_icon', [] ),
192 - 'add_to_cart_success_icon' => self::get_data( $meta, 'add_to_cart_success_icon', [] ),
193 - 'add_to_cart_text' => self::get_data( $meta, 'add_to_cart_text', esc_html__( 'Add to Cart', 'shopbuilder' ) ),
194 - 'add_to_cart_success_text' => self::get_data( $meta, 'add_to_cart_success_text', '' ),
195 - 'add_to_cart_alignment' => self::get_data( $meta, 'cart_icon_alignment', 'left' ),
145 + 'add_to_cart_icon' => ! empty( $meta['add_to_cart_icon'] ) ? $meta['add_to_cart_icon'] : [],
146 + 'add_to_cart_success_icon' => ! empty( $meta['add_to_cart_success_icon'] ) ? $meta['add_to_cart_success_icon'] : [],
147 + 'add_to_cart_text' => ! empty( $meta['add_to_cart_text'] ) ? $meta['add_to_cart_text'] : esc_html__( 'Add to Cart', 'shopbuilder' ),
148 + 'add_to_cart_success_text' => ! empty( $meta['add_to_cart_success_text'] ) ? $meta['add_to_cart_success_text'] : '',
149 + 'add_to_cart_alignment' => ! empty( $meta['cart_icon_alignment'] ) ? $meta['cart_icon_alignment'] : 'left',
196 150
197 151 // Action Button Icons.
198 - 'quick_view_icon' => self::get_data( $meta, 'quick_view_icon', [] ),
199 - 'comparison_icon' => self::get_data( $meta, 'comparison_icon', [] ),
200 - 'comparison_icon_added' => self::get_data( $meta, 'comparison_icon_added', [] ),
201 - 'wishlist_icon' => self::get_data( $meta, 'wishlist_icon', [] ),
202 - 'wishlist_icon_added' => self::get_data( $meta, 'wishlist_icon_added', [] ),
152 + 'quick_view_icon' => ! empty( $meta['quick_view_icon'] ) ? $meta['quick_view_icon'] : [],
153 + 'comparison_icon' => ! empty( $meta['comparison_icon'] ) ? $meta['comparison_icon'] : [],
154 + 'comparison_icon_added' => ! empty( $meta['comparison_icon_added'] ) ? $meta['comparison_icon_added'] : [],
155 + 'wishlist_icon' => ! empty( $meta['wishlist_icon'] ) ? $meta['wishlist_icon'] : [],
156 + 'wishlist_icon_added' => ! empty( $meta['wishlist_icon_added'] ) ? $meta['wishlist_icon_added'] : [],
203 157
204 158 // Badges.
205 - 'sale_badge_type' => self::get_data( $meta, 'sale_badges_type', 'percentage' ),
206 - 'sale_badge_text' => self::get_data( $meta, 'sale_badges_text', esc_html__( 'Sale', 'shopbuilder' ) ),
207 - 'custom_badge_text' => self::get_data( $meta, 'custom_badge_text', esc_html__( 'Sale', 'shopbuilder' ) ),
208 - 'out_of_stock_badge_text' => self::get_data( $meta, 'stock_badges_text', esc_html__( 'Out of Stock', 'shopbuilder' ) ),
209 - 'badge_position' => self::get_data( $meta, 'badges_position', 'top' ),
210 - 'badge_alignment' => self::get_data( $meta, 'badges_alignment', 'left' ),
211 - 'badge_preset' => self::get_data( $meta, 'custom_badge_preset', 'preset-1' ),
212 - 'enable_badges_module' => self::get_data( $meta, 'enable_badges_module', '' ),
159 + 'sale_badge_type' => ! empty( $meta['sale_badges_type'] ) ? $meta['sale_badges_type'] : 'percentage',
160 + 'sale_badge_text' => ! empty( $meta['sale_badges_text'] ) ? $meta['sale_badges_text'] : esc_html__( 'Sale', 'shopbuilder' ),
161 + 'custom_badge_text' => ! empty( $meta['custom_badge_text'] ) ? $meta['custom_badge_text'] : esc_html__( 'Sale', 'shopbuilder' ),
162 + 'out_of_stock_badge_text' => ! empty( $meta['stock_badges_text'] ) ? $meta['stock_badges_text'] : esc_html__( 'Out of Stock', 'shopbuilder' ),
163 + 'badge_position' => ! empty( $meta['badges_position'] ) ? $meta['badges_position'] : 'top',
164 + 'badge_alignment' => ! empty( $meta['badges_alignment'] ) ? $meta['badges_alignment'] : 'left',
165 + 'badge_preset' => ! empty( $meta['custom_badge_preset'] ) ? $meta['custom_badge_preset'] : 'preset-1',
213 166
214 167 // Link.
215 168 'image_link' => ! empty( $meta['image_link'] ),
216 169 'title_link' => ! empty( $meta['title_link'] ),
217 - 'hover_btn_text' => self::get_data( $meta, 'hover_btn_text', esc_html__( 'See Details', 'shopbuilder' ) ),
170 + 'hover_btn_text' => ! empty( $meta['hover_btn_text'] ) ? $meta['hover_btn_text'] : esc_html__( 'See Details', 'shopbuilder' ),
218 171 'show_hover_btn_icon' => ! empty( $meta['show_hover_btn_icon'] ),
219 - 'hover_btn_icon' => self::get_data( $meta, 'hover_btn_icon', [] ),
220 - 'custom_link' => self::get_data( $meta, 'custom_link' ),
172 + 'hover_btn_icon' => ! empty( $meta['hover_btn_icon'] ) ? $meta['hover_btn_icon'] : [],
173 + 'custom_link' => ! empty( $meta['custom_link'] ) ? $meta['custom_link'] : null,
221 174
222 175 // Slider.
223 - 'd_group' => self::get_data( $meta, 'cols_group', 1 ),
224 - 't_group' => self::get_data( $meta, 'cols_group_tablet', 1 ),
225 - 'm_group' => self::get_data( $meta, 'cols_group_mobile', 1 ),
176 + 'd_group' => ! empty( $meta['cols_group'] ) ? $meta['cols_group'] : 1,
177 + 't_group' => ! empty( $meta['cols_group_tablet'] ) ? $meta['cols_group_tablet'] : 1,
178 + 'm_group' => ! empty( $meta['cols_group_mobile'] ) ? $meta['cols_group_mobile'] : 1,
226 179 'auto_play' => ! empty( $meta['slide_autoplay'] ),
227 180 'stop_on_hover' => ! empty( $meta['pause_hover'] ),
228 181 'slider_nav' => ! empty( $meta['slider_nav'] ),
229 182 'slider_dot' => ! empty( $meta['slider_pagi'] ),
230 - 'slider_dynamic_dot' => ! empty( $meta['slider_dynamic_pagi'] ),
231 183 'loop' => ! empty( $meta['slider_loop'] ),
232 184 'lazy_load' => ! empty( $meta['slider_lazy_load'] ),
233 185 'auto_height' => ! empty( $meta['slider_auto_height'] ),
234 - 'speed' => self::get_data( $meta, 'slide_speed', 2000 ),
186 + 'speed' => ! empty( $meta['slide_speed'] ) ? $meta['slide_speed'] : 2000,
235 187 'space_between' => isset( $meta['grid_gap']['size'] ) && strlen( $meta['grid_gap']['size'] ) ? $meta['grid_gap']['size'] : 30,
236 - 'auto_play_timeout' => self::get_data( $meta, 'autoplay_timeout', 5000 ),
237 - 'nav_position' => self::get_data( $meta, 'slider_nav_position', 'top' ),
238 - 'left_arrow_icon' => self::get_data( $meta, 'slider_left_arrow_icon', [] ),
239 - 'right_arrow_icon' => self::get_data( $meta, 'slider_right_arrow_icon', [] ),
188 + 'auto_play_timeout' => ! empty( $meta['autoplay_timeout'] ) ? $meta['autoplay_timeout'] : 5000,
189 + 'nav_position' => ! empty( $meta['slider_nav_position'] ) ? $meta['slider_nav_position'] : 'top',
190 + 'left_arrow_icon' => ! empty( $meta['slider_left_arrow_icon'] ) ? $meta['slider_left_arrow_icon'] : [],
191 + 'right_arrow_icon' => ! empty( $meta['slider_right_arrow_icon'] ) ? $meta['slider_right_arrow_icon'] : [],
240 192 ],
241 193 $meta
242 194 );
243 195
244 - if ( ! empty( $meta['active_cat_slider'] ) ) {
245 - $data['active_cat_slider'] = true;
246 - }
247 -
248 196 if ( ! empty( $meta['cols_widescreen'] ) ) {
249 197 $data['w_cols'] = $meta['cols_widescreen'];
250 198 }
251 199
@@ -263,13 +211,11 @@
263 211
264 212 $queried_obj = get_queried_object();
265 213
266 214 if ( is_shop() || is_product_taxonomy() ) {
267 - if ( empty( $data['posts_per_page'] ) ) {
268 - $data['posts_per_page'] = self::get_products_per_page();
269 - }
270 - $data['order_by'] = self::get_data( $data, 'rtsb_orderby', $data['order_by'] );
271 - $data['order'] = self::get_data( $data, 'rtsb_order', $data['order'] );
215 + $data['posts_per_page'] = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
216 + $data['order_by'] = ! empty( $data['rtsb_orderby'] ) ? esc_html( $data['rtsb_orderby'] ) : $data['orderby'];
217 + $data['order'] = ! empty( $data['rtsb_order'] ) ? esc_html( $data['rtsb_order'] ) : $data['order'];
272 218 }
273 219
274 220 if ( ! is_shop() && is_product_taxonomy() ) {
275 221 if ( ! empty( $queried_obj->taxonomy ) ) {
@@ -280,20 +226,8 @@
280 226 $data['queried_term'] = esc_html( $queried_obj->term_id );
281 227 }
282 228 }
283 229
284 - if ( is_search() ) {
285 - // Ask for the unescaped term: this dataset is data, not output.
286 - // get_search_query() defaults to esc_attr(), and pre-escaping here
287 - // would be encoded a second time at the output boundary and would
288 - // corrupt the JSON payload this array is encoded into.
289 - $search_query = get_search_query( false );
290 -
291 - if ( $search_query ) {
292 - $data['search_query'] = sanitize_text_field( $search_query );
293 - }
294 - }
295 -
296 230 return apply_filters( 'rtsb/elementor/render/meta_dataset_final', $data, $meta, $raw_settings );
297 231 }
298 232
299 233 /**
@@ -304,15 +238,19 @@
304 238 *
305 239 * @return array
306 240 */
307 241 public static function archive_meta_dataset( array $meta, $template = '' ) {
242 + $products_row = absint( get_option( 'woocommerce_catalog_rows', 4 ) );
243 + $products_col = absint( get_option( 'woocommerce_catalog_columns', 4 ) );
244 + $products_per_page = apply_filters( 'rtsb/elementor/archive/products_per_page', $products_row * $products_col );
245 +
308 246 $data = apply_filters(
309 247 'rtsb/elementor/render/archive_meta_dataset',
310 248 [
311 249 // Layout.
312 250 'widget' => 'default',
313 - 'template' => self::get_data( $template, '', '' ),
314 - 'view_mode' => self::get_data( $meta, 'view_mode', 'grid' ),
251 + 'template' => ! empty( $template ) ? $template : '',
252 + 'view_mode' => ! empty( $meta['view_mode'] ) ? $meta['view_mode'] : 'grid',
315 253 'show_flash_sale' => ! empty( $meta['show_flash_sale'] ),
316 254 'wishlist_button' => ! empty( $meta['wishlist_button'] ),
317 255 'comparison_button' => ! empty( $meta['comparison_button'] ),
318 256 'quick_view_button' => ! empty( $meta['quick_view_button'] ),
@@ -317,24 +255,20 @@
317 255 'comparison_button' => ! empty( $meta['comparison_button'] ),
318 256 'quick_view_button' => ! empty( $meta['quick_view_button'] ),
319 257 'show_rating' => ! empty( $meta['show_rating'] ),
320 258 'show_pagination' => ! empty( $meta['show_pagination'] ),
321 - // The base Product Archive widget has no pagination-type control: with filters it
322 - // uses Ajax Load More (see the widget's pagination notice). Custom-layout widgets
323 - // pass their own pagination_type and use a different dataset.
324 - 'posts_loading_type' => self::get_data( $meta, 'pagination_type', 'load_more' ),
325 - 'cart_icon' => self::get_data( $meta, 'cart_icon', [] ),
326 - 'wishlist_icon' => self::get_data( $meta, 'wishlist_icon', [] ),
327 - 'wishlist_icon_added' => self::get_data( $meta, 'wishlist_icon_added', [] ),
328 - 'comparison_icon' => self::get_data( $meta, 'comparison_icon', [] ),
329 - 'comparison_icon_added' => self::get_data( $meta, 'comparison_icon_added', [] ),
330 - 'quick_view_icon' => self::get_data( $meta, 'quick_view_icon', [] ),
331 - 'prev_icon' => self::get_data( $meta, 'prev_icon', [] ),
332 - 'next_icon' => self::get_data( $meta, 'next_icon', [] ),
333 - 'posts_per_page' => self::get_products_per_page(),
334 - 'rtsb_order' => self::get_data( $meta, 'rtsb_order', 'ASC' ),
335 - 'rtsb_orderby' => self::get_data( $meta, 'rtsb_orderby', 'menu_order' ),
336 - 'tooltip_position' => self::get_data( $meta, 'action_btn_tooltip_position', 'top' ),
259 + 'cart_icon' => ! empty( $meta['cart_icon'] ) ? $meta['cart_icon'] : [],
260 + 'wishlist_icon' => ! empty( $meta['wishlist_icon'] ) ? $meta['wishlist_icon'] : [],
261 + 'wishlist_icon_added' => ! empty( $meta['wishlist_icon_added'] ) ? $meta['wishlist_icon_added'] : [],
262 + 'comparison_icon' => ! empty( $meta['comparison_icon'] ) ? $meta['comparison_icon'] : [],
263 + 'comparison_icon_added' => ! empty( $meta['comparison_icon_added'] ) ? $meta['comparison_icon_added'] : [],
264 + 'quick_view_icon' => ! empty( $meta['quick_view_icon'] ) ? $meta['quick_view_icon'] : [],
265 + 'prev_icon' => ! empty( $meta['prev_icon'] ) ? $meta['prev_icon'] : [],
266 + 'next_icon' => ! empty( $meta['next_icon'] ) ? $meta['next_icon'] : [],
267 + 'posts_per_page' => ! empty( $products_per_page ) ? $products_per_page : 12,
268 + 'rtsb_order' => ! empty( $meta['rtsb_order'] ) ? $meta['rtsb_order'] : 'ASC',
269 + 'rtsb_orderby' => ! empty( $meta['rtsb_orderby'] ) ? $meta['rtsb_orderby'] : 'menu_order',
270 + 'tooltip_position' => ! empty( $meta['action_btn_tooltip_position'] ) ? $meta['action_btn_tooltip_position'] : 'top',
337 271 ],
338 272 $meta
339 273 );
340 274
@@ -339,13 +273,9 @@
339 273 );
340 274
341 275 $queried_obj = get_queried_object();
342 276
343 - // Use WooCommerce's own taxonomy check (covers product_cat, product_tag
344 - // and product_brand) so the queried term is recorded on every product
345 - // taxonomy archive — Ajax load-more / filters scope to it. Mirrors the
346 - // custom-layout dataset in self::meta_dataset().
347 - if ( ! is_shop() && is_product_taxonomy() ) {
277 + if ( ! is_shop() && BuilderFns::is_archive() ) {
348 278 if ( ! empty( $queried_obj->taxonomy ) ) {
349 279 $data['queried_tax'] = esc_html( $queried_obj->taxonomy );
350 280 }
351 281
@@ -353,20 +283,8 @@
353 283 $data['queried_term'] = esc_html( $queried_obj->term_id );
354 284 }
355 285 }
356 286
357 - if ( is_search() ) {
358 - // Ask for the unescaped term: this dataset is data, not output.
359 - // get_search_query() defaults to esc_attr(), and pre-escaping here
360 - // would be encoded a second time at the output boundary and would
361 - // corrupt the JSON payload this array is encoded into.
362 - $search_query = get_search_query( false );
363 -
364 - if ( $search_query ) {
365 - $data['search_query'] = sanitize_text_field( $search_query );
366 - }
367 - }
368 -
369 287 return apply_filters( 'rtsb/elementor/render/archive_meta_dataset_final', $data, $meta );
370 288 }
371 289
372 290 /**
@@ -405,16 +323,9 @@
405 323
406 324 if ( ! empty( $settings['single_category'] ) ) {
407 325 $visibility[] = 'single-cat';
408 326 }
409 - if ( ! empty( $settings['show_brands'] ) ) {
410 - $visibility[] = 'brands';
411 - }
412 327
413 - if ( ! empty( $settings['single_brand'] ) ) {
414 - $visibility[] = 'single-brand';
415 - }
416 -
417 328 if ( ! empty( $settings['show_swatches'] ) ) {
418 329 $visibility[] = 'swatches';
419 330 }
420 331
@@ -480,47 +391,18 @@
480 391 * @return int
481 392 */
482 393 public static function default_columns( $layout ) {
483 394 switch ( $layout ) {
484 - case 'rtsb-post-grid-layout1':
485 - case 'rtsb-post-grid-layout2':
486 - case 'rtsb-post-grid-layout3':
487 - case 'rtsb-team-layout1':
488 - case 'rtsb-team-layout2':
489 - case 'rtsb-testimonial-layout2':
490 395 case 'grid-layout2':
491 396 case 'slider-layout2':
492 - case 'rtsb-coupon-layout1':
493 - case 'rtsb-coupon-layout2':
494 - case 'rtsb-coupon-layout3':
495 397 $columns = 3;
496 398 break;
497 - case 'rtsb-post-list-layout1':
498 - case 'rtsb-post-list-layout2':
399 +
499 400 case 'list-layout1':
500 401 case 'list-layout2':
501 402 case 'list-layout3':
502 - case 'list-layout7':
503 - case 'list-layout5':
504 403 $columns = 1;
505 404 break;
506 - case 'rtsb-team-layout4':
507 - case 'rtsb-testimonial-layout1':
508 - case 'rtsb-testimonial-layout3':
509 - case 'rtsb-testimonial-layout4':
510 - case 'rtsb-testimonial-layout5':
511 - case 'list-layout4':
512 - case 'list-layout6':
513 - case 'slider-layout5':
514 - case 'slider-layout8':
515 - $columns = 2;
516 - break;
517 - case 'rtsb-logo-layout1':
518 - case 'rtsb-logo-layout2':
519 - case 'grid-layout6':
520 - case 'grid-layout9':
521 - $columns = 5;
522 - break;
523 405
524 406 case 'category-layout1':
525 407 $columns = 6;
526 408 break;
@@ -529,9 +411,9 @@
529 411 $columns = 4;
530 412 break;
531 413 }
532 414
533 - return apply_filters( 'rtsb/element/general/default_columns', $columns, $layout );
415 + return $columns;
534 416 }
535 417
536 418 /**
537 419 * Pagination JSON data.
@@ -543,250 +425,12 @@
543 425 */
544 426 public static function pagination_data( $metas, $template ) {
545 427 $el_data = self::meta_dataset( $metas, $template );
546 428
547 - // Returned as an attribute *value*; the caller escapes it for the
548 - // attribute context (Elementor's Utils::render_html_attributes() applies
549 - // esc_attr()). esc_js() here only double-encoded the payload and, via
550 - // addslashes(), corrupted the JSON escapes for apostrophes and
551 - // non-ASCII search terms. The HTML-safe flags keep the payload free of
552 - // characters esc_attr() would have to encode - see self::archive_data().
553 - return wp_json_encode(
554 - $el_data,
555 - JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
556 - );
429 + return ' data-rtsb-ajax=\'' . esc_js( wp_json_encode( $el_data ) ) . '\'';
557 430 }
558 431
559 432 /**
560 - * Container classes
561 - *
562 - * @param array $metas Meta data.
563 - * @param array $settings Settings array.
564 - * @param string $custom_class Custom class.
565 - *
566 - * @return mixed|null
567 - */
568 - public static function prepare_container_classes( $metas = [], $settings = [], $custom_class = '' ) {
569 - $classes = 'rtsb-elementor-container products rtsb-pos-r ';
570 - $classes .= $custom_class;
571 -
572 - $raw_layout = esc_html( $metas['layout'] );
573 - $layout = self::filter_layout( $raw_layout );
574 - $is_cat = preg_match( '/category/', $layout );
575 - $cart_txt_class = '';
576 -
577 - $animation = $metas['hover_animation'];
578 -
579 - if ( ! $is_cat ) {
580 - $cart_txt_class = $metas['show_cart_text'] ? ' show-cart-text' : ' no-cart-text';
581 - $animation .= ' gallery-hover-' . ( ! empty( $settings['gallery_hover_animation'] ) ? $settings['gallery_hover_animation'] : 'fade' );
582 - }
583 -
584 - $badge_class = [
585 - 'position' => $metas['badge_position'],
586 - 'alignment' => $metas['badge_alignment'],
587 - ];
588 -
589 - if ( ! in_array( 'clear-btn', $metas['visibility'], true ) ) {
590 - $classes .= ' no-clear-btn';
591 - }
592 -
593 - $classes .= ! empty( $metas['pagination'] ) ? ' rtsb-has-pagination' : ' rtsb-no-pagination';
594 - $classes .= ' badge-' . esc_attr( $badge_class['position'] ) . ' badge-' . esc_attr( $badge_class['alignment'] ) . esc_attr( $cart_txt_class ) . ' img-hover-' . esc_attr( $animation ) . ' excerpt-' . esc_attr( $metas['cat_excerpt_position'] );
595 -
596 - if ( $metas['show_overlay'] ) {
597 - $classes .= ' has-overlay';
598 - }
599 -
600 - if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
601 - $classes .= ' show-single-cat';
602 - }
603 - if ( in_array( 'single-brand', $metas['visibility'], true ) ) {
604 - $classes .= ' show-single-brand';
605 - }
606 -
607 - if ( ! $is_cat ) {
608 - $action_btn_classes = [
609 - 'show_compare_text' => 'show-compare-text',
610 - 'show_quick_view_text' => 'show-quick-view-text',
611 - 'show_wishlist_text' => 'show-wishlist-text',
612 - // Quick checkout only had its icon entry here, so the "Show Button Text?"
613 - // switch had nothing to emit and the label stayed hidden whatever it was
614 - // set to.
615 - 'show_quick_checkout_text' => 'show-quick-checkout-text',
616 - 'show_compare_icon' => 'no-compare-icon',
617 - 'show_quick_view_icon' => 'no-quick-view-icon',
618 - 'show_wishlist_icon' => 'no-wishlist-icon',
619 - 'show_quick_checkout_icon' => 'no-quick_checkout-icon',
620 - ];
621 -
622 - foreach ( $action_btn_classes as $setting_key => $class ) {
623 - if ( strpos( $setting_key, 'icon' ) !== false ) {
624 - $classes .= empty( $settings[ $setting_key ] ) ? ' ' . $class : '';
625 - } else {
626 - $classes .= ! empty( $settings[ $setting_key ] ) ? ' ' . $class : '';
627 - }
628 - }
629 -
630 - if ( rtsb()->has_pro() && ! empty( $settings['custom_ordering'] ) ) {
631 - $classes .= ' has-custom-ordering';
632 - }
633 - }
634 - if ( rtsb()->has_pro() && ! empty( $settings['slider_slide_animation'] ) ) {
635 - $classes .= ' has-slide-animation';
636 - }
637 -
638 - return apply_filters( 'rtsb/product/container/class', $classes, $settings );
639 - }
640 -
641 - /**
642 - * Function to filter and validate the layout against allowed patterns.
643 - *
644 - * @param string $layout The layout string to be filtered.
645 - * @param string $template The template to be checked.
646 - *
647 - * @return string
648 - */
649 - public static function filter_layout( $layout, $template = '' ) {
650 - $allowed_patterns = apply_filters( 'rtsb/elementor/custom_layout', [ 'grid', 'list', 'slider', 'category', 'toyup', 'zilly', 'rtsb' ] );
651 - $layout_part = explode( '-', $layout );
652 - $default = 'grid-layout1';
653 -
654 - if ( empty( $layout_part[0] ) ) {
655 - return $default;
656 - }
657 -
658 - if ( in_array( $layout_part[0], $allowed_patterns, true ) ) {
659 - if ( ! rtsb()->has_pro() && ! in_array( $layout, array_keys( Fns::free_layouts( $layout ) ), true ) ) {
660 - $layout = self::set_default_layout( $layout );
661 - }
662 -
663 - return self::resolve_existing_layout( $layout, $template );
664 - } else {
665 - return ! empty( $template ) ? self::resolve_existing_layout( self::set_default_layout( $layout ), $template ) : $default;
666 - }
667 - }
668 -
669 - /**
670 - * Falls back to the default layout when the resolved template file cannot
671 - * be found.
672 - *
673 - * Layouts contributed by a theme or an addon (rtsb-*, toyup-*, zilly-*)
674 - * stay saved in the Elementor document after that theme or addon is gone.
675 - * Without this check the loop asks for a template that no longer exists,
676 - * so nothing renders and load_template() raises a "does not exist" notice
677 - * for every product in the loop.
678 - *
679 - * @param string $layout Resolved layout name.
680 - * @param string $template Template directory, e.g. "elementor/general/grid/".
681 - * Empty when the layout is only used for CSS classes.
682 - *
683 - * @return string
684 - */
685 - private static function resolve_existing_layout( $layout, $template ) {
686 - if ( empty( $template ) || empty( $layout ) ) {
687 - return $layout;
688 - }
689 -
690 - $cache_key = 'layout_exists_' . $template . $layout;
691 -
692 - if ( isset( self::$cache[ $cache_key ] ) ) {
693 - return self::$cache[ $cache_key ];
694 - }
695 -
696 - $resolved = $layout;
697 -
698 - if ( ! Fns::locate_template( $template . $layout ) ) {
699 - $resolved = self::set_default_layout( $layout );
700 - }
701 -
702 - self::$cache[ $cache_key ] = $resolved;
703 -
704 - return $resolved;
705 - }
706 -
707 - /**
708 - * Row classes
709 - *
710 - * @param array $settings Settings array.
711 - *
712 - * @return mixed|null
713 - */
714 - public static function prepare_row_classes( $settings = [] ) {
715 - $masonry_class = null;
716 - $classes = 'rtsb-row rtsb-content-loader element-loading';
717 -
718 - $loader_class = '';
719 - if ( Fns::enable_loader() ) {
720 - $loader_class = ' rtsb-pre-loader';
721 - }
722 - $raw_layout = esc_html( $settings['layout'] );
723 - $layout = self::filter_layout( $raw_layout );
724 - $grid_type = $settings['grid_type'];
725 - $is_carousel = preg_match( '/slider/', $layout );
726 -
727 - if ( preg_match( '/category/', $layout ) && ! empty( $settings['active_cat_slider'] ) ) {
728 - $classes .= ' rtsb-slider-layout';
729 - }
730 -
731 - if ( 'equal' === $grid_type ) {
732 - $masonry_class = ' rtsb-equal';
733 - } elseif ( 'masonry' === $grid_type ) {
734 - $masonry_class = ' rtsb-masonry';
735 - } else {
736 - $masonry_class = ' rtsb-even';
737 - }
738 -
739 - if ( ! rtsb()->has_pro() || $is_carousel ) {
740 - $masonry_class = ' rtsb-even';
741 - }
742 -
743 - if ( $is_carousel && ! empty( $settings['raw_settings']['always_show_nav'] ) ) {
744 - $classes .= ' always-show-nav';
745 - }
746 -
747 - if ( ! empty( $settings['raw_settings']['inner_slider_always_show_nav'] ) ) {
748 - $classes .= ' inner-slider-always-show-nav';
749 - }
750 - if ( empty( $settings['raw_settings']['cols_mobile'] ) ) {
751 - $classes .= ' rtsb-mobile-flex-row';
752 - }
753 -
754 - $classes .= ' rtsb-' . $layout . $masonry_class . $loader_class;
755 -
756 - return apply_filters( 'rtsb/elementor/row/classes', $classes, $settings );
757 - }
758 -
759 - /**
760 - * Prepare pagination attributes.
761 - *
762 - * @param string $layout The layout type.
763 - * @param bool $has_filters Whether the widget has filters.
764 - * @param string $template The template name.
765 - * @param array $settings The widget settings.
766 - * @param bool $ajax Whether to enable AJAX pagination.
767 - *
768 - * @return string
769 - */
770 - public static function prepare_pagination_attr( $layout, $has_filters, $template, $settings, $ajax ) {
771 - $is_cat = preg_match( '/category/', $layout );
772 - $is_carousel = preg_match( '/slider/', $layout );
773 -
774 - if ( $is_carousel ) {
775 - return $has_filters ? self::pagination_data( $settings, $template ) : '';
776 - } elseif ( $is_cat ) {
777 - return '';
778 - } else {
779 - $ajax_pagination = ! empty( $settings['show_pagination'] ) && 'pagination' !== $settings['pagination_type'];
780 - $page = Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) );
781 - $is_shop_or_archive = is_shop() || is_product_taxonomy();
782 - $condition = rtsb()->has_pro() && $ajax && ( $ajax_pagination || $has_filters || $page || $is_shop_or_archive );
783 -
784 - return $condition ? self::pagination_data( $settings, $template ) : '';
785 - }
786 - }
787 -
788 - /**
789 433 * Archive JSON data.
790 434 *
791 435 * @param array $metas Data set.
792 436 * @param string $template Template name.
@@ -795,23 +439,9 @@
795 439 */
796 440 public static function archive_data( $metas, $template ) {
797 441 $el_data = self::archive_meta_dataset( $metas, $template );
798 442
799 - // The return value is an HTML attribute, so it must be escaped for the
800 - // HTML attribute context. esc_js() is a JavaScript-string escaper: it
801 - // turns ' back into a bare apostrophe and relies on a backslash
802 - // that means nothing to an HTML parser, which let a search term close
803 - // the attribute and inject its own. esc_attr() encodes both quote
804 - // characters, so the value can never terminate the attribute.
805 - //
806 - // The JSON is additionally encoded with the HTML-safe flags so it holds
807 - // no <, >, &, ' or " at all. That keeps a search term that itself looks
808 - // like an entity (e.g. &quot;) intact: esc_attr() does not double-encode
809 - // an existing entity, so without this the browser would decode the
810 - // term back into a raw quote and break JSON.parse().
811 - $json = wp_json_encode( $el_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT );
812 -
813 - return sprintf( ' data-rtsb-ajax="%s"', esc_attr( $json ) );
443 + return ' data-rtsb-ajax=\'' . esc_js( wp_json_encode( $el_data ) ) . '\'';
814 444 }
815 445
816 446 /**
817 447 * Slider options.
@@ -821,35 +451,34 @@
821 451 *
822 452 * @return array
823 453 */
824 454 public static function slider_data( array $meta, $settings = [] ) {
825 - $has_dots = $meta['slider_dot'] ? ' has-dot' : ' no-dot';
826 - $has_dots .= $meta['slider_nav'] ? ' has-nav' : ' no-nav';
827 - $has_dynamic_dots = $meta['slider_dynamic_dot'] ? true : false;
828 - $d_col = 0 === $meta['d_cols'] ? self::default_columns( $meta['layout'] ) : $meta['d_cols'];
829 - $t_col = 0 === $meta['t_cols'] ? 2 : $meta['t_cols'];
830 - $m_col = 0 === $meta['m_cols'] ? 1 : $meta['m_cols'];
455 + $has_dots = $meta['slider_dot'] ? ' has-dot' : ' no-dot';
456 + $has_dots .= $meta['slider_nav'] ? ' has-nav' : ' no-nav';
457 + $d_col = 0 === $meta['d_cols'] ? self::default_columns( $meta['layout'] ) : $meta['d_cols'];
458 + $t_col = 0 === $meta['t_cols'] ? 2 : $meta['t_cols'];
459 + $m_col = 0 === $meta['m_cols'] ? 1 : $meta['m_cols'];
831 460
832 - if ( Plugin::$instance->breakpoints->has_custom_breakpoints() ) {
461 + if ( \Elementor\Plugin::$instance->breakpoints->has_custom_breakpoints() ) {
833 462 // Widescreen.
834 - $w_col = self::get_data( $settings, 'cols_widescreen' );
463 + $w_col = ! empty( $settings['cols_widescreen'] ) ? $settings['cols_widescreen'] : null;
835 464 $w_col = 0 === $w_col ? self::default_columns( $meta['layout'] ) : $w_col;
836 - $w_group = self::get_data( $settings, 'cols_group_widescreen', 1 );
465 + $w_group = ! empty( $settings['cols_group_widescreen'] ) ? $settings['cols_group_widescreen'] : 1;
837 466
838 467 // Laptop.
839 - $l_col = self::get_data( $settings, 'cols_laptop' );
468 + $l_col = ! empty( $settings['cols_laptop'] ) ? $settings['cols_laptop'] : null;
840 469 $l_col = 0 === $l_col ? self::default_columns( $meta['layout'] ) : $l_col;
841 - $l_group = self::get_data( $settings, 'cols_group_laptop', 1 );
470 + $l_group = ! empty( $settings['cols_group_laptop'] ) ? $settings['cols_group_laptop'] : 1;
842 471
843 472 // Tablet Landscape.
844 - $te_col = self::get_data( $settings, 'cols_tablet_extra' );
473 + $te_col = ! empty( $settings['cols_tablet_extra'] ) ? $settings['cols_tablet_extra'] : null;
845 474 $te_col = 0 === $te_col ? 3 : $te_col;
846 - $te_group = self::get_data( $settings, 'cols_group_tablet_extra', 1 );
475 + $te_group = ! empty( $settings['cols_group_tablet_extra'] ) ? $settings['cols_group_tablet_extra'] : 1;
847 476
848 477 // Mobile Landscape.
849 - $me_col = self::get_data( $settings, 'cols_mobile_extra' );
478 + $me_col = ! empty( $settings['cols_mobile_extra'] ) ? $settings['cols_mobile_extra'] : null;
850 479 $me_col = 0 === $me_col ? 2 : $me_col;
851 - $me_group = self::get_data( $settings, 'cols_group_mobile_extra', 1 );
480 + $me_group = ! empty( $settings['cols_group_mobile_extra'] ) ? $settings['cols_group_mobile_extra'] : 1;
852 481
853 482 $el_breakpoints = Fns::get_elementor_breakpoints();
854 483
855 484 foreach ( $el_breakpoints as $key => $value ) {
@@ -861,13 +490,10 @@
861 490 $breakpoints = [
862 491 0 => [
863 492 'slidesPerView' => absint( $m_col ),
864 493 'slidesPerGroup' => absint( $meta['m_group'] ),
865 - 'grid' => [
866 - 'rows' => 1,
867 - ],
868 494 'pagination' => [
869 - 'dynamicBullets' => $has_dynamic_dots,
495 + 'dynamicBullets' => true,
870 496 ],
871 497 ],
872 498 ];
873 499
@@ -892,16 +518,16 @@
892 518 }
893 519
894 520 foreach ( $el_breakpoints as $el_breakpoint => $value ) {
895 521 $breakpoint_value = ! empty( $value['value'] ) ? $value['value'] : $value['default_value'];
896 - $dynamic_bullets = $has_dynamic_dots;
522 + $dynamic_bullets = false;
897 523 $slides_per_view = $d_col;
898 524 $slides_per_group = $meta['d_group'];
899 525
900 526 switch ( $el_breakpoint ) {
901 527 case 'widescreen':
902 - $slides_per_view = $w_col;
903 - $slides_per_group = $w_group;
528 + $slides_per_view = $w_col;
529 + $slides_per_group = $w_group;
904 530
905 531 break;
906 532
907 533 case 'laptop':
@@ -910,10 +536,10 @@
910 536
911 537 break;
912 538
913 539 case 'tablet_extra':
914 - $slides_per_view = $l_col;
915 - $slides_per_group = $l_group;
540 + $slides_per_view = $l_col;
541 + $slides_per_group = $l_group;
916 542
917 543 if ( empty( $el_breakpoints['laptop'] ) ) {
918 544 $slides_per_view = $d_col;
919 545 $slides_per_group = $meta['d_group'];
@@ -938,11 +564,11 @@
938 564
939 565 break;
940 566
941 567 case 'mobile_extra':
942 - $slides_per_view = $t_col;
943 - $slides_per_group = $meta['t_group'];
944 - $dynamic_bullets = $has_dynamic_dots;
568 + $slides_per_view = $t_col;
569 + $slides_per_group = $meta['t_group'];
570 + $dynamic_bullets = true;
945 571
946 572 break;
947 573
948 574 case 'mobile':
@@ -947,9 +573,9 @@
947 573
948 574 case 'mobile':
949 575 $slides_per_view = $t_col;
950 576 $slides_per_group = $meta['t_group'];
951 - $dynamic_bullets = $has_dynamic_dots;
577 + $dynamic_bullets = true;
952 578
953 579 if ( ! empty( $el_breakpoints['mobile_extra'] ) ) {
954 580 $slides_per_view = $me_col;
955 581 $slides_per_group = $me_group;
@@ -975,9 +601,9 @@
975 601 0 => [
976 602 'slidesPerView' => absint( $m_col ),
977 603 'slidesPerGroup' => absint( $meta['m_group'] ),
978 604 'pagination' => [
979 - 'dynamicBullets' => $has_dynamic_dots,
605 + 'dynamicBullets' => true,
980 606 ],
981 607 ],
982 608 768 => [
983 609 'slidesPerView' => absint( $t_col ),
@@ -982,9 +608,9 @@
982 608 768 => [
983 609 'slidesPerView' => absint( $t_col ),
984 610 'slidesPerGroup' => absint( $meta['t_group'] ),
985 611 'pagination' => [
986 - 'dynamicBullets' => $has_dynamic_dots,
612 + 'dynamicBullets' => false,
987 613 ],
988 614 ],
989 615 1025 => [
990 616 'slidesPerView' => absint( $d_col ),
@@ -989,9 +615,9 @@
989 615 1025 => [
990 616 'slidesPerView' => absint( $d_col ),
991 617 'slidesPerGroup' => absint( $meta['d_group'] ),
992 618 'pagination' => [
993 - 'dynamicBullets' => $has_dynamic_dots,
619 + 'dynamicBullets' => false,
994 620 ],
995 621 ],
996 622 ];
997 623 }
@@ -1006,11 +632,8 @@
1006 632 'lazy' => $meta['lazy_load'],
1007 633 'breakpoints' => $breakpoints,
1008 634 ];
1009 635
1010 - if ( ! empty( $meta['raw_settings']['slider_slide_animation'] ) ) {
1011 - $slider_options['watchSlidesProgress'] = true;
1012 - }
1013 636 if ( $meta['auto_play'] ) {
1014 637 $slider_options['autoplay'] = [
1015 638 'delay' => absint( $meta['auto_play_timeout'] ),
1016 639 'pauseOnMouseEnter' => $meta['stop_on_hover'],
@@ -1017,18 +640,72 @@
1017 640 'disableOnInteraction' => false,
1018 641 ];
1019 642 }
1020 643
1021 - $slider_options = apply_filters( 'rtsb/elementor/render/slider_dataset', $slider_options, $meta );
1022 - $carouselClass = 'rtsb-carousel-slider slider-loading rtsb-pos-s ' . $meta['nav_position'] . '-nav' . $has_dots;
644 + $carouselClass = 'rtsb-carousel-slider slider-loading rtsb-pos-s ' . $meta['nav_position'] . '-nav' . $has_dots;
1023 645
1024 646 return [
1025 - 'data' => esc_js( wp_json_encode( $slider_options ) ),
647 + 'data' => ' data-options=\'' . esc_js( wp_json_encode( $slider_options ) ) . '\'',
1026 648 'class' => $carouselClass,
1027 649 ];
1028 650 }
1029 651
1030 652 /**
653 + * Renders pagination
654 + *
655 + * @param object $wp_query WP_Query object.
656 + * @param array $meta Meta values.
657 + * @param int $limit Post limit.
658 + * @param int $per_page Posts per page.
659 + *
660 + * @return string
661 + */
662 + public static function render_pagination( $wp_query, $meta, $limit, $per_page ) {
663 + $html_utility = null;
664 + $html = null;
665 + $ajax = false;
666 + $is_grid = preg_match( '/grid-layout/', $meta['layout'] ) || preg_match( '/list-layout/', $meta['layout'] );
667 + $posts_per_page = $wp_query->query_vars['posts_per_page'];
668 + $total_page = $wp_query->max_num_pages;
669 +
670 + if ( $limit && ( empty( $wp_query->query['tax_query'] ) ) ) {
671 + $found_post = $wp_query->found_posts;
672 +
673 + if ( $per_page && $found_post > $per_page ) {
674 + $found_post = $limit;
675 + $total_page = ceil( $found_post / $per_page );
676 + }
677 + }
678 +
679 + $total_page = absint( $total_page );
680 +
681 + $args = [
682 + 'total_page' => $total_page,
683 + 'posts_per_page' => $posts_per_page,
684 + 'ajax' => $ajax,
685 + ];
686 +
687 + if ( 'pagination' === $meta['posts_loading_type'] ) {
688 + if ( $is_grid ) {
689 + $html_utility .= Fns::custom_pagination(
690 + $total_page,
691 + $posts_per_page
692 + );
693 + }
694 + } else {
695 + ob_start();
696 + do_action( 'rtsb/elementor/render/pagination', $meta, $wp_query, $args );
697 + $html_utility .= ob_get_clean();
698 + }
699 +
700 + if ( $html_utility ) {
701 + $html .= '<div class="rtsb-pagination-wrap element-loading" data-total-pages="' . absint( $total_page ) . '" data-posts-per-page="' . absint( $posts_per_page ) . '" data-type="' . esc_attr( $meta['posts_loading_type'] ) . '">' . $html_utility . '</div>';
702 + }
703 +
704 + return $html;
705 + }
706 +
707 + /**
1031 708 * Preloader.
1032 709 *
1033 710 * @param string $layout Layout.
1034 711 *
@@ -1041,11 +718,9 @@
1041 718
1042 719 if ( $is_carousel ) {
1043 720 $loader_class = ' full-op';
1044 721 }
1045 - if ( ! Fns::enable_loader() ) {
1046 - return '';
1047 - }
722 +
1048 723 return '<div class="rtsb-elements-loading rtsb-ball-clip-rotate"><div></div></div>';
1049 724 }
1050 725
1051 726 /**
@@ -1086,11 +761,10 @@
1086 761 *
1087 762 * @return void
1088 763 */
1089 764 public static function register_scripts( $scripts ) {
1090 - $caro = false;
1091 - $masonry = false;
1092 - $script = [];
765 + $caro = false;
766 + $script = [];
1093 767
1094 768 $script[] = 'jquery';
1095 769
1096 770 foreach ( $scripts as $sc => $value ) {
@@ -1097,12 +771,8 @@
1097 771 if ( ! empty( $sc ) ) {
1098 772 if ( 'isCarousel' === $sc ) {
1099 773 $caro = $value;
1100 774 }
1101 -
1102 - if ( 'isMasonry' === $sc ) {
1103 - $masonry = $value;
1104 - }
1105 775 }
1106 776 }
1107 777
1108 778 if ( count( $scripts ) ) {
@@ -1112,18 +782,12 @@
1112 782 if ( $caro ) {
1113 783 $script[] = 'swiper';
1114 784 }
1115 785
1116 - if ( $masonry ) {
1117 - $script[] = 'masonry';
1118 - }
786 + $script[] = 'rtsb-imagesloaded';
787 + $script[] = 'rtsb-public';
1119 788
1120 - $script[] = 'imagesloaded';
1121 - $script[] = Fns::optimized_handle( 'rtsb-public' );
1122 -
1123 - foreach ( $script as $sc ) {
1124 - wp_enqueue_script( $sc );
1125 - }
789 + wp_enqueue_script( $script );
1126 790 }
1127 791 }
1128 792
1129 793 /**
@@ -1149,996 +813,6 @@
1149 813 return true;
1150 814 }
1151 815
1152 816 return false;
1153 - }
1154 -
1155 - /**
1156 - * Get the number of products per page based on WooCommerce settings.
1157 - *
1158 - * @return int
1159 - */
1160 - public static function get_products_per_page() {
1161 - $products_row = absint( get_option( 'woocommerce_catalog_rows', 4 ) );
1162 - $products_col = absint( get_option( 'woocommerce_catalog_columns', 4 ) );
1163 - $products_per_page = apply_filters( 'rtsb/elementor/archive/products_per_page', $products_row * $products_col );
1164 -
1165 - return ! empty( $products_per_page ) ? $products_per_page : 12;
1166 - }
1167 - /**
1168 - * Render Filters View.
1169 - *
1170 - * @param array $templates Template name.
1171 - * @param array $settings Control settings.
1172 - *
1173 - * @return string
1174 - */
1175 - public static function filters_view( $templates, $settings ) {
1176 - global $wp;
1177 - $filters = [];
1178 - $html = null;
1179 - $reset = ! empty( $settings['reset_btn'] );
1180 - $reset_mode = 'show' === $settings['reset_btn_behavior'] ? ' show-reset' : ' ondemand-reset';
1181 - $reset_text = ! empty( $settings['reset_btn_text'] ) ? $settings['reset_btn_text'] : esc_html__( 'Reset Filter', 'shopbuilder' );
1182 - $scroll_mode = ! empty( $settings['enable_scroll'] ) ? ' has-scroll' : ' no-scroll';
1183 - $scroll_height = ! empty( $settings['enable_scroll'] ) ? $settings['scroll_height']['size'] : 300;
1184 - $has_mobile_toggle = ! empty( $settings['filter_mobile_toggle'] );
1185 - $toggle_class = $has_mobile_toggle ? ' default-filter-has-toggle' : ' default-filter-no-toggle';
1186 - $mobile_toggle_text = ! empty( $settings['filter_mobile_toggle_text'] ) ? $settings['filter_mobile_toggle_text'] : esc_html__( 'Click to Filter', 'shopbuilder' );
1187 - $scroll_attr = '';
1188 -
1189 - if ( ! empty( $settings['enable_scroll'] ) ) {
1190 - $scroll_attr = ' style="--rtsb-filter-scroll-height: ' . absint( $scroll_height ) . 'px;"';
1191 - }
1192 -
1193 - foreach ( $settings['filter_types'] as $key => $item ) {
1194 - if ( ! defined( 'RTWPVS_PLUGIN_FILE' ) && in_array( $item['input_type_all'], [ 'color', 'button', 'image' ], true ) ) {
1195 - $item['input_type_all'] = 'checkbox';
1196 - }
1197 - $filters[] = [
1198 - 'filter_title' => $item['filter_title'],
1199 - 'filter_items' => $item['filter_items'],
1200 - 'filter_attr' => $item['filter_attr'],
1201 - 'input_type' => 'product_attr' === $item['filter_items'] ? $item['input_type_all'] : $item['input_type'],
1202 - ];
1203 -
1204 - if ( 'rating_filter' === $item['filter_items'] ) {
1205 - $filters[ $key ]['template'] = $templates['rating'];
1206 - } elseif ( 'price_filter' === $item['filter_items'] ) {
1207 -
1208 - $filters[ $key ]['input_type'] = 'slider';
1209 - $filters[ $key ]['template'] = $templates['price'];
1210 - } else {
1211 - $filters[ $key ]['template'] = 'product_attr' === $item['filter_items'] ? $templates[ $item['input_type_all'] ] : $templates[ $item['input_type'] ];
1212 - }
1213 - }
1214 -
1215 - if ( '' === get_option( 'permalink_structure' ) ) {
1216 - $form_action = remove_query_arg( [ 'page', 'paged', 'product-page' ], add_query_arg( $wp->query_string, '', home_url( $wp->request ) ) );
1217 - } else {
1218 - $form_action = preg_replace( '%\/page/[0-9]+%', '', home_url( trailingslashit( $wp->request ) ) );
1219 - }
1220 - $args = [
1221 - 'filters' => $filters,
1222 - 'scroll_mode' => $scroll_mode,
1223 - 'reset_mode' => $reset_mode,
1224 - 'reset' => $reset,
1225 - 'scroll_attr' => $scroll_attr,
1226 - 'toggle_class' => $toggle_class,
1227 - 'reset_text' => $reset_text,
1228 - 'settings' => $settings,
1229 - 'form_action' => $form_action,
1230 - ];
1231 -
1232 - $html .= '<div class="rtsb-default-archive-filters' . esc_attr( $toggle_class ) . '">';
1233 -
1234 - if ( $has_mobile_toggle ) {
1235 - $html .= '<div class="rtsb-filter-mobile-toggle">
1236 - <button class="product-filter-toggle">
1237 - <span class="icon"><i aria-hidden="true" class="toggle-icon rtsb-icon rtsb-icon-filter"></i></span>
1238 - <span class="text reset-text">' . esc_html( $mobile_toggle_text ) . '</span>
1239 - <span></span>
1240 - </button>
1241 - </div>';
1242 - }
1243 -
1244 - $html .= Fns::load_template( $templates['layout'], $args, true );
1245 - $html .= '</div>';
1246 - return $html;
1247 - }
1248 - /**
1249 - * Get taxonomy type.
1250 - *
1251 - * @param string $tax_type Taxonomy type.
1252 - * @param string $attr_type Attribute type.
1253 - *
1254 - * @return string
1255 - */
1256 - public static function get_product_filters_tax_type( $tax_type, $attr_type = '' ) {
1257 - if ( 'product_attr' !== $tax_type ) {
1258 - return $tax_type;
1259 - }
1260 -
1261 - if ( empty( $attr_type ) ) {
1262 - return $tax_type;
1263 - }
1264 -
1265 - return $attr_type;
1266 - }
1267 - /**
1268 - * Get attribute terms.
1269 - *
1270 - * @param string $tax Taxonomy type.
1271 - * @return int[]|string|string[]|\WP_Error|\WP_Term[]
1272 - */
1273 - public static function get_product_filters_attribute_terms( $tax ) {
1274 - $cache_key = 'get_default_filter_attribute_terms_' . $tax;
1275 - if ( isset( self::$cache[ $cache_key ] ) ) {
1276 - return self::$cache[ $cache_key ];
1277 - }
1278 -
1279 - $attributes = wc_get_attribute_taxonomies();
1280 - $att_name = str_replace( 'pa_', '', $tax );
1281 - $exist = array_search( $att_name, array_column( $attributes, 'attribute_name' ), true );
1282 - if ( false === $exist ) {
1283 - self::$cache[ $cache_key ] = [];
1284 - return self::$cache[ $cache_key ];
1285 - }
1286 - $attribute = $tax;
1287 -
1288 - if ( ! is_shop() && is_product_taxonomy() ) {
1289 - $term = get_queried_object();
1290 -
1291 - if ( ! $term instanceof WP_Term ) {
1292 - return [];
1293 - }
1294 -
1295 - $args = [
1296 - 'status' => 'publish',
1297 - 'limit' => -1,
1298 - 'return' => 'ids',
1299 - ];
1300 -
1301 - if ( 'product_cat' === $term->taxonomy ) {
1302 - $args['category'] = [ $term->slug ];
1303 - }
1304 -
1305 - if ( 'product_tag' === $term->taxonomy ) {
1306 - $args['tag'] = [ $term->slug ];
1307 - }
1308 -
1309 - if ( strpos( $term->taxonomy, 'pa_' ) !== false ) {
1310 - $args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1311 - [
1312 - 'taxonomy' => $term->taxonomy,
1313 - 'field' => 'term_id',
1314 - 'terms' => $term->term_id,
1315 - 'operator' => 'IN',
1316 - ],
1317 - ];
1318 - }
1319 -
1320 - $query = new WC_Product_Query( $args );
1321 - $products = $query->get_products();
1322 - $attr_terms = [];
1323 -
1324 - foreach ( $products as $product_id ) {
1325 - $product_attr = wp_get_post_terms( $product_id, $attribute );
1326 -
1327 - foreach ( $product_attr as $attr ) {
1328 - $attr_terms[] = $attr;
1329 - }
1330 - }
1331 - self::$cache[ $cache_key ] = self::filter_products_array_unique( $attr_terms );
1332 - return self::$cache[ $cache_key ];
1333 - } else {
1334 - self::$cache[ $cache_key ] = get_terms(
1335 - [
1336 - 'taxonomy' => $attribute,
1337 - 'hide_empty' => false,
1338 - ]
1339 - );
1340 - return self::$cache[ $cache_key ];
1341 - }
1342 - }
1343 - /**
1344 - * Remove duplicate values from an array.
1345 - *
1346 - * @param array $array The input array.
1347 - * @param bool $keep_key_assoc Whether to keep the key associations after removing duplicates.
1348 - * @return array The array with duplicates removed.
1349 - */
1350 - public static function filter_products_array_unique( $array, $keep_key_assoc = false ) {
1351 - $duplicate_keys = [];
1352 - $tmp = [];
1353 -
1354 - foreach ( $array as $key => $val ) {
1355 - // convert objects to arrays, in_array() does not support objects.
1356 - if ( is_object( $val ) ) {
1357 - $val = (array) $val;
1358 - }
1359 -
1360 - if ( ! in_array( $val, $tmp, true ) ) {
1361 - $tmp[] = $val;
1362 - } else {
1363 - $duplicate_keys[] = $key;
1364 - }
1365 - }
1366 -
1367 - foreach ( $duplicate_keys as $key ) {
1368 - unset( $array[ $key ] );
1369 - }
1370 -
1371 - return $keep_key_assoc ? $array : array_values( $array );
1372 - }
1373 - /**
1374 - * Retrieve product categories (including hierarchical support)
1375 - *
1376 - * @param string $taxonomy Taxonomy name.
1377 - * @return int[]|string|string[]|\WP_Error|\WP_Term[]
1378 - */
1379 - public static function get_all_terms( $taxonomy ) {
1380 - $args = [
1381 - 'taxonomy' => $taxonomy,
1382 - 'suppress_filter' => true,
1383 - 'hide_empty' => false,
1384 - 'pad_counts' => true,
1385 - 'hierarchical' => true,
1386 - ];
1387 -
1388 - // Archive-scope only when the URL path itself is a term archive
1389 - // (e.g. /product-category/clothing/). On the shop page WordPress can
1390 - // resolve ?product_cat=X to a queried term, but in that case we want
1391 - // the full hierarchy to remain visible for additional filtering.
1392 - $archive_term = self::request_path_is_term_archive( $taxonomy ) ? self::resolve_archive_term_from_path( $taxonomy ) : null;
1393 -
1394 - // The cache key MUST include the archive term id. The scoped list is
1395 - // different for every term archive, so a taxonomy-only key leaks the
1396 - // first visited category's term list into every other category archive
1397 - // when a persistent object cache is active - the leaked terms are then
1398 - // counted as empty in the current archive and the whole filter renders
1399 - // blank.
1400 - $cache_key = $archive_term instanceof WP_Term
1401 - ? 'rtsb_get_terms_filter_products_archive_' . $taxonomy . '_' . $archive_term->term_id
1402 - : 'rtsb_get_terms_filter_products_full_' . $taxonomy;
1403 -
1404 - $taxonomy_terms = wp_cache_get( $cache_key, Cache::group() );
1405 -
1406 - if ( false === $taxonomy_terms ) {
1407 - if ( $archive_term instanceof WP_Term ) {
1408 - // Treat the URL-derived term as the root of the visible
1409 - // hierarchy so the recursive renderer in
1410 - // get_product_default_filter_list_html() (which starts
1411 - // with $parent = 0) picks it up on child-category
1412 - // archives. Clone first to avoid mutating WP's term
1413 - // object.
1414 - $current_term = clone $archive_term;
1415 - $current_term->parent = 0;
1416 - $taxonomy_terms = [ $current_term ];
1417 - $child_args = [
1418 - 'taxonomy' => $taxonomy,
1419 - 'child_of' => $archive_term->term_id,
1420 - 'hide_empty' => false,
1421 - ];
1422 -
1423 - $child_terms = get_terms( $child_args );
1424 -
1425 - if ( ! empty( $child_terms ) && ! is_wp_error( $child_terms ) ) {
1426 - $taxonomy_terms = array_merge( $taxonomy_terms, $child_terms );
1427 - }
1428 - } else {
1429 - $taxonomy_terms = get_terms( $args );
1430 - }
1431 -
1432 - wp_cache_set( $cache_key, $taxonomy_terms, Cache::group(), 12 * HOUR_IN_SECONDS );
1433 - Cache::set_data_cache_key( $cache_key );
1434 - }
1435 -
1436 - return $taxonomy_terms;
1437 - }
1438 -
1439 - /**
1440 - * Resolve the archive term from the request URL path. Avoids relying on
1441 - * get_queried_object() which can return an unexpected term when
1442 - * ?product_cat=a,b,c is also present in the request.
1443 - *
1444 - * @param string $taxonomy Taxonomy slug.
1445 - *
1446 - * @return \WP_Term|null
1447 - */
1448 - private static function resolve_archive_term_from_path( $taxonomy ) {
1449 - $rewrite_slugs = self::get_taxonomy_archive_slugs( $taxonomy );
1450 -
1451 - if ( empty( $rewrite_slugs ) ) {
1452 - return null;
1453 - }
1454 -
1455 - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Path only used for slug extraction.
1456 - $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
1457 - // phpcs:enable
1458 - $path = (string) wp_parse_url( $request_uri, PHP_URL_PATH );
1459 -
1460 - if ( '' === $path ) {
1461 - return null;
1462 - }
1463 -
1464 - foreach ( $rewrite_slugs as $base ) {
1465 - $base = trim( $base, '/' );
1466 - $needle = '/' . $base . '/';
1467 -
1468 - $pos = strpos( $path, $needle );
1469 - if ( false === $pos ) {
1470 - continue;
1471 - }
1472 -
1473 - $remainder = substr( $path, $pos + strlen( $needle ) );
1474 - $segments = array_values(
1475 - array_filter(
1476 - explode( '/', $remainder ),
1477 - static function ( $segment ) {
1478 - return '' !== $segment;
1479 - }
1480 - )
1481 - );
1482 -
1483 - if ( empty( $segments ) ) {
1484 - continue;
1485 - }
1486 -
1487 - $slug = sanitize_title( end( $segments ) );
1488 - $term = get_term_by( 'slug', $slug, $taxonomy );
1489 -
1490 - if ( $term instanceof WP_Term ) {
1491 - return $term;
1492 - }
1493 - }
1494 -
1495 - return null;
1496 - }
1497 -
1498 - /**
1499 - * Decide whether the current REQUEST_URI path represents a real term
1500 - * archive page for the given taxonomy. URL-based so it ignores cases
1501 - * where WordPress merely resolves a query string into a queried term
1502 - * on the shop page.
1503 - *
1504 - * @param string $taxonomy Taxonomy slug.
1505 - *
1506 - * @return bool
1507 - */
1508 - private static function request_path_is_term_archive( $taxonomy ) {
1509 - $rewrite_slugs = self::get_taxonomy_archive_slugs( $taxonomy );
1510 -
1511 - if ( empty( $rewrite_slugs ) ) {
1512 - return false;
1513 - }
1514 -
1515 - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Path only used in match.
1516 - $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
1517 - // phpcs:enable
1518 - $path = (string) wp_parse_url( $request_uri, PHP_URL_PATH );
1519 -
1520 - if ( '' === $path ) {
1521 - return false;
1522 - }
1523 -
1524 - foreach ( $rewrite_slugs as $slug ) {
1525 - if ( '' === $slug ) {
1526 - continue;
1527 - }
1528 - if ( false !== strpos( $path, '/' . trim( $slug, '/' ) . '/' ) ) {
1529 - return true;
1530 - }
1531 - }
1532 -
1533 - return false;
1534 - }
1535 -
1536 - /**
1537 - * Resolve the URL slug(s) used by a taxonomy archive (e.g. product_cat
1538 - * maps to "product-category"). Falls back to common WooCommerce defaults
1539 - * when the taxonomy object is unavailable.
1540 - *
1541 - * @param string $taxonomy Taxonomy slug.
1542 - *
1543 - * @return array<string>
1544 - */
1545 - private static function get_taxonomy_archive_slugs( $taxonomy ) {
1546 - $slugs = [];
1547 -
1548 - if ( taxonomy_exists( $taxonomy ) ) {
1549 - $tax_obj = get_taxonomy( $taxonomy );
1550 - if ( $tax_obj && ! empty( $tax_obj->rewrite['slug'] ) ) {
1551 - $slugs[] = $tax_obj->rewrite['slug'];
1552 - }
1553 - }
1554 -
1555 - $defaults = [
1556 - 'product_cat' => 'product-category',
1557 - 'product_tag' => 'product-tag',
1558 - 'product_brand' => 'product-brand',
1559 - ];
1560 -
1561 - if ( isset( $defaults[ $taxonomy ] ) ) {
1562 - $slugs[] = $defaults[ $taxonomy ];
1563 - }
1564 -
1565 - return array_unique( array_filter( $slugs ) );
1566 - }
1567 - /**
1568 - * Generates an error message block.
1569 - *
1570 - * @param array $title The title array of the error message block.
1571 - * @param string $wrapper Additional CSS class for the wrapper div.
1572 - *
1573 - * @return string The HTML representation of the error message block.
1574 - */
1575 - public static function filter_error_message( $title, $wrapper ) {
1576 - $html = '<div class="rtsb-product-default-filters ' . esc_attr( $wrapper ) . '">';
1577 - $html .= self::get_default_filter_title( $title );
1578 - $html .= '<div class="default-filter-content">';
1579 - $html .= '<p class="no-filter">' . esc_html__( 'Nothing found.', 'shopbuilder' ) . '</p>';
1580 - $html .= '</div>';
1581 - $html .= '</div>';
1582 -
1583 - return $html;
1584 - }
1585 - /**
1586 - * Get filter title.
1587 - *
1588 - * @param array $title Filter title.
1589 - * @return string
1590 - */
1591 - public static function get_default_filter_title( $title ) {
1592 - $html = '';
1593 -
1594 - if ( empty( $title['title'] ) ) {
1595 - return $html;
1596 - }
1597 -
1598 - $html .= '<div class="default-filter-title-wrapper">';
1599 -
1600 - $html .= '<h3 class="widget-title rtsb-d-flex rtsb-align-items-center"><span class="title">' . $title['title'] . '</span></h3>';
1601 -
1602 - $html .= '</div>';
1603 -
1604 - return apply_filters( 'rtsb/elementor/default/filter/title', $html, $title );
1605 - }
1606 - /**
1607 - * Recursive function to generate the filter list with hierarchy.
1608 - *
1609 - * @param array $data Array of required data.
1610 - * @param string $input Type of input field for the filter (e.g., checkbox).
1611 - * @param array $additional_data Data for additional filtering.
1612 - * @param int $parent ID of the parent term (default: 0).
1613 - *
1614 - * @return string The generated HTML for the product filter list.
1615 - */
1616 - public static function get_product_default_filter_list_html( $data, $input = 'checkbox', $additional_data = [], $parent = 0 ) {
1617 -
1618 - $html = '';
1619 - $default_tax = [];
1620 - $is_attribute = ! empty( $data['is_attribute'] );
1621 -
1622 - $param_key = isset( $additional_data['taxonomy'] ) ? $additional_data['taxonomy'] : '';
1623 - if ( $param_key && 0 === strpos( $param_key, 'pa_' ) ) {
1624 - $param_key = 'filter_' . substr( $param_key, 3 );
1625 - }
1626 -
1627 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1628 - $raw_values = ( $param_key && isset( $_GET[ $param_key ] ) ) ? wc_clean( wp_unslash( $_GET[ $param_key ] ) ) : '';
1629 - $active_option = $raw_values ? array_map( 'urldecode', explode( ',', $raw_values ) ) : [];
1630 - $counter = 0;
1631 -
1632 - if ( $is_attribute ) {
1633 - list(
1634 - 'filter_name' => $filter_name,
1635 - 'base_link' => $base_link
1636 - ) = self::get_product_filters_attribute_term_info( $additional_data['taxonomy'] );
1637 - }
1638 -
1639 - $html .= '<ul class="product-default-filters input-type-' . esc_attr( $input ) . '">';
1640 -
1641 - foreach ( $data['taxonomy'] as $tax ) {
1642 - if ( $tax->parent !== $parent ) {
1643 - continue;
1644 - }
1645 -
1646 - if ( $is_attribute ) {
1647 - $tax_link = remove_query_arg( $filter_name, $base_link );
1648 - $tax_link = add_query_arg( $filter_name, $tax->slug, $tax_link );
1649 - } else {
1650 - $tax_link = get_term_link( $tax );
1651 - }
1652 -
1653 - $unique_id = substr( uniqid(), -4 );
1654 -
1655 - $decoded_slug = urldecode( $tax->slug );
1656 - $is_active = in_array( $decoded_slug, $active_option, true ) || in_array( $tax->slug, $active_option, true );
1657 - $active_tax = $is_active ? ' checked' : '';
1658 - $active_tax_parent = $is_active ? ' active' : '';
1659 - $term_children = in_array( $tax->taxonomy, [ 'product_cat', 'product_brand' ], true ) ? get_term_children( $tax->term_id, $tax->taxonomy ) : [];
1660 - $taxonomy = $tax->taxonomy;
1661 - $product_count = self::get_visible_term_count( $tax->taxonomy, $tax->term_id );
1662 -
1663 - // On a product taxonomy archive (category/tag/attribute page) scope the
1664 - // count to the current archive - only products that are in THIS archive
1665 - // AND have this term. This makes the displayed count reflect the current
1666 - // category context and lets terms that are empty in this archive be
1667 - // dropped from the list.
1668 - if ( is_product_taxonomy() || 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) {
1669 - $product_count = self::count_tax_data( $tax, $product_count );
1670 - }
1671 -
1672 - // Show Empty Categories.
1673 - if ( empty( $data['show_empty_terms'] ) && 0 === $product_count ) {
1674 - continue;
1675 - }
1676 -
1677 - $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' : '' ) . '">
1678 - <div class="rtsb-default-filter-group' . esc_attr( $active_tax_parent ) . '">
1679 - <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 ) . '>
1680 - <label class="rtsb-default-' . esc_attr( $input ) . '-filter-label" for="rtsb-default-term-filter-' . $unique_id . absint( $tax->term_id ) . '">
1681 - <span class="name">' . esc_html( $tax->name ) . '</span>
1682 - </label>
1683 - <div class="rtsb-product-count">(' . esc_html( $product_count ) . ')</div>
1684 - ' . ( ! empty( $term_children ) && $data['show_child'] ? '<i class="rtsb-plus-icon"></i>' : '' ) . '
1685 - </div>';
1686 -
1687 - // Show Sub-categories.
1688 - if ( $data['show_child'] ) {
1689 - $children = self::get_product_default_filter_list_html( $data, $input, $additional_data, $tax->term_id );
1690 -
1691 - if ( ! empty( $children ) ) {
1692 - $html .= '<div class="filter-child">' . $children . '</div>';
1693 - }
1694 - }
1695 -
1696 - $counter++;
1697 -
1698 - $html .= '</li>';
1699 - }
1700 -
1701 - $html .= '</ul>';
1702 -
1703 - // Check if the output contains any child elements.
1704 - $has_children = strpos( $html, '<li class="rtsb-default-filter-term-item' ) !== false;
1705 -
1706 - if ( ! $has_children ) {
1707 - // Remove the outer <ul> if there are no child elements.
1708 - $html = '';
1709 - }
1710 - return $html;
1711 - }
1712 - /**
1713 - * Get attribute term info.
1714 - *
1715 - * @param string $tax Taxonomy type.
1716 - * @return array
1717 - */
1718 - public static function get_product_filters_attribute_term_info( $tax ) {
1719 - $result = [];
1720 - $attributes = wc_get_attribute_taxonomies();
1721 -
1722 - foreach ( $attributes as $attr ) {
1723 - if ( str_replace( 'pa_', '', $tax ) === $attr->attribute_name ) {
1724 - $term_type = $attr->attribute_type;
1725 - $attribute_slug = $attr->attribute_name;
1726 - $attribute = wc_attribute_taxonomy_name( $attr->attribute_name );
1727 - break;
1728 - }
1729 - }
1730 -
1731 - $filter_name = 'filter_' . wc_attribute_taxonomy_slug( $attribute_slug );
1732 -
1733 - $result['attribute'] = $attribute ?? '';
1734 - $result['term_type'] = $term_type ?? '';
1735 - $result['attribute_slug'] = $attribute_slug ?? '';
1736 - $result['filter_name'] = $filter_name;
1737 - $result['base_link'] = self::get_base_url();
1738 -
1739 - return $result;
1740 - }
1741 - /**
1742 - * Get base URL.
1743 - *
1744 - * @return string|null
1745 - */
1746 - public static function get_base_url() {
1747 - global $wp;
1748 - return home_url( add_query_arg( [], $wp->request ) );
1749 - }
1750 - /**
1751 - * Count the number of occurrences for a specific term in a taxonomy for the current queried object.
1752 - *
1753 - * @param object|array $tax The term object for the taxonomy.
1754 - * @param int $count The initial count value.
1755 - *
1756 - * @return int The updated count value.
1757 - */
1758 - public static function count_tax_data( $tax, $count ) {
1759 - $page = get_queried_object();
1760 -
1761 - // Scoping only applies on a real taxonomy archive where the queried
1762 - // object is a term. On other contexts (e.g. the shop page) the queried
1763 - // object is not a term, so keep the original (global) count.
1764 - if ( ! $page instanceof \WP_Term ) {
1765 - return $count;
1766 - }
1767 -
1768 - if ( is_array( $tax ) && strpos( $tax['taxonomy'], 'attribute_' ) !== false ) {
1769 - $taxonomy = str_replace( 'attribute_', '', $tax['taxonomy'] );
1770 - $term_id = $tax['term_id'];
1771 - } else {
1772 - $taxonomy = $tax->taxonomy;
1773 - $term_id = $tax->term_id;
1774 - }
1775 -
1776 - // A taxonomy selected through the query string is a removable choice,
1777 - // not a fixed archive context, and the filter ORs terms within one
1778 - // taxonomy. Scoping a term's count to that selection therefore
1779 - // advertises a result the filter never produces - on
1780 - // /shop/?product_cat=aliens the Movies count read 32 while choosing
1781 - // Movies returns 279 - and it hides every sibling term whose overlap
1782 - // with the selection is zero. Only a real term-archive URL
1783 - // (/product-category/aliens/) is a fixed context worth scoping to.
1784 - if ( $taxonomy === $page->taxonomy && ! self::request_path_is_term_archive( $page->taxonomy ) ) {
1785 - return $count;
1786 - }
1787 -
1788 - if ( strpos( $page->taxonomy, 'pa_' ) !== false ) {
1789 - $args['status'] = 'publish';
1790 - $args['limit'] = -1;
1791 - $args['return'] = 'ids';
1792 - $args['tax_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1793 - 'relation' => 'AND',
1794 - [
1795 - 'taxonomy' => $page->taxonomy,
1796 - 'field' => 'term_id',
1797 - 'terms' => $page->term_id,
1798 - 'operator' => 'IN',
1799 - ],
1800 - [
1801 - 'taxonomy' => $tax->taxonomy,
1802 - 'field' => 'term_id',
1803 - 'terms' => $tax->term_id,
1804 - 'operator' => 'IN',
1805 - ],
1806 - [
1807 - 'taxonomy' => 'product_visibility',
1808 - 'terms' => [ 'exclude-from-catalog' ],
1809 - 'field' => 'name',
1810 - 'operator' => 'NOT IN',
1811 - ],
1812 - ];
1813 -
1814 - return Fns::count_products_by_tax_query( $args['tax_query'] );
1815 - }
1816 -
1817 - // Scope the count to the current archive term (category, brand, tag, etc.)
1818 - // so it reflects only products that are in this archive AND also have the
1819 - // filter term. Works for cross-taxonomy filters (e.g. brands on a tag
1820 - // archive) and same-taxonomy filters (e.g. the Tags filter on a tag
1821 - // archive shows the intersection with the current tag).
1822 - $args = [
1823 - 'limit' => -1,
1824 - 'return' => 'ids',
1825 - 'status' => 'publish',
1826 - 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1827 - 'relation' => 'AND',
1828 - [
1829 - 'taxonomy' => $taxonomy,
1830 - 'field' => 'term_id',
1831 - 'terms' => $term_id,
1832 - ],
1833 - [
1834 - 'taxonomy' => $page->taxonomy,
1835 - 'field' => 'term_id',
1836 - 'terms' => $page->term_id,
1837 - ],
1838 - [
1839 - 'taxonomy' => 'product_visibility',
1840 - 'terms' => [ 'exclude-from-catalog' ],
1841 - 'field' => 'name',
1842 - 'operator' => 'NOT IN',
1843 - ],
1844 - ],
1845 - ];
1846 -
1847 - return Fns::count_products_by_tax_query( $args['tax_query'] );
1848 - }
1849 -
1850 - /**
1851 - * Get the count of visible (non-hidden) products for a given taxonomy term.
1852 - *
1853 - * Excludes products with WooCommerce visibility set to 'Hidden'
1854 - * (products assigned the 'exclude-from-catalog' term).
1855 - *
1856 - * @param string $taxonomy The taxonomy name.
1857 - * @param int $term_id The term ID.
1858 - *
1859 - * @return int Visible product count.
1860 - */
1861 - public static function get_visible_term_count( $taxonomy, $term_id ) {
1862 - static $cache = [];
1863 -
1864 - $cache_key = $taxonomy . '_' . $term_id;
1865 -
1866 - if ( isset( $cache[ $cache_key ] ) ) {
1867 - return $cache[ $cache_key ];
1868 - }
1869 -
1870 - $args = [
1871 - 'limit' => -1,
1872 - 'return' => 'ids',
1873 - 'status' => 'publish',
1874 - 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
1875 - 'relation' => 'AND',
1876 - [
1877 - 'taxonomy' => $taxonomy,
1878 - 'field' => 'term_id',
1879 - 'terms' => $term_id,
1880 - ],
1881 - [
1882 - 'taxonomy' => 'product_visibility',
1883 - 'terms' => [ 'exclude-from-catalog' ],
1884 - 'field' => 'name',
1885 - 'operator' => 'NOT IN',
1886 - ],
1887 - ],
1888 - ];
1889 -
1890 - $count = Fns::count_products_by_tax_query( $args['tax_query'] );
1891 -
1892 - $cache[ $cache_key ] = $count;
1893 -
1894 - return $count;
1895 - }
1896 -
1897 - /**
1898 - * Get filter HTML.
1899 - *
1900 - * @param string $filter_type Filter type.
1901 - * @param array $options Options.
1902 - * @param array $additional_data Data for additional filtering.
1903 - * @param string $input Input type.
1904 - *
1905 - * @return string The generated HTML for the attribute filter list.
1906 - */
1907 - public static function get_default_filter_html( $filter_type, $options, $additional_data = [], $input = 'checkbox' ) {
1908 - $html = '<ul class="product-filters input-type-' . esc_attr( $input ) . '" ';
1909 - $active_option = isset( $_GET[ $filter_type ] ) ? explode( ',', wc_clean( wp_unslash( $_GET[ $filter_type ] ) ) ) : []; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1910 - $option_link = self::get_base_url() . '/' . $filter_type . '/';
1911 -
1912 - foreach ( $options as $option => $option_name ) {
1913 - $unique_id = substr( uniqid(), -6 );
1914 - $active_tax = in_array( $option, $active_option, true ) ? ' checked' : '';
1915 - $active_group = in_array( $option, $active_option, true ) ? ' active' : '';
1916 -
1917 - $html .= '
1918 - <li class="rtsb-term-item">
1919 - <div class="rtsb-default-filter-group' . esc_attr( $active_group ) . '">
1920 - <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 . '>
1921 - <label class="rtsb-default-' . esc_attr( $input ) . '-filter-label" for="rtsb-term-default-filter-' . $unique_id . '">
1922 - <span class="name">' . esc_html( $option_name ) . '</span>
1923 - </label>
1924 - </div>
1925 - </li>';
1926 - }
1927 -
1928 - $html .= '</ul>';
1929 -
1930 - return $html;
1931 - }
1932 - /**
1933 - * Get the count of products with a specific star rating.
1934 - *
1935 - * @param int $star_rating The star rating value.
1936 - *
1937 - * @return int
1938 - */
1939 - public static function get_product_rating_count( $star_rating ) {
1940 - $args = [
1941 - 'status' => 'publish',
1942 - 'limit' => -1,
1943 - 'product_rating' => sprintf( '%.1f', $star_rating ),
1944 - ];
1945 -
1946 - if ( is_product_taxonomy() ) {
1947 - $term = get_queried_object();
1948 - $term_type = 'product_cat' === $term->taxonomy ? 'product_category_id' : 'product_tag_id';
1949 -
1950 - if ( $term instanceof WP_Term ) {
1951 - $args[ $term_type ] = [ $term->term_id ];
1952 - }
1953 - }
1954 -
1955 - $query = new WC_Product_Query( $args );
1956 - $products = $query->get_products();
1957 -
1958 - return count( $products );
1959 - }
1960 - /**
1961 - * Get attribute filter HTML.
1962 - *
1963 - * @param array $data Array of required data.
1964 - * @param string $input Type of input field for the filter (e.g., color).
1965 - *
1966 - * @return string The generated HTML for the attribute filter list.
1967 - */
1968 - public static function get_attribute_filter_list_html( $data, $input = 'color' ) {
1969 - if ( ! function_exists( 'rtwpvs' ) ) {
1970 - return '';
1971 - }
1972 -
1973 - $terms = $data['terms'];
1974 - $term_info = $data['term_info'];
1975 - $show_tooltips = $data['show_tooltips'];
1976 - $show_empty_terms = $data['show_empty_terms'];
1977 - $counter = 0;
1978 -
1979 - $html = '<ul class="product-default-filters rtsb-terms-wrapper ' . esc_attr( $term_info['type'] ) . '-variable-wrapper' . esc_attr( $data['show_label'] ) . '">';
1980 -
1981 - foreach ( $terms as $attr_term ) {
1982 - $count = self::get_visible_term_count( $attr_term->taxonomy, $attr_term->term_id );
1983 - $term_link = remove_query_arg( $term_info['filter_name'], $term_info['base_link'] );
1984 - $term_link = add_query_arg( $term_info['filter_name'], $attr_term->slug, $term_link );
1985 - $unique_id = substr( uniqid(), -6 );
1986 - $name = 'term-' . wc_variation_attribute_name( $term_info['attribute'] ) . '-' . $unique_id;
1987 - $decoded_slug = urldecode( $attr_term->slug );
1988 - $is_selected = in_array( $decoded_slug, $term_info['current_filter'], true ) || in_array( $attr_term->slug, $term_info['current_filter'], true );
1989 - $selected_class = $is_selected ? ' selected' : '';
1990 - $selected_item = $is_selected ? ' checked' : '';
1991 - $active_class = $is_selected ? ' active' : '';
1992 -
1993 - $args = [
1994 - 'id' => $name,
1995 - 'name' => $attr_term->name,
1996 - 'link' => $term_link,
1997 - 'type' => $term_info['type'],
1998 - 'term_id' => $attr_term->term_id,
1999 - 'term_slug' => $attr_term->slug,
2000 - 'taxonomy' => wc_variation_attribute_name( $term_info['attribute'] ),
2001 - 'tooltips' => $show_tooltips,
2002 - 'attribute_name' => $term_info['attribute'],
2003 - 'selected_item' => $selected_item,
2004 - ];
2005 -
2006 - if ( 'archive' === apply_filters( 'rtsb/builder/set/current/page/type', '' ) && ! empty( $data['count_display'] ) && 'block' === $data['count_display'] ) {
2007 - $count = self::count_tax_data( $args, $count );
2008 - }
2009 -
2010 - $args['count'] = $count;
2011 -
2012 - if ( ! $show_empty_terms && 0 === $count ) {
2013 - continue;
2014 - }
2015 -
2016 - $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 ) . '">';
2017 - $html .= '<div class="rtsb-default-filter-group' . esc_attr( $active_class ) . '">';
2018 - $html .= self::get_input_type_html( $input, $args );
2019 -
2020 - $counter++;
2021 -
2022 - $html .= '</div>';
2023 - $html .= '</li>';
2024 - }
2025 -
2026 - $html .= '</ul>';
2027 -
2028 - return $html;
2029 - }
2030 - /**
2031 - * Get input type HTML.
2032 - *
2033 - * @param string $input Input type.
2034 - * @param array $args Args.
2035 - * @return mixed|string|null
2036 - */
2037 - public static function get_input_type_html( $input, $args ) {
2038 - $html = '';
2039 - $count = $args['count'];
2040 -
2041 - switch ( $input ) {
2042 - case 'color':
2043 - $color = sanitize_hex_color( get_term_meta( $args['term_id'], 'product_attribute_color', true ) );
2044 -
2045 - $html .= sprintf(
2046 - // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
2047 - '<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>',
2048 - esc_attr( $args['id'] ),
2049 - esc_attr( $args['type'] ),
2050 - $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
2051 - esc_attr( $color ),
2052 - esc_html( $args['name'] ),
2053 - absint( $count )
2054 - );
2055 - break;
2056 -
2057 - case 'image':
2058 - if ( ! function_exists( 'rtwpvs' ) ) {
2059 - return $html;
2060 - }
2061 -
2062 - $attachment_id = absint( get_term_meta( $args['term_id'], 'product_attribute_image', true ) );
2063 - $image_size = rtwpvs()->get_option( 'attribute_image_size' );
2064 - $image_url = wp_get_attachment_image_url( $attachment_id, apply_filters( 'rtsb_product_attribute_image_size', $image_size ) );
2065 -
2066 - $html .= sprintf(
2067 - // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
2068 - '<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>',
2069 - esc_attr( $args['id'] ),
2070 - esc_attr( $args['type'] ),
2071 - $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
2072 - esc_attr( $args['name'] ),
2073 - esc_url( $image_url )
2074 - );
2075 - break;
2076 -
2077 - case 'button':
2078 - $html .= sprintf(
2079 - // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
2080 - '<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>',
2081 - esc_attr( $args['id'] ),
2082 - esc_attr( $args['type'] ),
2083 - $args['tooltips'] ? esc_attr( ' tipsy' ) : '',
2084 - esc_html( $args['name'] ),
2085 - absint( $count )
2086 - );
2087 - break;
2088 -
2089 - default:
2090 - }
2091 -
2092 - return apply_filters( 'rtsb/elementor/default/filter/get_input_type_html', $html );
2093 - }
2094 - /**
2095 - * Get reset button.
2096 - *
2097 - * @param string $btn_text Button text.
2098 - * @param array $settings Settings array.
2099 - *
2100 - * @return string
2101 - */
2102 - public static function get_default_filter_reset_button( $btn_text, $settings ) {
2103 - $html = '';
2104 -
2105 - ob_start();
2106 -
2107 - $is_editor = ! empty( \Elementor\Plugin::$instance->editor ) && \Elementor\Plugin::$instance->editor->is_edit_mode();
2108 - $active = ( $is_editor || ! empty( $_SERVER['QUERY_STRING'] ) ) ? ' active' : '';
2109 - $reset = ! empty( $settings['reset_btn'] );
2110 - $apply_text = ! empty( $settings['apply_filters_btn_text'] ) ? $settings['apply_filters_btn_text'] : esc_html__( 'Apply Filters', 'shopbuilder' );
2111 - $apply_icon = ! empty( $settings['apply_filters_btn_icon'] ) ? $settings['apply_filters_btn_icon'] : [];
2112 - echo '<div class="default-filter-btn-wrapper">';
2113 - ?>
2114 - <?php if ( $settings['show_filter_btn'] ) { ?>
2115 - <div class="rtsb-product-default-filters rtsb-apply-filters-btn">
2116 - <div class="reset-btn-item">
2117 - <button class="rtsb-apply-filters init">
2118 - <span class="icon"><?php Fns::print_html( Fns::icons_manager( $apply_icon, 'icon-default' ) ); ?></span>
2119 - <span class="text reset-text"><?php echo esc_html( $apply_text ); ?></span>
2120 - <span></span>
2121 - </button>
2122 - </div>
2123 - </div>
2124 - <?php } ?>
2125 - <?php
2126 - if ( $reset ) {
2127 - ?>
2128 - <div class="rtsb-product-default-filters rtsb-reset<?php echo esc_attr( $active ); ?>">
2129 - <div class="reset-btn-item">
2130 - <button class="product-default-filter-reset">
2131 - <span class="text reset-text"><?php echo esc_html( $btn_text ); ?></span>
2132 - <span></span>
2133 - </button>
2134 - </div>
2135 - </div>
2136 - <?php
2137 - }
2138 - echo '</div>';
2139 -
2140 - $html .= ob_get_clean();
2141 -
2142 - return $html;
2143 817 }
2144 818 }