PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 2.1.14
ShopBuilder – WooCommerce Builder For Elementor v2.1.14
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 2.1.14, at app/Elementor/Render/Render.php

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