PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.4.1
ShopBuilder – WooCommerce Builder For Elementor v3.4.1
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.1, at app/Elementor/Render/Render.php

1,603 lines 50.0 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 );
543 } elseif ( 'pagination' === $posts_loading_type ) {
544 $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, false );
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 $arg['img_html'] = $meta['f_img'] ? Fns::get_product_image_html(
1095 'product',
1096 $post_id,
1097 $meta['f_img_size'],
1098 $meta['default_img_id'],
1099 $meta['custom_img_size'],
1100 $lazy_load
1101 ) : null;
1102
1103 if ( ! ( function_exists( 'rtwpvsp' ) && $product->is_type( 'variable' ) && $product->get_attributes() ) ) {
1104 $gallery_ids = $product->get_gallery_image_ids();
1105
1106 if ( $meta['hover_img'] && ! empty( $gallery_ids ) ) {
1107 $arg['class'] .= ! $meta['gallery_img'] ? ' rtsb-double-img' : '';
1108
1109 if ( ! rtsb()->has_pro() ) {
1110 $arg['class'] .= ' rtsb-double-img';
1111 }
1112
1113 $arg['hover_img_html'] = Fns::get_product_image_html(
1114 'product',
1115 $gallery_ids[0],
1116 $meta['f_img_size'],
1117 null,
1118 $meta['custom_img_size'],
1119 $lazy_load,
1120 true
1121 );
1122 }
1123 }
1124
1125 $arg['hover_btn_text'] = $meta['hover_btn_text'];
1126
1127 if ( $meta['show_hover_btn_icon'] && ! empty( $meta['hover_btn_icon'] ) ) {
1128 $arg['hover_btn_icon'] = '<span class="icon">' . Fns::icons_manager( $meta['hover_btn_icon'], 'hover-btn-icon' ) . '</span>';
1129 }
1130
1131 $arg['img_args'] = [
1132 'p_id' => $arg['p_id'],
1133 'title' => get_the_title(),
1134 'p_link' => $arg['p_link'],
1135 'image_link' => $arg['image_link'],
1136 'img_html' => $arg['img_html'],
1137 'hover_img_html' => $arg['hover_img_html'],
1138 'meta' => $meta,
1139 ];
1140
1141 return $arg;
1142 }
1143 /**
1144 * Renders add to cart button.
1145 *
1146 * @param array $args Cart args.
1147 *
1148 * @return string
1149 */
1150 public function render_add_to_cart_button( $args ) {
1151 list(
1152 'link' => $link,
1153 'id' => $id,
1154 'type' => $type,
1155 'text' => $text,
1156 'success' => $success,
1157 'icon_html' => $icon_html,
1158 'alignment' => $alignment,
1159 'product' => $product
1160 ) = $args;
1161
1162 $content = null;
1163 $rand = wp_rand();
1164 $ext_link = get_post_meta( $id, '_product_url', true );
1165 $btn_txt = get_post_meta( $id, '_button_text', true );
1166
1167 ob_start();
1168 do_action( 'rtsb/before/cart/button' );
1169 $content .= ob_get_clean();
1170
1171 $cart_attr = [
1172 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1173 'href' => esc_url( $link ),
1174 'data-quantity' => '1',
1175 'data-product_id' => absint( $id ),
1176 'data-id' => absint( $id ),
1177 ];
1178
1179 if ( empty( $text ) ) {
1180 $tooltip_mapping = [
1181 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1182 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1183 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1184 'external' => ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1185 'rtsb-gift-card' => esc_attr__( 'Select Gift Card', 'shopbuilder' ),
1186 ];
1187
1188 $cart_attr['title'] = $tooltip_mapping[ $type ];
1189 }
1190
1191 $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1192 if ( 'variable' === $type ) {
1193 $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
1194 }
1195 if ( 'variable' === $type || 'grouped' === $type ) {
1196 $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1197
1198 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1199
1200 $text_btn = apply_filters(
1201 'rtsb/elementor/render/add_to_cart_text',
1202 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' ),
1203 $product,
1204 $type
1205 );
1206
1207 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1208 $content .= $icon_html;
1209 $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>';
1210 } elseif ( 'external' === $type ) {
1211 if ( ! empty( $ext_link ) ) {
1212 $cart_attr['target'] = '_blank';
1213 }
1214
1215 $cart_attr['href'] = ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link );
1216
1217 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1218
1219 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1220 $content .= $icon_html;
1221 $content .= '<span class="button-text text">' . ( ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ) ) . '</span>';
1222 } else {
1223 $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1224 $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1225 $cart_attr['data-type'] = $type;
1226 $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1227
1228 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1229
1230 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1231 $content .= $icon_html;
1232 $content .= '<span class="button-text text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '">' . esc_html( $text ) . '</span>';
1233 }
1234
1235 $content .= '</a>';
1236 $content .= '</div>';
1237
1238 ob_start();
1239 do_action( 'rtsb/after/cart/button' );
1240 $content .= ob_get_clean();
1241
1242 return apply_filters( 'rtsb/render/cart/button', $content, $args );
1243 }
1244
1245 /**
1246 * Button classes.
1247 *
1248 * @param array $params Button params.
1249 *
1250 * @return array
1251 */
1252 public function button_classes( $params ) {
1253 $params['classes'] = array_merge( $params['classes'], [ 'rtsb-action-btn', 'tipsy' ] );
1254
1255 return $params;
1256 }
1257
1258 /**
1259 * Render the section title.
1260 *
1261 * @param array $settings The Elementor settings.
1262 *
1263 * @return string
1264 */
1265 public function render_section_title( $settings ) {
1266 $html = '';
1267 $rand = wp_rand();
1268
1269 if ( empty( $settings['show_section_title'] ) || ( empty( $settings['query_products'] ) && 0 === count( $settings['query_products'] ) ) ) {
1270 return $html;
1271 }
1272
1273 // Set attributes for the section title.
1274 $this->add_attribute( 'rtsb_section_title_wrapper_' . $rand, 'class', 'rtsb-section-title-wrapper' );
1275 $this->add_attribute( 'rtsb_section_title_' . $rand, 'class', 'rtsb-section-title' );
1276
1277 // Start rendering.
1278 $html .= '<div ' . $this->get_attribute_string( 'rtsb_section_title_wrapper_' . $rand ) . '>';
1279 $html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' ' . $this->get_attribute_string( 'rtsb_section_title_' . $rand ) . '>';
1280 $html .= esc_html( $settings['section_title_text'] );
1281 $html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
1282 $html .= '</div>';
1283
1284 return $html;
1285 }
1286
1287 /**
1288 * Renders products pagination
1289 *
1290 * @param array $args Pagination args.
1291 * @param string $query_type Query type.
1292 *
1293 * @return string
1294 */
1295 public function render_products_pagination( $args, $query_type = 'wc' ) {
1296 list(
1297 'query' => $query,
1298 'meta' => $meta,
1299 'limit' => $limit,
1300 'per_page' => $per_page
1301 ) = $args;
1302
1303 $html_utility = null;
1304 $html = null;
1305 $ajax = false;
1306 $is_grid = preg_match( '/grid-layout/', $meta['layout'] ) || preg_match( '/list-layout/', $meta['layout'] );
1307 $posts_per_page = 'wp' === $query_type ? $query->query_vars['posts_per_page'] : null;
1308 $total_page = 'wp' === $query_type ? $query->max_num_pages : $query->get_products()->max_num_pages;
1309
1310 if ( $limit ) {
1311 if ( 'wc' === $query_type ) {
1312 $found_post = $query->get_products()->total;
1313 } elseif ( 'wp' === $query_type && ( empty( $wp_query->query['tax_query'] ) ) ) {
1314 $found_post = $query->found_posts;
1315 }
1316
1317 if ( $per_page && $found_post > $per_page ) {
1318 $found_post = $limit;
1319 $total_page = ceil( $found_post / $per_page );
1320 }
1321 }
1322
1323 $total_page = absint( $total_page );
1324
1325 $args = [
1326 'total_page' => $total_page,
1327 'posts_per_page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1328 'ajax' => $ajax,
1329 ];
1330
1331 if ( 'pagination' === $meta['posts_loading_type'] ) {
1332 if ( $is_grid ) {
1333 $html_utility .= Fns::custom_pagination(
1334 $total_page,
1335 'wc' === $query_type ? $per_page : $posts_per_page
1336 );
1337 }
1338 } elseif ( 'pagination_ajax' === $meta['posts_loading_type'] && ! rtsb()->has_pro() ) {
1339 // Free version fallback: render non-AJAX number pagination so filter
1340 // params in the URL are preserved via get_pagenum_link().
1341 if ( $is_grid ) {
1342 $html_utility .= Fns::custom_pagination(
1343 $total_page,
1344 'wc' === $query_type ? $per_page : $posts_per_page
1345 );
1346 }
1347 } else {
1348 ob_start();
1349 do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1350 $html_utility .= ob_get_clean();
1351 }
1352
1353 if ( $html_utility ) {
1354 $rand = wp_rand();
1355
1356 // Adding pagination render attributes.
1357 $this->add_attribute(
1358 'rtsb_pagination_attr_' . $rand,
1359 [
1360 'class' => 'rtsb-pagination-wrap element-loading',
1361 'data-total-pages' => $total_page,
1362 'data-posts-per-page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1363 'data-type' => $meta['posts_loading_type'],
1364 ]
1365 );
1366
1367 // Start rendering.
1368 $html = '<div ' . $this->get_attribute_string( 'rtsb_pagination_attr_' . $rand ) . '>';
1369 $html .= $html_utility;
1370 $html .= '</div><!-- .rtsb-pagination-wrap -->';
1371 }
1372
1373 return $html;
1374 }
1375
1376 /**
1377 * Renders slider buttons
1378 *
1379 * @param array $settings Settings array.
1380 *
1381 * @return string
1382 */
1383 public function render_slider_buttons( $settings ) {
1384 $html = '';
1385 $left_icon = $settings['left_arrow_icon'];
1386 $right_icon = $settings['right_arrow_icon'];
1387
1388 if ( ! empty( $settings['slider_nav'] ) ) {
1389 $html .=
1390 '<div class="swiper-nav">
1391 <div class="swiper-arrow swiper-button-next">
1392 ' . Fns::icons_manager( $right_icon ) . '
1393 </div>
1394 <div class="swiper-arrow swiper-button-prev">
1395 ' . Fns::icons_manager( $left_icon ) . '
1396 </div>
1397 </div>';
1398 }
1399
1400 $html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
1401
1402 return $html;
1403 }
1404
1405 /**
1406 * No products message.
1407 *
1408 * @param array $metas Meta data.
1409 *
1410 * @return void
1411 */
1412 public function no_products_msg( $metas ) {
1413 $content =
1414 '<div class="rtsb-notice woocommerce-no-products-found">
1415 <div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div>
1416 </div>';
1417
1418 $this->row( $content, $metas );
1419 }
1420
1421 /**
1422 * Start the rendering.
1423 *
1424 * @param array $metas Meta Data.
1425 * @param array $settings Widget settings.
1426 * @param array $temp_args Additional temporary arguments for rendering.
1427 *
1428 * @return string
1429 */
1430 public function render_start( $metas, $settings, $temp_args ) {
1431 ob_start();
1432
1433 /**
1434 * Before render row hook.
1435 */
1436 do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $temp_args, $this );
1437
1438 return ob_get_clean();
1439 }
1440
1441 /**
1442 * End the rendering.
1443 *
1444 * @param array $metas Meta Data.
1445 * @param array $settings Widget settings.
1446 * @param array $products Array of products being rendered.
1447 *
1448 * @return string
1449 */
1450 public function render_end( $metas, $settings, $products ) {
1451 ob_start();
1452
1453 /**
1454 * After render row hook.
1455 */
1456 do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $products );
1457
1458 return ob_get_clean();
1459 }
1460
1461 /**
1462 * Add render attribute.
1463 *
1464 * @param array|string $element The HTML element.
1465 * @param array|string $key Optional. Attribute key. Default is null.
1466 * @param array|string $value Optional. Attribute value. Default is null.
1467 * @param bool $overwrite Optional. Whether to overwrite existing.
1468 *
1469 * @return self
1470 */
1471 public function add_attribute( $element, $key = null, $value = null, $overwrite = false ) {
1472 if ( is_array( $element ) ) {
1473 foreach ( $element as $element_key => $attributes ) {
1474 $this->add_attribute( $element_key, $attributes, null, $overwrite );
1475 }
1476
1477 return $this;
1478 }
1479
1480 if ( is_array( $key ) ) {
1481 foreach ( $key as $attribute_key => $attributes ) {
1482 $this->add_attribute( $element, $attribute_key, $attributes, $overwrite );
1483 }
1484
1485 return $this;
1486 }
1487
1488 if ( empty( $this->attributes[ $element ][ $key ] ) ) {
1489 $this->attributes[ $element ][ $key ] = [];
1490 }
1491
1492 settype( $value, 'array' );
1493
1494 if ( $overwrite ) {
1495 $this->attributes[ $element ][ $key ] = $value;
1496 } else {
1497 $this->attributes[ $element ][ $key ] = array_merge( $this->attributes[ $element ][ $key ], $value );
1498 }
1499
1500 return $this;
1501 }
1502
1503 /**
1504 * Add link render attributes.
1505 *
1506 * @param array|string $element The HTML element.
1507 * @param array $url_control Array of link settings.
1508 * @param bool $overwrite Optional. Whether to overwrite existing
1509 * attribute. Default is false, not to overwrite.
1510 *
1511 * @return Render
1512 */
1513 public function add_link_attributes( $element, array $url_control, $overwrite = false ) {
1514 $attributes = [];
1515
1516 if ( ! empty( $url_control['url'] ) ) {
1517 $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] );
1518
1519 $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols );
1520 }
1521
1522 if ( ! empty( $url_control['is_external'] ) ) {
1523 $attributes['target'] = '_blank';
1524 }
1525
1526 if ( ! empty( $url_control['nofollow'] ) ) {
1527 $attributes['rel'] = 'nofollow';
1528 }
1529
1530 if ( ! empty( $url_control['custom_attributes'] ) ) {
1531 $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) );
1532 }
1533
1534 if ( $attributes ) {
1535 $this->add_attribute( $element, $attributes, null, $overwrite );
1536 }
1537
1538 return $this;
1539 }
1540
1541 /**
1542 * Get render attribute string.
1543 *
1544 * @param string $element The element.
1545 *
1546 * @return string
1547 */
1548 public function get_attribute_string( $element ) {
1549 if ( empty( $this->attributes[ $element ] ) ) {
1550 return '';
1551 }
1552
1553 if ( class_exists( '\Elementor\Utils' ) ) {
1554 return Utils::render_html_attributes( $this->attributes[ $element ] );
1555 }
1556
1557 // Fallback.
1558 return $this->render_fallback_attributes( $this->attributes[ $element ] );
1559 }
1560
1561 /**
1562 * Function to render link attributes.
1563 *
1564 * @param string $id Element ID.
1565 * @param array $control_name Control name.
1566 * @param string $class_name Class name.
1567 *
1568 * @return string
1569 */
1570 public function render_link_attributes( $id, $control_name, $class_name = 'logo-img-link' ) {
1571 $this->add_attribute( $id, 'class', $class_name );
1572
1573 if ( ! empty( $control_name['url'] ) ) {
1574 $this->add_link_attributes( $id, $control_name );
1575 } else {
1576 $this->add_attribute( $id, 'role', 'button' );
1577 }
1578
1579 return $this->get_attribute_string( $id );
1580 }
1581
1582 /**
1583 * Render fallback attributes.
1584 *
1585 * @param array $attributes The attributes.
1586 *
1587 * @return string
1588 */
1589 private function render_fallback_attributes( $attributes ) {
1590 $rendered_attributes = [];
1591
1592 foreach ( $attributes as $attribute_key => $attribute_values ) {
1593 if ( is_array( $attribute_values ) ) {
1594 $attribute_values = implode( ' ', $attribute_values );
1595 }
1596
1597 $rendered_attributes[] = sprintf( '%1$s="%2$s"', $attribute_key, esc_attr( $attribute_values ) );
1598 }
1599
1600 return implode( ' ', $rendered_attributes );
1601 }
1602 }
1603