PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.4.2
ShopBuilder – WooCommerce Builder For Elementor v3.4.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
shopbuilder / app / Elementor / Render / Render.php

Render.php in ShopBuilder – WooCommerce Builder For Elementor 3.4.2, at app/Elementor/Render/Render.php

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