PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.3.0
ShopBuilder – WooCommerce Builder For Elementor v3.3.0
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/Render/Render.php +928 -411 2.1.2 → 3.3.0 View file →
@@ -1,9 +1,9 @@
1 1 <?php
2 2 /**
3 3 * Elementor Render Class.
4 4 *
5 - * This class contains render logics & output.
5 + * This class contains render logics & output. It utilizes WC_Products_Query().
6 6 *
7 7 * @package RadiusTheme\SB
8 8 */
9 9
@@ -8,10 +8,14 @@
8 8 */
9 9
10 10 namespace RadiusTheme\SB\Elementor\Render;
11 11
12 +use RadiusTheme\SBPRO\Modules\CatalogMode\CatalogModeFns;
13 +use WC_Query;
12 14 use WP_Query;
13 15 use WC_Product;
16 +use Elementor\Utils;
17 +use WC_Product_Query;
14 18 use RadiusTheme\SB\Helpers\Fns;
15 19 use RadiusTheme\SB\Models\QueryArgs;
16 20 use RadiusTheme\SB\Helpers\BuilderFns;
17 21 use RadiusTheme\SB\Traits\SingletonTrait;
@@ -51,8 +55,15 @@
51 55 */
52 56 public $get_settings;
53 57
54 58 /**
59 + * Element attributes.
60 + *
61 + * @var array
62 + */
63 + private $attributes = [];
64 +
65 + /**
55 66 * Get elementor settings.
56 67 *
57 68 * @return array
58 69 */
@@ -67,140 +78,81 @@
67 78 * @param array $settings Settings.
68 79 * @param string $class Class.
69 80 * @param string $template Template name.
70 81 * @param bool $fullwidth Fullwidth check.
71 - * @param bool $ajax_attr Ajax attributes.
82 + * @param bool $ajax Ajax attributes.
72 83 *
73 84 * @return string
74 85 */
75 - public function container( $content, $settings, $class = '', $template = '', $fullwidth = false, $ajax_attr = true ) {
76 - $layout_id = 'rtsb-container-' . absint( wp_rand() );
77 - $container_attr = null;
78 - $cart_txt_class = '';
79 - $container_class = 'rtsb-elementor-container products rtsb-pos-r ' . esc_attr( $class );
80 - $metas = RenderHelpers::meta_dataset( $settings, $template );
86 + public function container( $content, $settings, $class = '', $template = '', $fullwidth = false, $ajax = true ) {
87 + $rand = wp_rand();
88 + $layout_id = 'rtsb-container-' . $rand;
89 + $metas = RenderHelpers::meta_dataset( $settings, $template );
90 + $raw_layout = esc_html( $metas['layout'] );
91 + $layout = RenderHelpers::filter_layout( $raw_layout, $template );
92 + $style_attr = '--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) );
93 + $view_mode = $settings['view_mode'] ?? 'grid';
94 + $view_mode = isset( $_GET['displayview'] ) ? sanitize_text_field( wp_unslash( $_GET['displayview'] ) ) : $view_mode; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
95 + $tooltip_attr = 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ? esc_attr( $metas['tooltip_position_list'] ) : $metas['tooltip_position'];
96 + $has_pro = rtsb()->has_pro();
97 + $has_filters = $has_pro && ! empty( $settings['tax_filter'] );
98 + $grid_style = $settings['grid_style'] ?? 'even';
81 99
82 - $layout = $metas['layout'];
83 - $animation = $metas['hover_animation'];
84 - $animation .= ' gallery-hover-' . ( ! empty( $settings['gallery_hover_animation'] ) ? $settings['gallery_hover_animation'] : 'fade' );
85 -
86 - $is_cat = preg_match( '/category/', $layout );
87 - $is_carousel = preg_match( '/slider/', $layout );
88 -
89 - if ( ! $is_cat ) {
90 - $cart_txt_class = $metas['show_cart_text'] ? ' has-cart-text' : ' no-cart-text';
91 - }
92 -
93 - if ( $is_carousel ) {
94 - $fullwidth = true;
95 - }
96 -
97 - $badge_class = [
98 - 'position' => $metas['badge_position'],
99 - 'alignment' => $metas['badge_alignment'],
100 - ];
101 -
102 - if ( ! rtsb()->has_pro() && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
100 + // Set default layout.
101 + if ( ! $has_pro && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
103 102 $layout = RenderHelpers::set_default_layout( $layout );
104 103 }
105 104
106 - $has_tab_filters = rtsb()->has_pro() && ! empty( $settings['tax_filter'] );
107 -
108 - if ( $is_carousel ) {
109 - $pagination_attr = $has_tab_filters ? RenderHelpers::pagination_data( $settings, $template ) : '';
110 - } elseif ( $is_cat ) {
111 - $pagination_attr = '';
112 - } else {
113 - $ajax_pagination = ! empty( $settings['show_pagination'] ) && 'pagination' !== $settings['pagination_type'];
114 - $page = Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' );
115 - $condition = rtsb()->has_pro() && $ajax_attr && ( $ajax_pagination || $has_tab_filters || $page );
116 - $pagination_attr = $condition ? RenderHelpers::pagination_data( $settings, $template ) : '';
105 + if ( $has_pro && 'masonry' === $grid_style ) {
106 + $style_attr .= '; --rtsb-masonry-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) );
107 + $style_attr .= '; --rtsb-masonry-list-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) );
117 108 }
118 109
119 - if ( ! in_array( 'clear-btn', $metas['visibility'], true ) ) {
120 - $container_class .= ' no-clear-btn';
121 - }
110 + // Container classes.
111 + $container_classes = RenderHelpers::prepare_container_classes( $metas, $settings, $class );
122 112
123 - $container_class .= ! empty( $metas['pagination'] ) ? ' rtsb-has-pagination' : ' rtsb-no-pagination';
124 - $style_attr = ' style="--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) ) . '"';
125 - $tooltip_attr = ' data-tooltip-position="' . esc_attr( $metas['tooltip_position'] ) . '"';
113 + // Pagination attributes.
114 + $pagination_attributes = RenderHelpers::prepare_pagination_attr( $layout, $has_filters, $template, $settings, $ajax );
126 115
127 - $view_mode = $settings['view_mode'] ?? 'grid';
116 + // Attributes for the container.
117 + $container_attributes = [
118 + 'id' => $layout_id,
119 + 'class' => $container_classes,
120 + 'style' => apply_filters( 'rtsb/product/container/attr', $style_attr, $settings ),
121 + 'data-layout' => $layout,
122 + 'data-tooltip-position' => $tooltip_attr,
123 + ];
128 124
129 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended
130 - if ( isset( $_GET['displayview'] ) ) {
131 - $view_mode = sanitize_text_field( wp_unslash( $_GET['displayview'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
125 + if ( ! empty( $pagination_attributes ) ) {
126 + $container_attributes['data-rtsb-ajax'] = $pagination_attributes;
132 127 }
133 128
134 - if ( 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ) {
135 - $tooltip_attr = ' data-tooltip-position="' . esc_attr( $metas['tooltip_position_list'] ) . '"';
136 - }
129 + // Adding render attributes.
130 + $this->add_attribute( 'rtsb_container_attr_' . $rand, $container_attributes );
137 131
138 - $container_attr .= ' data-layout="' . esc_attr( $layout ) . '"' . $style_attr . $tooltip_attr . $pagination_attr;
139 - $container_class .= ' 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'] );
132 + // Start rendering.
133 + $this->html = '<div ' . $this->get_attribute_string( 'rtsb_container_attr_' . $rand ) . '>';
140 134
141 - if ( $metas['show_overlay'] ) {
142 - $container_class .= ' has-overlay';
143 - }
135 + // Section title.
136 + $this->html .= $this->render_section_title( $settings );
144 137
145 - if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
146 - $container_class .= ' show-single-cat';
147 - }
148 -
149 - if ( ! empty( $settings['show_compare_text'] ) ) {
150 - $container_class .= ' has-compare-text';
151 - }
152 -
153 - if ( ! empty( $settings['show_quick_view_text'] ) ) {
154 - $container_class .= ' has-quick-view-text';
155 - }
156 -
157 - if ( ! empty( $settings['show_wishlist_text'] ) ) {
158 - $container_class .= ' has-wishlist-text';
159 - }
160 -
161 - if ( empty( $settings['show_compare_icon'] ) ) {
162 - $container_class .= ' no-compare-icon';
163 - }
164 -
165 - if ( empty( $settings['show_quick_view_icon'] ) ) {
166 - $container_class .= ' no-quick-view-icon';
167 - }
168 -
169 - if ( empty( $settings['show_wishlist_icon'] ) ) {
170 - $container_class .= ' no-wishlist-icon';
171 - }
172 -
173 - if ( empty( $settings['show_quick_checkout_icon'] ) ) {
174 - $container_class .= ' no-quick_checkout-icon';
175 - }
176 - if ( $is_cat && empty( $settings['show_overlay'] ) ) {
177 - $container_class .= ' no-category-bg-overlay';
178 - }
179 -
180 - $container_class = apply_filters( 'rtsb/product/container/class', $container_class, $settings );
181 - $this->html = '<div class="' . esc_attr( $container_class ) . '" id="' . esc_attr( $layout_id ) . '" ' . $container_attr . '>';
182 -
183 - if ( ! empty( $settings['query_products'] ) && count( $settings['query_products'] ) && 'yes' === $settings['show_section_title'] ) {
184 - $this->html .= '<div class="rtsb-section-title-wrapper">';
185 - $this->html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' class="rtsb-section-title" >';
186 - $this->html .= esc_html( $settings['section_title_text'] );
187 - $this->html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
188 - $this->html .= '</div>';
189 - }
190 -
138 + // Content.
191 139 $this->html .= $content;
192 140
141 + // End rendering.
193 142 $this->html .= '</div><!-- .rtsb-elementor-container -->';
194 143
195 144 // Action button icon reset.
196 145 $this->action_button_icon_set_default();
197 146
147 + // Script generator.
198 148 $script_generator = [];
199 149 $script_generator['layout'] = $layout_id;
200 - $script_generator['rand'] = absint( wp_rand() );
201 - $script_generator['isCarousel'] = $is_carousel;
150 + $script_generator['rand'] = absint( $rand );
151 + $script_generator['isCarousel'] = preg_match( '/slider/', $layout );
152 + $script_generator['isMasonry'] = $has_pro && 'masonry' === $grid_style;
202 153
154 + // Register scripts in the wp_footer action.
203 155 add_action(
204 156 'wp_footer',
205 157 static function () use ( $script_generator ) {
206 158 RenderHelpers::register_scripts( $script_generator );
@@ -220,39 +172,42 @@
220 172 *
221 173 * @return string
222 174 */
223 175 public function row( $content, $settings, $hook_html = '', $raw_settings = [] ) {
224 - $masonry_class = null;
225 - $loader_class = ' rtsb-pre-loader';
176 + $raw_layout = esc_html( $settings['layout'] );
177 + $layout = RenderHelpers::filter_layout( $raw_layout );
226 178
227 - $layout = $settings['layout'];
228 - $grid_type = $settings['grid_type'];
229 - $is_carousel = preg_match( '/slider/', $layout );
179 + $rand = wp_rand();
180 + $is_carousel = preg_match( '/slider/', $raw_layout );
230 181
231 - if ( 'even' === $grid_type ) {
232 - $masonry_class = ' rtsb-even';
233 - } elseif ( 'masonry' === $grid_type ) {
234 - $masonry_class = ' rtsb-masonry';
182 + if ( preg_match( '/category/', $raw_layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
183 + $is_carousel = true;
235 184 }
236 185
237 - if ( ! rtsb()->has_pro() || $is_carousel ) {
238 - $masonry_class = ' rtsb-even';
239 - }
186 + // Row classes.
187 + $row_classes = RenderHelpers::prepare_row_classes( $settings );
240 188
241 - $row_start_class = apply_filters( 'rtsb/custom/layout/row/classes', esc_attr( 'rtsb-row rtsb-content-loader element-loading rtsb-' . $layout . $masonry_class . $loader_class ), $settings );
242 - $row_wrapper = '<div data-title="' . esc_html__( 'Loading ...', 'shopbuilder' ) . '" class="' . $row_start_class . '">';
243 - $start_wrapper = apply_filters( 'rtsb/custom/layout/row/start', $row_wrapper, $row_start_class );
244 - if ( ! empty( $settings['show_filter'] ) ) {
245 - $this->html = $hook_html;
246 - $this->html .= $start_wrapper;
247 - } else {
248 - $this->html = $start_wrapper;
249 - }
189 + // Adding render attributes.
190 + $this->add_attribute( 'rtsb_row_attr_' . $rand, 'class', $row_classes );
250 191
192 + // Start rendering.
193 + $row_wrapper = '<div ' . $this->get_attribute_string( 'rtsb_row_attr_' . $rand ) . '>';
194 + $start_wrapper = apply_filters( 'rtsb/elementor/row/start', $row_wrapper, $raw_settings );
195 +
196 + $this->html = ! empty( $settings['show_filter'] ) ? $hook_html . $start_wrapper : $start_wrapper;
197 +
251 198 if ( $is_carousel ) {
252 199 $slider_data = RenderHelpers::slider_data( $settings, $raw_settings );
200 + // Adding slider render attributes.
201 + $this->add_attribute(
202 + 'rtsb_slider_attr_' . $rand,
203 + [
204 + 'class' => 'rtsb-col-grid ' . $slider_data['class'],
205 + 'data-options' => $slider_data['data'],
206 + ]
207 + );
253 208
254 - $this->html .= '<div class="rtsb-col-grid ' . esc_attr( $slider_data['class'] ) . '" ' . $slider_data['data'] . '>';
209 + $this->html .= '<div ' . $this->get_attribute_string( 'rtsb_slider_attr_' . $rand ) . '>';
255 210 $this->html .= '<div class="swiper-wrapper">';
256 211 }
257 212
258 213 $this->html .= $content;
@@ -259,32 +214,18 @@
259 214
260 215 if ( $is_carousel ) {
261 216 $this->html .= '</div><!-- .swiper-wrapper -->';
262 217
263 - $left_icon = $settings['left_arrow_icon'];
264 - $right_icon = $settings['right_arrow_icon'];
265 -
266 - if ( ! empty( $settings['slider_nav'] ) ) {
267 - $this->html .=
268 - '<div class="swiper-nav">
269 - <div class="swiper-arrow swiper-button-next">
270 - ' . Fns::icons_manager( $right_icon ) . '
271 - </div>
272 - <div class="swiper-arrow swiper-button-prev">
273 - ' . Fns::icons_manager( $left_icon ) . '
274 - </div>
275 - </div>';
276 - }
277 -
278 - $this->html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
279 -
218 + // Slider buttons.
219 + $this->html .= $this->render_slider_buttons( $settings );
280 220 $this->html .= '</div><!-- .rtsb-carousel-slider -->';
281 221 }
282 222
283 223 $row_end = '</div><!-- .rtsb-row -->';
284 224
285 - $this->html .= apply_filters( 'rtsb/custom/layout/row/end', $row_end );
225 + $this->html .= apply_filters( 'rtsb/elementor/row/end', $row_end );
286 226
227 + // Preloader.
287 228 $this->html .= RenderHelpers::pre_loader( $layout );
288 229
289 230 return $this->html;
290 231 }
@@ -291,8 +232,63 @@
291 232
292 233 /**
293 234 * Product loop.
294 235 *
236 + * @param object $products Products.
237 + * @param array $settings Settings.
238 + * @param string $template Template.
239 + *
240 + * @return string|null
241 + */
242 + public function product_loop( $products, $settings, $template ) {
243 + $html = null;
244 +
245 + if ( empty( $products ) ) {
246 + return null;
247 + }
248 +
249 + $i = 0;
250 +
251 + global $product;
252 +
253 + $isGlobalProduct = false;
254 +
255 + if ( $product instanceof WC_Product ) {
256 + $isGlobalProduct = $product;
257 + }
258 +
259 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
260 +
261 + foreach ( $products as $_product ) {
262 + $i++;
263 + $GLOBALS['product'] = $_product; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Global variable is intentionally used to share the current product context with hooks and templates during rendering.
264 + /**
265 + * Before product template render hook.
266 + */
267 + do_action( 'rtsb_before_product_template_render' );
268 +
269 + // Loop arg.
270 + $arg = $this->arg_dataset( $settings, $_product, $settings['lazy_load'], $i );
271 +
272 + // Get template.
273 + $html .= Fns::load_template( $template . $layout, $arg, true );
274 +
275 + /**
276 + * After product template render hook.
277 + */
278 + do_action( 'rtsb_after_product_template_render' );
279 +
280 + unset( $GLOBALS['product'] );
281 + }
282 + if ( $isGlobalProduct ) {
283 + $GLOBALS['product'] = $isGlobalProduct; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Global variable is intentionally used to share the current product context with hooks and templates during rendering.
284 + }
285 + return $html;
286 + }
287 +
288 + /**
289 + * WC Product loop.
290 + *
295 291 * @param object $query The query.
296 292 * @param array $settings Settings.
297 293 * @param string $template Template.
298 294 *
@@ -297,9 +293,9 @@
297 293 * @param string $template Template.
298 294 *
299 295 * @return string|null
300 296 */
301 - public function product_loop( $query, $settings, $template ) {
297 + public function wc_product_loop( $query, $settings, $template ) {
302 298 $html = null;
303 299
304 300 if ( empty( $query ) ) {
305 301 return null;
@@ -304,19 +300,20 @@
304 300 if ( empty( $query ) ) {
305 301 return null;
306 302 }
307 303
308 - $i = 0;
304 + $i = 0;
305 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
309 306
310 307 while ( $query->have_posts() ) {
311 308 $query->the_post();
312 309 $i++;
313 -
310 + global $product;
314 311 // Loop arg.
315 - $arg = $this->arg_dataset( $settings, get_the_ID(), $settings['lazy_load'], $i );
312 + $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
316 313
317 314 // Get template.
318 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
315 + $html .= Fns::load_template( $template . $layout, $arg, true );
319 316 }
320 317
321 318 return $html;
322 319 }
@@ -321,9 +318,9 @@
321 318 return $html;
322 319 }
323 320
324 321 /**
325 - * Product loop.
322 + * Category loop.
326 323 *
327 324 * @param object $query The query.
328 325 * @param array $settings Settings.
329 326 * @param string $template Template.
@@ -333,11 +330,13 @@
333 330 */
334 331 public function category_loop( $query, $settings, $template, $is_single = false ) {
335 332 $html = null;
336 333 $category_loop = false;
334 +
337 335 if ( 'category-layout3' === $settings['raw_settings']['layout'] || 'category-single-layout2' === $settings['raw_settings']['layout'] ) {
338 336 $category_loop = true;
339 337 }
338 +
340 339 if ( $is_single ) {
341 340 // Loop arg.
342 341 $arg = $this->cat_arg_dataset( $settings, $query, true, false, $category_loop );
343 342
@@ -374,42 +373,56 @@
374 373
375 374 // Query Args.
376 375 $args = ( new QueryArgs() )->buildArgs( $metas, 'product', $is_carousel );
377 376
378 - $temp_args = $args;
379 - $args = apply_filters( 'rtsb/elementor/args_before_loop', $args, $metas );
377 + $temp_args = $args;
378 + $default_args = [
379 + 'visibility' => 'visible',
380 + 'status' => 'publish',
381 + ];
382 + $custom_args = apply_filters( 'rtsb/elementor/args_before_loop', $args, $metas );
383 + $args = wp_parse_args( $custom_args, $default_args );
380 384
385 + /**
386 + * Before Product query hook.
387 + *
388 + * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::modify_wc_query_args 10
389 + */
390 +
391 + do_action( 'rtsb/elements/render/before_query', $settings, $args );
381 392 // Query.
382 - $product_query = new WP_Query( $args );
393 + $product_query = new WC_Product_Query( $args );
383 394
384 - if ( $product_query->have_posts() ) {
385 - // Elementor Render hook before row.
386 - ob_start();
387 - do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $product_query, $temp_args );
388 - $hook_html .= ob_get_clean();
395 + /**
396 + * Product query hook.
397 + */
398 + do_action( 'rtsb/elements/render/product_query', $product_query, $this );
389 399
400 + $products = $pagination ? $product_query->get_products()->products : $product_query->get_products();
401 +
402 + if ( ! empty( $products ) ) {
403 + // Start rendering.
404 + $hook_html .= $this->render_start( $metas, $settings, $temp_args );
405 +
390 406 // Row.
391 - $this->row( $this->product_loop( $product_query, $metas, $template ), $metas, $hook_html, $settings );
407 + $this->row( $this->product_loop( $products, $metas, $template ), $metas, $hook_html, $settings );
392 408
393 409 // Pagination.
394 410 if ( $pagination ) {
395 - $this->html .= RenderHelpers::render_pagination(
396 - $product_query,
397 - $metas,
398 - $settings['posts_limit'],
399 - $settings['pagination_per_page']
400 - );
411 + $pagination_args = [
412 + 'query' => $product_query,
413 + 'meta' => $metas,
414 + 'limit' => $settings['posts_limit'],
415 + 'per_page' => $settings['pagination_per_page'],
416 + ];
417 +
418 + $this->html .= $this->render_products_pagination( $pagination_args );
401 419 }
402 420
403 - // Elementor Render hook before row.
404 - ob_start();
405 - do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $product_query );
406 - $this->html .= ob_get_clean();
407 -
421 + // End rendering.
422 + $this->html .= $this->render_end( $metas, $settings, $products );
408 423 } else {
409 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div></div>';
410 -
411 - $this->row( $content, $metas );
424 + $this->no_products_msg( $metas );
412 425 }
413 426
414 427 wp_reset_postdata();
415 428
@@ -414,8 +427,15 @@
414 427 wp_reset_postdata();
415 428
416 429 unset( $args['tax_query'] );
417 430
431 + /**
432 + * After Product query hook.
433 + *
434 + * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::reset_wc_query_args 10
435 + */
436 + do_action( 'rtsb/elements/render/after_query', $settings, $args );
437 +
418 438 // Container.
419 439 $this->container( $this->html, $settings, 'rtsb-products-container', $template, $is_carousel );
420 440
421 441 return $this->html;
@@ -420,9 +440,8 @@
420 440
421 441 return $this->html;
422 442 }
423 443
424 -
425 444 /**
426 445 * Render Product View.
427 446 *
428 447 * @param string $template Template name.
@@ -434,24 +453,29 @@
434 453 public function wc_loop_for_product_view( $template, $settings, $widget ) {
435 454 global $wp_query, $post;
436 455
437 456 $this->get_settings = $settings;
457 + $rand = wp_rand();
438 458 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
439 459
440 - $settings['rtsb_order'] = ! empty( $wp_query->query_vars['order'] ) ? $wp_query->query_vars['order'] : 'ASC';
441 - $settings['rtsb_orderby'] = ! empty( $wp_query->query_vars['orderby'] ) ? $wp_query->query_vars['orderby'] : 'menu_order';
460 + $order = strtoupper( $wp_query->query_vars['order'] ?? 'ASC' );
461 + $settings['rtsb_orderby'] = RenderHelpers::escaping_product_orderby( $wp_query->query_vars['orderby'] ?? 'menu_order' );
462 + $settings['rtsb_order'] = in_array( $order, [ 'ASC','DESC' ], true ) ? $order : 'ASC';
442 463
443 464 $pagination = $metas['pagination'];
444 465
445 - $page_type = BuilderFns::builder_type( get_the_ID() );
446 - $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
447 - $posts_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
448 -
466 + $page_type = BuilderFns::builder_type( get_the_ID() );
467 + $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
468 + $raw_settings = $widget->get_settings_for_display();
469 + $widget_per_page = ! empty( $raw_settings['posts_per_page'] ) ? absint( $raw_settings['posts_per_page'] ) : 0;
470 + $wc_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
471 + $posts_per_page = $widget_per_page ? $widget_per_page : apply_filters( 'loop_shop_per_page', $wc_per_page ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- woocommerce default hooks used.
472 + $restore_query = false;
449 473 if ( $is_preview ) {
450 474 $main_query = clone $wp_query;
451 475 $main_post = clone $post;
452 476
453 - $ordering = ( new \WC_Query() )->get_catalog_ordering_args();
477 + $ordering = ( new WC_Query() )->get_catalog_ordering_args();
454 478 $wp_query_args = [
455 479 'post_type' => 'product',
456 480 'posts_per_page' => $posts_per_page,
457 481 ];
@@ -464,61 +488,111 @@
464 488 $wp_query_args['order'] = $ordering['order'];
465 489 }
466 490
467 491 $wp_query = new WP_Query( $wp_query_args ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
492 +
468 493 wc_set_loop_prop( 'total', $wp_query->found_posts );
469 494 wc_set_loop_prop( 'total_pages', $wp_query->max_num_pages );
495 + } elseif ( ! is_admin() && ( is_shop() || is_product_taxonomy() ) && absint( $wp_query->get( 'posts_per_page' ) ) !== $posts_per_page ) {
496 + // Apply widget's posts_per_page to the main query on the live frontend.
497 + $main_query = clone $wp_query;
498 + $main_post = clone $post;
499 +
500 + $query_vars = $wp_query->query_vars;
501 + $query_vars['posts_per_page'] = $posts_per_page;
502 +
503 + $wp_query = new WP_Query( $query_vars ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
504 +
505 + wc_set_loop_prop( 'total', $wp_query->found_posts );
506 + wc_set_loop_prop( 'total_pages', $wp_query->max_num_pages );
507 +
508 + $restore_query = true;
470 509 }
471 510
511 + if ( Fns::product_has_applied_filters( 'shop' ) || Fns::product_has_applied_filters( 'archive' ) || 'rtsb_builder' === get_post_type( get_the_ID() ) ) {
512 + $rtsb_active_filters_html = Fns::get_active_filters_html();
513 + $rtsb_wrapper_style = '' !== $rtsb_active_filters_html ? ' style="' . esc_attr( 'display:block;' ) . '"' : '';
514 + echo '<div class="rtsb-active-filters-wrapper"' . $rtsb_wrapper_style . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Style value is escaped above.
515 + Fns::print_html( $rtsb_active_filters_html, true );
516 + echo '</div>';
517 + }
518 +
472 519 // column_per_row.
473 520 if ( woocommerce_product_loop() ) {
474 521 if ( wc_get_loop_prop( 'total' ) ) {
475 - if ( Fns::product_has_applied_filters( 'shop' ) || Fns::product_has_applied_filters( 'archive' ) || 'rtsb_builder' === get_post_type( get_the_ID() ) ) {
476 - echo '<div class="rtsb-active-filters-wrapper"></div>';
477 - }
478 522
479 523 // Row.
480 - $this->row( $this->product_loop( $wp_query, $metas, $template ), $metas );
524 + $this->row( $this->wc_product_loop( $wp_query, $metas, $template ), $metas, '', $settings );
481 525 }
482 526
483 527 // Pagination.
484 - $this->html .= '<div class="rtsb-archive-pagination-wrap">';
528 + $this->html .= '<div class="rtsb-archive-pagination-wrap" data-per-page="' . absint( $posts_per_page ) . '">';
485 529
486 530 if ( $pagination ) {
487 - if ( Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' ) ) {
531 + if ( Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) ) {
488 532 if ( $wp_query->max_num_pages > 1 ) {
489 - $this->html .= "
490 - <div class='rtsb-load-more rtsb-pos-r'>
491 - <button data-paged='2' data-max-page='{$wp_query->max_num_pages}'><span>" . esc_html__( 'Load More', 'shopbuilder' ) . "</span></button>
492 - <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
493 - </div>
494 - ";
533 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
534 +
535 + if ( 'pagination_ajax' === $posts_loading_type ) {
536 + $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, true );
537 + } elseif ( 'pagination' === $posts_loading_type ) {
538 + $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, false );
539 + } elseif ( 'load_on_scroll' === $posts_loading_type ) {
540 + $this->html .= "<div class='rtsb-infinite-scroll' data-trigger='1' data-paged='2' data-max-page='{$wp_query->max_num_pages}'>
541 + <div class='rtsb-infinite-action'>
542 + <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
543 + </div>
544 + </div>";
545 + } else {
546 + $load_more_text = ! empty( $metas['load_more_text'] ) ? $metas['load_more_text'] : esc_html__( 'Load More', 'shopbuilder' );
547 +
548 + $this->add_attribute(
549 + 'rtsb_load_more_btn_attr_' . $rand,
550 + [
551 + 'data-paged' => '2',
552 + 'data-max-page' => $wp_query->max_num_pages,
553 + ]
554 + );
555 +
556 + $this->html .=
557 + "<div class='rtsb-load-more rtsb-pos-r'>
558 + <button {$this->get_attribute_string( 'rtsb_load_more_btn_attr_' . $rand )}>
559 + <span>" . esc_html( $load_more_text ) . "</span>
560 + </button>
561 + <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
562 + </div>";
563 + }
495 564 }
496 565 } else {
497 - $this->html .= RenderHelpers::render_pagination(
498 - $wp_query,
499 - $metas,
500 - wc_get_loop_prop( 'total' ),
501 - $posts_per_page
502 - );
566 + $pagination_args = [
567 + 'query' => $wp_query,
568 + 'meta' => $metas,
569 + 'limit' => wc_get_loop_prop( 'total' ),
570 + 'per_page' => $posts_per_page,
571 + ];
572 +
573 + $this->html .= $this->render_products_pagination( $pagination_args, 'wp' );
503 574 }
504 575 }
505 576
506 577 $this->html .= '</div>';
507 578 } else {
508 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div></div>';
509 -
510 - $this->row( $content, $metas );
579 + $this->no_products_msg( $metas );
511 580 }
512 581
513 - if ( $is_preview ) {
582 + if ( $is_preview || ! empty( $restore_query ) ) {
583 + // phpcs:disable
514 584 $wp_query = $main_query;
515 585 $post = $main_post;
516 586
517 587 wp_reset_query();
518 588 wp_reset_postdata();
589 + // phpcs:enable
519 590 }
520 591
592 + // Ensure the container's data-rtsb-ajax uses the same posts_per_page as the initial render.
593 + $settings['pagination_per_page'] = $posts_per_page;
594 +
521 595 // Container.
522 596 $this->container( $this->html, $settings, 'rtsb-products-container', $template );
523 597
524 598 return $this->html;
@@ -524,12 +598,13 @@
524 598 return $this->html;
525 599 }
526 600
527 601 /**
528 - * Render Product View.
602 + * Loop Setup for Product View.
529 603 *
530 604 * @param string $template Template name.
531 605 * @param array $settings Control settings.
606 + * @param object $widget Widget object.
532 607 *
533 608 * @return string
534 609 */
535 610 public function setup_loop_for_product_view( $template, $settings, $widget ) {
@@ -546,13 +621,13 @@
546 621
547 622 setup_postdata( $GLOBALS['post'] =& $post_object ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, Squiz.PHP.DisallowMultipleAssignments.Found
548 623
549 624 // Loop arg.
550 - $arg = $this->arg_dataset( $metas, $_product->get_id() );
625 + $arg = $this->arg_dataset( $metas, $_product );
551 626 // Get template.
552 627 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
553 628
554 - $this->row( $html, $metas );
629 + $this->row( $html, $metas, '', $settings );
555 630 }
556 631
557 632 // Container.
558 633 $this->container( $this->html, $settings, 'rtsb-products-container', '', false, false );
@@ -571,8 +646,13 @@
571 646 *
572 647 * @return string
573 648 */
574 649 public function category_view( $template, $settings, $is_single = false ) {
650 + // Reset the shared singleton's html buffer so leftover markup from a
651 + // previously rendered widget (e.g. a products grid that ran earlier
652 + // in the same request) doesn't get folded into the categories
653 + // container by container() below.
654 + $this->html = '';
575 655 $this->get_settings = $settings;
576 656
577 657 $metas = RenderHelpers::meta_dataset( $settings, $template, $this->get_settings_for_display() );
578 658 $taxonomy = $metas['category_source'];
@@ -580,8 +660,11 @@
580 660
581 661 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
582 662 $term = $metas['tag_term'];
583 663 }
664 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
665 + $term = $metas['brand_term'];
666 + }
584 667
585 668 // Query.
586 669 if ( $is_single ) {
587 670 if ( empty( $term ) ) {
@@ -599,9 +682,9 @@
599 682 if ( ! is_wp_error( $taxonomy_query ) && ! empty( $taxonomy_query ) ) {
600 683 // Row.
601 684 $this->row( $this->category_loop( $taxonomy_query, $metas, $template, $is_single ), $metas );
602 685 } else {
603 - $this->html .= '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
686 + $this->html = '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
604 687 }
605 688
606 689 // Container.
607 690 $this->container( $this->html, $settings, 'rtsb-categories-container', $is_single );
@@ -648,8 +731,34 @@
648 731
649 732 /**
650 733 * Builds an array with meta values.
651 734 *
735 + * @param array $meta Meta values.
736 + * @param object $product Product object.
737 + * @param bool $lazy_load Lazy Load.
738 + * @param int $i Integer.
739 + *
740 + * @return array
741 + */
742 + public function arg_dataset( array $meta, object $product, bool $lazy_load = false, $i = 0 ) {
743 + $post_id = $product->get_id();
744 + $arg = $this->build_product_arg(
745 + [
746 + 'product' => $product,
747 + 'id' => $product->get_id(),
748 + 'type' => $product->get_type(),
749 + 'meta' => $meta,
750 + 'lazy_load' => $lazy_load,
751 + 'i' => $i,
752 + ]
753 + );
754 +
755 + return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
756 + }
757 +
758 + /**
759 + * Builds an array with meta values for WP_Query.
760 + *
652 761 * @param array $meta Meta values.
653 762 * @param int $post_id Post ID.
654 763 * @param bool $lazy_load Lazy Load.
655 764 * @param int $i Integer.
@@ -655,22 +764,184 @@
655 764 * @param int $i Integer.
656 765 *
657 766 * @return array
658 767 */
659 - public function arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
768 + public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
769 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
660 770 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
661 771 return [];
662 772 }
663 773
664 - global $post, $product;
774 + global $product;
665 775
666 776 if ( ! $product instanceof WC_Product && $post_id ) {
667 - $product = wc_get_product( $post_id );
777 + $product = wc_get_product( $post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Global variable is intentionally used to share the current product context with hooks and templates during rendering.
668 778 }
669 779
670 - $arg = [];
671 - $p_type = $product->get_type();
780 + $post_id = $product->get_id();
781 + $arg = $this->build_product_arg(
782 + [
783 + 'product' => $product,
784 + 'id' => $post_id,
785 + 'type' => $product->get_type(),
786 + 'meta' => $meta,
787 + 'lazy_load' => $lazy_load,
788 + 'i' => $i,
789 + ]
790 + );
672 791
792 + return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
793 + }
794 +
795 + /**
796 + * Builds an array with meta values.
797 + *
798 + * @param array $meta Meta values.
799 + * @param Object $query Category object.
800 + * @param bool $fullwidth Fullwidth check.
801 + * @param bool $lazy_load Lazy Load.
802 + * @param bool $category_loop Category loop.
803 + *
804 + * @return array
805 + */
806 + public function cat_arg_dataset( $meta, $query = null, $fullwidth = false, $lazy_load = false, $category_loop = false ) {
807 + if ( empty( $meta ) ) {
808 + return [];
809 + }
810 +
811 + $arg = [];
812 +
813 + $products = null;
814 + $arg['class'] = null;
815 + $arg['grid'] = null;
816 + $arg['anchor_class'] = null;
817 + $arg['p_sale'] = null;
818 + $arg['count'] = max( $query->count, 0 );
819 + $arg['count_position'] = $meta['count_position'];
820 + $arg['excerpt_position'] = $meta['cat_excerpt_position'];
821 + $arg['cat_link'] = get_term_link( $query );
822 + $arg['target'] = '_self';
823 + $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
824 +
825 + if ( ! empty( $meta['tag_term'] ) ) {
826 + $arg['tag_term'] = $meta['tag_term'];
827 + }
828 +
829 + if ( ! empty( $meta['custom_link']['url'] ) ) {
830 + $arg['cat_link'] = $meta['custom_link']['url'];
831 + $arg['target'] = $meta['custom_link']['is_external'] ? '_blank' : '_self';
832 + }
833 +
834 + $title = $query->name;
835 + $excerpt = $query->description;
836 +
837 + if ( in_array( 'badges', $meta['visibility'], true ) ) {
838 + $arg['p_sale'] = $meta['custom_badge_text'];
839 + }
840 +
841 + $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
842 +
843 + if ( $arg['count'] >= 0 ) {
844 + $prefix = ! empty( $meta['before_count'] ) ? $meta['before_count'] : '';
845 + $suffix = ! empty( $meta['after_count'] ) ? $meta['after_count'] : '';
846 +
847 + $arg['count'] = $prefix . $arg['count'] . $suffix;
848 + }
849 +
850 + if ( $meta['show_custom_title'] && ! empty( $meta['cat_custom_title'] ) ) {
851 + $title = $meta['cat_custom_title'];
852 + }
853 +
854 + if ( $meta['show_custom_excerpt'] && ! empty( $meta['cat_custom_excerpt'] ) ) {
855 + $excerpt = $meta['cat_custom_excerpt'];
856 + }
857 +
858 + $arg['c_img'] = $meta['c_img'];
859 + $arg['items'] = $meta['visibility'];
860 + $arg['title_tag'] = $meta['title_tag'];
861 + $arg['title_class'] = 'category-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
862 + $arg['title'] = Fns::text_truncation( $title, $meta['title_limit_custom'] );
863 + $arg['excerpt_limit'] = $meta['excerpt_limit'];
864 + $arg['excerpt'] = wpautop( Fns::text_truncation( $excerpt, $meta['excerpt_limit_custom'] ) );
865 + $arg['title_link'] = $meta['title_link'];
866 + $arg['image_link'] = $meta['image_link'];
867 + $arg['grid'] .= 'rtsb-col-grid ';
868 +
869 + if ( ! $fullwidth ) {
870 + if ( 'masonry' === $meta['grid_type'] ) {
871 + $arg['class'] .= 'masonry-grid-item ';
872 + } else {
873 + $arg['class'] .= 'even-grid-item ';
874 + }
875 + }
876 +
877 + if ( ! empty( $meta['active_cat_slider'] ) ) {
878 + $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
879 + }
880 +
881 + $arg['class'] .= 'rtsb-category-grid';
882 +
883 + ob_start();
884 + do_action( 'rtsb/categories/category_image_override', $meta, $query->term_id );
885 + $override_category_image = ob_get_clean();
886 +
887 + $arg['img_html'] = $meta['c_img'] ? Fns::get_product_image_html(
888 + 'category',
889 + $query->term_id,
890 + $meta['f_img_size'],
891 + $meta['default_img_id'],
892 + $meta['custom_img_size'],
893 + $lazy_load,
894 + false,
895 + false,
896 + $override_category_image
897 + ) : null;
898 +
899 + if ( $meta['show_custom_image'] && ! empty( $meta['custom_image'] ) ) {
900 + $arg['img_html'] = Fns::get_product_image_html(
901 + 'custom',
902 + null,
903 + $meta['f_img_size'],
904 + $meta['custom_image'],
905 + $meta['custom_img_size'],
906 + $lazy_load
907 + );
908 + }
909 +
910 + $arg['excerpt_class'] = 'category-info' . ( 'block' === $meta['count_position'] ? ' block-count' : ' inline-count' ) . ( 'above' === $meta['cat_excerpt_position'] ? ' excerpt-above' : '' ) . ( empty( $arg['excerpt'] ) ? ' no-excerpt' : '' );
911 +
912 + if ( $category_loop ) {
913 + $query_args = [
914 + 'product_category_id' => $query->term_id,
915 + 'limit' => -1,
916 + 'order' => 'DESC',
917 + 'orderby' => 'date',
918 + ];
919 +
920 + $products_query = new WC_Product_Query( $query_args );
921 + $products = $products_query->get_products();
922 + }
923 +
924 + return apply_filters( 'rtsb/elementor/render/cat_arg_dataset', $arg, $meta, $query, $fullwidth, $lazy_load, $products );
925 + }
926 +
927 + /**
928 + * Building product args.
929 + *
930 + * @param array $args Args.
931 + * @return array
932 + */
933 + public function build_product_arg( $args ) {
934 + list(
935 + 'product' => $product,
936 + 'id' => $post_id,
937 + 'type' => $p_type,
938 + 'meta' => $meta,
939 + 'lazy_load' => $lazy_load,
940 + 'i' => $i,
941 + ) = $args;
942 +
943 + $arg = [];
673 944 $arg['class'] = null;
674 945 $arg['grid'] = null;
675 946 $arg['anchor_class'] = null;
676 947 $arg['hover_img_html'] = null;
@@ -675,26 +946,28 @@
675 946 $arg['anchor_class'] = null;
676 947 $arg['hover_img_html'] = null;
677 948 $arg['s_link'] = [];
678 949 $arg['add_to_cart'] = '';
950 + $arg['p_counter'] = absint( $i );
679 951 $arg['p_id'] = $post_id;
680 - $arg['p_link'] = get_permalink();
952 + $arg['product'] = $product;
953 + $arg['p_link'] = $product->get_permalink();
681 954 $arg['p_sale'] = '';
682 955 $arg['rating_args'] = [];
683 956 $arg['has_attributes'] = $product->is_type( 'variable' ) && $product->get_attributes();
684 957 $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
685 958 $cart_icon_html = '';
959 + $layout = $meta['layout'];
686 960
687 - $layout = $meta['layout'];
688 -
961 + // Building the arg.
689 962 $arg['target'] = '_self';
690 963 $arg['f_img'] = $meta['f_img'];
691 964 $arg['items'] = $meta['visibility'];
692 965 $arg['title_tag'] = $meta['title_tag'];
693 966 $arg['title_class'] = 'product-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
694 - $arg['title'] = Fns::text_truncation( get_the_title(), $meta['title_limit_custom'] );
967 + $arg['title'] = Fns::text_truncation( $product->get_title(), $meta['title_limit_custom'] );
695 968 $arg['excerpt_limit'] = $meta['excerpt_limit'];
696 - $arg['excerpt'] = wpautop( Fns::text_truncation( do_shortcode( $post->post_excerpt ), $meta['excerpt_limit_custom'] ) );
969 + $arg['excerpt'] = wpautop( Fns::text_truncation( do_shortcode( get_post_field( 'post_excerpt', $post_id ) ), $meta['excerpt_limit_custom'] ) );
697 970 $arg['title_link'] = $meta['title_link'];
698 971 $arg['image_link'] = $meta['image_link'];
699 972 $arg['out_of_stock'] = $meta['out_of_stock_badge_text'];
700 973 $arg['action_btn_preset'] = $meta['btn_preset'];
@@ -706,10 +979,15 @@
706 979 $is_list = preg_match( '/list/', $layout );
707 980 $is_carousel = preg_match( '/slider/', $layout );
708 981
709 982 if ( ! $is_carousel ) {
710 - $arg['grid'] .= 'rtsb-col-grid ';
711 - $arg['class'] .= ' even-grid-item ';
983 + $arg['grid'] .= 'rtsb-col-grid ';
984 +
985 + if ( 'masonry' === $meta['grid_type'] ) {
986 + $arg['class'] .= 'masonry-grid-item ';
987 + } else {
988 + $arg['class'] .= 'even-grid-item ';
989 + }
712 990 } else {
713 991 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
714 992 }
715 993
@@ -729,17 +1007,21 @@
729 1007
730 1008 $arg['class'] .= ' animated rtFadeIn';
731 1009
732 1010 if ( in_array( 'badges', $meta['visibility'], true ) ) {
733 - $arg['p_sale'] = Fns::get_sale_badge(
1011 + $badge_module = ! empty( $meta['enable_badges_module'] );
1012 + $arg['p_sale'] = Fns::is_module_active( 'product_badges' ) && $badge_module ? 'rtsb_yes' : Fns::get_promo_badge(
1013 + $product,
734 1014 $meta['sale_badge_type'],
735 1015 $meta['sale_badge_text'],
736 - $meta['out_of_stock_badge_text']
1016 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
737 1017 );
738 1018 }
739 1019
740 1020 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
741 1021
1022 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
1023 +
742 1024 if ( ! $meta['show_cart_text'] ) {
743 1025 $meta['add_to_cart_text'] = '';
744 1026 $meta['add_to_cart_success_text'] = '';
745 1027 }
@@ -752,23 +1034,41 @@
752 1034 if ( ! empty( $meta['add_to_cart_success_icon'] ) ) {
753 1035 $cart_icon_html .= Fns::icons_manager( $meta['add_to_cart_success_icon'], 'cart-success-icon' );
754 1036 }
755 1037
756 - $cart_icon = '<span class="icon">' . $cart_icon_html . '</span>';
1038 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
757 1039
758 1040 if ( $product->is_purchasable() || 'grouped' === $p_type || 'external' === $p_type ) {
759 1041 if ( $product->is_in_stock() ) {
760 1042 $arg['add_to_cart'] = $this->render_add_to_cart_button(
761 - get_permalink(),
762 - $post_id,
763 - $p_type,
764 - $meta['add_to_cart_text'],
765 - $meta['add_to_cart_success_text'],
766 - $cart_icon,
767 - $meta['add_to_cart_alignment']
1043 + [
1044 + 'link' => $product->get_permalink(),
1045 + 'id' => $post_id,
1046 + 'type' => $p_type,
1047 + 'text' => $meta['add_to_cart_text'],
1048 + 'success' => $meta['add_to_cart_success_text'],
1049 + 'icon_html' => $cart_icon,
1050 + 'alignment' => $meta['add_to_cart_alignment'],
1051 + 'product' => $product,
1052 + ]
768 1053 );
769 1054 }
770 1055 }
1056 + if ( Fns::is_catalog_mode() && empty( $arg['add_to_cart'] ) ) {
1057 + if ( ! empty( $meta['add_to_cart_icon'] ) ) {
1058 + $cart_icon_html = Fns::icons_manager( $meta['add_to_cart_icon'], 'cart-icon' );
1059 + }
1060 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
1061 + $arg['add_to_cart'] = CatalogModeFns::render_elementor_widget_catalog_mode_button(
1062 + [
1063 + 'id' => $post_id,
1064 + 'type' => $p_type,
1065 + 'text' => $meta['add_to_cart_text'],
1066 + 'icon_html' => $cart_icon,
1067 + 'alignment' => $meta['add_to_cart_alignment'],
1068 + ]
1069 + );
1070 + }
771 1071 }
772 1072
773 1073 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
774 1074 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -831,249 +1131,466 @@
831 1131 'hover_img_html' => $arg['hover_img_html'],
832 1132 'meta' => $meta,
833 1133 ];
834 1134
835 - return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
1135 + return $arg;
836 1136 }
837 -
838 1137 /**
839 - * Builds an array with meta values.
1138 + * Renders add to cart button.
840 1139 *
841 - * @param array $meta Meta values.
842 - * @param Object $query Category object.
843 - * @param bool $fullwidth Fullwidth check.
844 - * @param bool $lazy_load Lazy Load.
1140 + * @param array $args Cart args.
845 1141 *
846 - * @return array
1142 + * @return string
847 1143 */
848 - public function cat_arg_dataset( $meta, $query = null, $fullwidth = false, $lazy_load = false, $category_loop = false ) {
849 - if ( empty( $meta ) ) {
850 - return [];
851 - }
1144 + public function render_add_to_cart_button( $args ) {
1145 + list(
1146 + 'link' => $link,
1147 + 'id' => $id,
1148 + 'type' => $type,
1149 + 'text' => $text,
1150 + 'success' => $success,
1151 + 'icon_html' => $icon_html,
1152 + 'alignment' => $alignment,
1153 + 'product' => $product
1154 + ) = $args;
852 1155
853 - $arg = [];
1156 + $content = null;
1157 + $rand = wp_rand();
1158 + $ext_link = get_post_meta( $id, '_product_url', true );
1159 + $btn_txt = get_post_meta( $id, '_button_text', true );
854 1160
855 - $products = null;
856 - $arg['class'] = null;
857 - $arg['grid'] = null;
858 - $arg['anchor_class'] = null;
859 - $arg['p_sale'] = null;
860 - $arg['count'] = max( $query->count, 0 );
861 - $arg['count_position'] = $meta['count_position'];
862 - $arg['excerpt_position'] = $meta['cat_excerpt_position'];
863 - $arg['cat_link'] = get_term_link( $query );
864 - $arg['target'] = '_self';
865 - $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
1161 + ob_start();
1162 + do_action( 'rtsb/before/cart/button' );
1163 + $content .= ob_get_clean();
866 1164
867 - if ( ! empty( $meta['tag_term'] ) ) {
868 - $arg['tag_term'] = $meta['tag_term'];
1165 + $cart_attr = [
1166 + 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1167 + 'href' => esc_url( $link ),
1168 + 'data-quantity' => '1',
1169 + 'data-product_id' => absint( $id ),
1170 + 'data-id' => absint( $id ),
1171 + ];
1172 +
1173 + if ( empty( $text ) ) {
1174 + $tooltip_mapping = [
1175 + 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1176 + 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1177 + 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1178 + 'external' => ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1179 + 'rtsb-gift-card' => esc_attr__( 'Select Gift Card', 'shopbuilder' ),
1180 + ];
1181 +
1182 + $cart_attr['title'] = $tooltip_mapping[ $type ];
869 1183 }
870 1184
871 - if ( ! empty( $meta['custom_link']['url'] ) ) {
872 - $arg['cat_link'] = $meta['custom_link']['url'];
873 - $arg['target'] = $meta['custom_link']['is_external'] ? '_blank' : '_self';
1185 + $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1186 + if ( 'variable' === $type ) {
1187 + $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
874 1188 }
1189 + if ( 'variable' === $type || 'grouped' === $type ) {
1190 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
875 1191
876 - $title = $query->name;
877 - $excerpt = $query->description;
1192 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
878 1193
879 - if ( in_array( 'badges', $meta['visibility'], true ) ) {
880 - $arg['p_sale'] = $meta['custom_badge_text'];
1194 + $text_btn = apply_filters(
1195 + 'rtsb/elementor/render/add_to_cart_text',
1196 + 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' ),
1197 + $product,
1198 + $type
1199 + );
1200 +
1201 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1202 + $content .= $icon_html;
1203 + $content .= '<span class="button-text text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '" data-cart-text="' . ( ! empty( $text ) ? esc_attr( $text ) : $tooltip_mapping['simple'] ) . '" data-variable-text="' . esc_attr( $text_btn ) . '">' . esc_html( $text_btn ) . '</span>';
1204 + } elseif ( 'external' === $type ) {
1205 + if ( ! empty( $ext_link ) ) {
1206 + $cart_attr['target'] = '_blank';
1207 + }
1208 +
1209 + $cart_attr['href'] = ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link );
1210 +
1211 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1212 +
1213 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1214 + $content .= $icon_html;
1215 + $content .= '<span class="button-text text">' . ( ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ) ) . '</span>';
1216 + } else {
1217 + $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1218 + $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1219 + $cart_attr['data-type'] = $type;
1220 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1221 +
1222 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1223 +
1224 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1225 + $content .= $icon_html;
1226 + $content .= '<span class="button-text text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '">' . esc_html( $text ) . '</span>';
881 1227 }
882 1228
883 - $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
1229 + $content .= '</a>';
1230 + $content .= '</div>';
884 1231
885 - if ( $arg['count'] >= 0 ) {
886 - $prefix = ! empty( $meta['before_count'] ) ? $meta['before_count'] : '';
887 - $suffix = ! empty( $meta['after_count'] ) ? $meta['after_count'] : '';
1232 + ob_start();
1233 + do_action( 'rtsb/after/cart/button' );
1234 + $content .= ob_get_clean();
888 1235
889 - $arg['count'] = $prefix . $arg['count'] . $suffix;
1236 + return apply_filters( 'rtsb/render/cart/button', $content, $args );
1237 + }
1238 +
1239 + /**
1240 + * Button classes.
1241 + *
1242 + * @param array $params Button params.
1243 + *
1244 + * @return array
1245 + */
1246 + public function button_classes( $params ) {
1247 + $params['classes'] = array_merge( $params['classes'], [ 'rtsb-action-btn', 'tipsy' ] );
1248 +
1249 + return $params;
1250 + }
1251 +
1252 + /**
1253 + * Render the section title.
1254 + *
1255 + * @param array $settings The Elementor settings.
1256 + *
1257 + * @return string
1258 + */
1259 + public function render_section_title( $settings ) {
1260 + $html = '';
1261 + $rand = wp_rand();
1262 +
1263 + if ( empty( $settings['show_section_title'] ) || ( empty( $settings['query_products'] ) && 0 === count( $settings['query_products'] ) ) ) {
1264 + return $html;
890 1265 }
891 1266
892 - if ( $meta['show_custom_title'] && ! empty( $meta['cat_custom_title'] ) ) {
893 - $title = $meta['cat_custom_title'];
894 - }
1267 + // Set attributes for the section title.
1268 + $this->add_attribute( 'rtsb_section_title_wrapper_' . $rand, 'class', 'rtsb-section-title-wrapper' );
1269 + $this->add_attribute( 'rtsb_section_title_' . $rand, 'class', 'rtsb-section-title' );
895 1270
896 - if ( $meta['show_custom_excerpt'] && ! empty( $meta['cat_custom_excerpt'] ) ) {
897 - $excerpt = $meta['cat_custom_excerpt'];
1271 + // Start rendering.
1272 + $html .= '<div ' . $this->get_attribute_string( 'rtsb_section_title_wrapper_' . $rand ) . '>';
1273 + $html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' ' . $this->get_attribute_string( 'rtsb_section_title_' . $rand ) . '>';
1274 + $html .= esc_html( $settings['section_title_text'] );
1275 + $html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
1276 + $html .= '</div>';
1277 +
1278 + return $html;
1279 + }
1280 +
1281 + /**
1282 + * Renders products pagination
1283 + *
1284 + * @param array $args Pagination args.
1285 + * @param string $query_type Query type.
1286 + *
1287 + * @return string
1288 + */
1289 + public function render_products_pagination( $args, $query_type = 'wc' ) {
1290 + list(
1291 + 'query' => $query,
1292 + 'meta' => $meta,
1293 + 'limit' => $limit,
1294 + 'per_page' => $per_page
1295 + ) = $args;
1296 +
1297 + $html_utility = null;
1298 + $html = null;
1299 + $ajax = false;
1300 + $is_grid = preg_match( '/grid-layout/', $meta['layout'] ) || preg_match( '/list-layout/', $meta['layout'] );
1301 + $posts_per_page = 'wp' === $query_type ? $query->query_vars['posts_per_page'] : null;
1302 + $total_page = 'wp' === $query_type ? $query->max_num_pages : $query->get_products()->max_num_pages;
1303 +
1304 + if ( $limit ) {
1305 + if ( 'wc' === $query_type ) {
1306 + $found_post = $query->get_products()->total;
1307 + } elseif ( 'wp' === $query_type && ( empty( $wp_query->query['tax_query'] ) ) ) {
1308 + $found_post = $query->found_posts;
1309 + }
1310 +
1311 + if ( $per_page && $found_post > $per_page ) {
1312 + $found_post = $limit;
1313 + $total_page = ceil( $found_post / $per_page );
1314 + }
898 1315 }
899 1316
900 - $layout = $meta['layout'];
1317 + $total_page = absint( $total_page );
901 1318
902 - $arg['c_img'] = $meta['c_img'];
903 - $arg['items'] = $meta['visibility'];
904 - $arg['title_tag'] = $meta['title_tag'];
905 - $arg['title_class'] = 'category-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
906 - $arg['title'] = Fns::text_truncation( $title, $meta['title_limit_custom'] );
907 - $arg['excerpt_limit'] = $meta['excerpt_limit'];
908 - $arg['excerpt'] = wpautop( Fns::text_truncation( $excerpt, $meta['excerpt_limit_custom'] ) );
909 - $arg['title_link'] = $meta['title_link'];
910 - $arg['image_link'] = $meta['image_link'];
1319 + $args = [
1320 + 'total_page' => $total_page,
1321 + 'posts_per_page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1322 + 'ajax' => $ajax,
1323 + ];
911 1324
912 - if ( ! $fullwidth ) {
913 - $arg['grid'] .= 'rtsb-col-grid ';
914 - $arg['class'] .= ' even-grid-item ';
1325 + if ( 'pagination' === $meta['posts_loading_type'] ) {
1326 + if ( $is_grid ) {
1327 + $html_utility .= Fns::custom_pagination(
1328 + $total_page,
1329 + 'wc' === $query_type ? $per_page : $posts_per_page
1330 + );
1331 + }
1332 + } elseif ( 'pagination_ajax' === $meta['posts_loading_type'] && ! rtsb()->has_pro() ) {
1333 + // Free version fallback: render non-AJAX number pagination so filter
1334 + // params in the URL are preserved via get_pagenum_link().
1335 + if ( $is_grid ) {
1336 + $html_utility .= Fns::custom_pagination(
1337 + $total_page,
1338 + 'wc' === $query_type ? $per_page : $posts_per_page
1339 + );
1340 + }
915 1341 } else {
916 - $arg['grid'] .= 'rtsb-col-grid ';
1342 + ob_start();
1343 + do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1344 + $html_utility .= ob_get_clean();
917 1345 }
918 1346
919 - $arg['class'] .= 'rtsb-category-grid';
1347 + if ( $html_utility ) {
1348 + $rand = wp_rand();
920 1349
921 - $arg['img_html'] = $meta['c_img'] ? Fns::get_product_image_html(
922 - 'category',
923 - $query->term_id,
924 - $meta['f_img_size'],
925 - $meta['default_img_id'],
926 - $meta['custom_img_size'],
927 - $lazy_load
928 - ) : null;
1350 + // Adding pagination render attributes.
1351 + $this->add_attribute(
1352 + 'rtsb_pagination_attr_' . $rand,
1353 + [
1354 + 'class' => 'rtsb-pagination-wrap element-loading',
1355 + 'data-total-pages' => $total_page,
1356 + 'data-posts-per-page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1357 + 'data-type' => $meta['posts_loading_type'],
1358 + ]
1359 + );
929 1360
930 - if ( $meta['show_custom_image'] && ! empty( $meta['custom_image'] ) ) {
931 - $arg['img_html'] = Fns::get_product_image_html(
932 - 'custom',
933 - null,
934 - $meta['f_img_size'],
935 - $meta['custom_image'],
936 - $meta['custom_img_size'],
937 - $lazy_load
938 - );
1361 + // Start rendering.
1362 + $html = '<div ' . $this->get_attribute_string( 'rtsb_pagination_attr_' . $rand ) . '>';
1363 + $html .= $html_utility;
1364 + $html .= '</div><!-- .rtsb-pagination-wrap -->';
939 1365 }
940 1366
941 - $arg['excerpt_class'] = 'category-info' . ( 'block' === $meta['count_position'] ? ' block-count' : ' inline-count' ) . ( 'above' === $meta['cat_excerpt_position'] ? ' excerpt-above' : '' ) . ( empty( $arg['excerpt'] ) ? ' no-excerpt' : '' );
1367 + return $html;
1368 + }
942 1369
943 - if ( $category_loop ) {
944 - $query_args = [
945 - 'product_category_id' => $query->term_id,
946 - 'limit' => -1,
947 - 'order' => 'DESC',
948 - 'orderby' => 'date',
949 - ];
1370 + /**
1371 + * Renders slider buttons
1372 + *
1373 + * @param array $settings Settings array.
1374 + *
1375 + * @return string
1376 + */
1377 + public function render_slider_buttons( $settings ) {
1378 + $html = '';
1379 + $left_icon = $settings['left_arrow_icon'];
1380 + $right_icon = $settings['right_arrow_icon'];
950 1381
951 - $products_query = new \WC_Product_Query( $query_args );
952 - $products = $products_query->get_products();
1382 + if ( ! empty( $settings['slider_nav'] ) ) {
1383 + $html .=
1384 + '<div class="swiper-nav">
1385 + <div class="swiper-arrow swiper-button-next">
1386 + ' . Fns::icons_manager( $right_icon ) . '
1387 + </div>
1388 + <div class="swiper-arrow swiper-button-prev">
1389 + ' . Fns::icons_manager( $left_icon ) . '
1390 + </div>
1391 + </div>';
953 1392 }
954 1393
955 - return apply_filters( 'rtsb/elementor/render/cat_arg_dataset', $arg, $meta, $query, $fullwidth, $lazy_load, $products );
1394 + $html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
1395 +
1396 + return $html;
956 1397 }
957 1398
958 1399 /**
959 - * Renders add to cart button.
1400 + * No products message.
960 1401 *
961 - * @param string $link Product link.
962 - * @param int $id Product ID.
963 - * @param string $type Product type.
964 - * @param string $text Custom text.
965 - * @param string $success Success text.
966 - * @param string $icon_html Custom icon.
967 - * @param string $alignment Icon alignment.
1402 + * @param array $metas Meta data.
968 1403 *
1404 + * @return void
1405 + */
1406 + public function no_products_msg( $metas ) {
1407 + $content =
1408 + '<div class="rtsb-notice woocommerce-no-products-found">
1409 + <div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div>
1410 + </div>';
1411 +
1412 + $this->row( $content, $metas );
1413 + }
1414 +
1415 + /**
1416 + * Start the rendering.
1417 + *
1418 + * @param array $metas Meta Data.
1419 + * @param array $settings Widget settings.
1420 + * @param array $temp_args Additional temporary arguments for rendering.
1421 + *
969 1422 * @return string
970 1423 */
971 - public function render_add_to_cart_button( $link, $id, $type, $text, $success, $icon_html, $alignment ) {
972 - $content = null;
973 - $tooltip_attr = '';
974 - $ext_link = get_post_meta( $id, '_product_url', true );
975 - $btn_txt = get_post_meta( $id, '_button_text', true );
1424 + public function render_start( $metas, $settings, $temp_args ) {
1425 + ob_start();
976 1426
977 - if ( empty( $text ) ) {
978 - $tooltip_attr = ' title="' . esc_attr__( 'Add to Cart', 'shopbuilder' ) . '"';
1427 + /**
1428 + * Before render row hook.
1429 + */
1430 + do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $temp_args, $this );
979 1431
980 - if ( 'variable' === $type ) {
981 - $tooltip_attr = ' title="' . esc_attr__( 'Select Options', 'shopbuilder' ) . '"';
1432 + return ob_get_clean();
1433 + }
1434 +
1435 + /**
1436 + * End the rendering.
1437 + *
1438 + * @param array $metas Meta Data.
1439 + * @param array $settings Widget settings.
1440 + * @param array $products Array of products being rendered.
1441 + *
1442 + * @return string
1443 + */
1444 + public function render_end( $metas, $settings, $products ) {
1445 + ob_start();
1446 +
1447 + /**
1448 + * After render row hook.
1449 + */
1450 + do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $products );
1451 +
1452 + return ob_get_clean();
1453 + }
1454 +
1455 + /**
1456 + * Add render attribute.
1457 + *
1458 + * @param array|string $element The HTML element.
1459 + * @param array|string $key Optional. Attribute key. Default is null.
1460 + * @param array|string $value Optional. Attribute value. Default is null.
1461 + * @param bool $overwrite Optional. Whether to overwrite existing.
1462 + *
1463 + * @return self
1464 + */
1465 + public function add_attribute( $element, $key = null, $value = null, $overwrite = false ) {
1466 + if ( is_array( $element ) ) {
1467 + foreach ( $element as $element_key => $attributes ) {
1468 + $this->add_attribute( $element_key, $attributes, null, $overwrite );
982 1469 }
983 1470
984 - if ( 'grouped' === $type ) {
985 - $tooltip_attr = ' title="' . esc_attr__( 'View Products', 'shopbuilder' ) . '"';
1471 + return $this;
1472 + }
1473 +
1474 + if ( is_array( $key ) ) {
1475 + foreach ( $key as $attribute_key => $attributes ) {
1476 + $this->add_attribute( $element, $attribute_key, $attributes, $overwrite );
986 1477 }
987 1478
988 - if ( 'external' === $type ) {
989 - $tooltip_txt = ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' );
990 - $tooltip_attr = ' title="' . esc_attr( $tooltip_txt ) . '"';
991 - }
1479 + return $this;
992 1480 }
993 1481
994 - ob_start();
995 - do_action( 'rtsb/before/cart/button' );
996 - $content .= ob_get_clean();
1482 + if ( empty( $this->attributes[ $element ][ $key ] ) ) {
1483 + $this->attributes[ $element ][ $key ] = [];
1484 + }
997 1485
998 - if ( 'variable' === $type ) {
999 - $data_cart_text = ! empty( $text ) ? esc_attr( $text ) : __( 'Add to Cart', 'shopbuilder' );
1000 - $variable_text = __( 'Select Options', 'shopbuilder' );
1486 + settype( $value, 'array' );
1001 1487
1002 - $content .= sprintf(
1003 - '<div class="rtsb-wc-add-to-cart-wrap">
1004 - <a href="%1$s" data-quantity="1" class="rtsb-action-btn variable-product tipsy icon-%4$s %5$s" data-id="%3$d" data-product_id="%3$s" rel="nofollow"%6$s>%7$s<span class="text" data-cart-text="%8$s" data-variable-text="%9$s" data-success="%10$s">%2$s</span><span></span></a>
1005 - </div>',
1006 - esc_url( $link ),
1007 - esc_html__( 'Select Options', 'shopbuilder' ),
1008 - absint( $id ),
1009 - esc_attr( $alignment ),
1010 - empty( $text ) ? 'no-text' : 'has-text',
1011 - $tooltip_attr,
1012 - $icon_html,
1013 - esc_attr( $data_cart_text ),
1014 - esc_attr( $variable_text ),
1015 - esc_html( $success ),
1016 - );
1017 - } elseif ( 'grouped' === $type ) {
1018 - $content .= sprintf(
1019 - '<div class="rtsb-wc-add-to-cart-wrap">
1020 - <a href="%1$s" data-quantity="1" class="rtsb-action-btn variable-product tipsy icon-%4$s %5$s" data-product_id="%3$s" rel="nofollow"%6$s>%7$s<span class="text">%2$s</span></a>
1021 - </div>',
1022 - esc_url( $link ),
1023 - esc_html__( 'View Products', 'shopbuilder' ),
1024 - absint( $id ),
1025 - esc_attr( $alignment ),
1026 - empty( $text ) ? 'no-text' : 'has-text',
1027 - $tooltip_attr,
1028 - $icon_html,
1029 - );
1030 - } elseif ( 'external' === $type ) {
1031 - $content .= sprintf(
1032 - '<div class="rtsb-wc-add-to-cart-wrap">
1033 - <a href="%1$s" data-quantity="1" class="rtsb-action-btn variable-product tipsy icon-%4$s %5$s" data-product_id="%3$s" rel="nofollow"%6$s %8$s>%7$s<span class="text">%2$s</span></a>
1034 - </div>',
1035 - ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link ),
1036 - ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1037 - absint( $id ),
1038 - esc_attr( $alignment ),
1039 - empty( $text ) ? 'no-text' : 'has-text',
1040 - $tooltip_attr,
1041 - $icon_html,
1042 - ! empty( $ext_link ) ? ' target="_blank"' : ''
1043 - );
1488 + if ( $overwrite ) {
1489 + $this->attributes[ $element ][ $key ] = $value;
1044 1490 } else {
1045 - $content .= sprintf(
1046 - '<a href="%1$s?add-to-cart=%2$d" data-quantity="1" class="rtsb-action-btn rtsb-add-to-cart-btn tipsy icon-%6$s %7$s" data-id="%2$d" data-type="%3$s"%8$s>%4$s<span class="text %10$s" data-success="%9$s">%5$s</span><span></span></a>',
1047 - esc_url( $link ),
1048 - absint( $id ),
1049 - esc_attr( $type ),
1050 - $icon_html,
1051 - esc_html( $text ),
1052 - esc_attr( $alignment ),
1053 - empty( $text ) ? 'no-text' : 'has-text',
1054 - $tooltip_attr,
1055 - esc_html( $success ),
1056 - ! empty( $success ) ? 'has-success-text' : 'no-success-text',
1057 - );
1491 + $this->attributes[ $element ][ $key ] = array_merge( $this->attributes[ $element ][ $key ], $value );
1058 1492 }
1059 1493
1060 - ob_start();
1061 - do_action( 'rtsb/after/cart/button' );
1062 - $content .= ob_get_clean();
1494 + return $this;
1495 + }
1063 1496
1064 - return $content;
1497 + /**
1498 + * Add link render attributes.
1499 + *
1500 + * @param array|string $element The HTML element.
1501 + * @param array $url_control Array of link settings.
1502 + * @param bool $overwrite Optional. Whether to overwrite existing
1503 + * attribute. Default is false, not to overwrite.
1504 + *
1505 + * @return Render
1506 + */
1507 + public function add_link_attributes( $element, array $url_control, $overwrite = false ) {
1508 + $attributes = [];
1509 +
1510 + if ( ! empty( $url_control['url'] ) ) {
1511 + $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] );
1512 +
1513 + $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols );
1514 + }
1515 +
1516 + if ( ! empty( $url_control['is_external'] ) ) {
1517 + $attributes['target'] = '_blank';
1518 + }
1519 +
1520 + if ( ! empty( $url_control['nofollow'] ) ) {
1521 + $attributes['rel'] = 'nofollow';
1522 + }
1523 +
1524 + if ( ! empty( $url_control['custom_attributes'] ) ) {
1525 + $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) );
1526 + }
1527 +
1528 + if ( $attributes ) {
1529 + $this->add_attribute( $element, $attributes, null, $overwrite );
1530 + }
1531 +
1532 + return $this;
1065 1533 }
1066 1534
1067 1535 /**
1068 - * Button classes.
1536 + * Get render attribute string.
1069 1537 *
1070 - * @param array $params Button params.
1538 + * @param string $element The element.
1071 1539 *
1072 - * @return array
1540 + * @return string
1073 1541 */
1074 - public function button_classes( $params ) {
1075 - $params['classes'] = array_merge( $params['classes'], [ 'rtsb-action-btn', 'tipsy' ] );
1542 + public function get_attribute_string( $element ) {
1543 + if ( empty( $this->attributes[ $element ] ) ) {
1544 + return '';
1545 + }
1076 1546
1077 - return $params;
1547 + if ( class_exists( '\Elementor\Utils' ) ) {
1548 + return Utils::render_html_attributes( $this->attributes[ $element ] );
1549 + }
1550 +
1551 + // Fallback.
1552 + return $this->render_fallback_attributes( $this->attributes[ $element ] );
1553 + }
1554 +
1555 + /**
1556 + * Function to render link attributes.
1557 + *
1558 + * @param string $id Element ID.
1559 + * @param array $control_name Control name.
1560 + * @param string $class_name Class name.
1561 + *
1562 + * @return string
1563 + */
1564 + public function render_link_attributes( $id, $control_name, $class_name = 'logo-img-link' ) {
1565 + $this->add_attribute( $id, 'class', $class_name );
1566 +
1567 + if ( ! empty( $control_name['url'] ) ) {
1568 + $this->add_link_attributes( $id, $control_name );
1569 + } else {
1570 + $this->add_attribute( $id, 'role', 'button' );
1571 + }
1572 +
1573 + return $this->get_attribute_string( $id );
1574 + }
1575 +
1576 + /**
1577 + * Render fallback attributes.
1578 + *
1579 + * @param array $attributes The attributes.
1580 + *
1581 + * @return string
1582 + */
1583 + private function render_fallback_attributes( $attributes ) {
1584 + $rendered_attributes = [];
1585 +
1586 + foreach ( $attributes as $attribute_key => $attribute_values ) {
1587 + if ( is_array( $attribute_values ) ) {
1588 + $attribute_values = implode( ' ', $attribute_values );
1589 + }
1590 +
1591 + $rendered_attributes[] = sprintf( '%1$s="%2$s"', $attribute_key, esc_attr( $attribute_values ) );
1592 + }
1593 +
1594 + return implode( ' ', $rendered_attributes );
1078 1595 }
1079 1596 }