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

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