PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.4.0
ShopBuilder – WooCommerce Builder For Elementor v3.4.0
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
← All changes | app/Elementor/Render/Render.php +276 -62 2.1.123.4.0 View file →
@@ -8,8 +8,9 @@
8 8 */
9 9
10 10 namespace RadiusTheme\SB\Elementor\Render;
11 11
12 +use RadiusTheme\SBPRO\Modules\CatalogMode\CatalogModeFns;
12 13 use WC_Query;
13 14 use WP_Query;
14 15 use WC_Product;
15 16 use Elementor\Utils;
@@ -85,9 +86,10 @@
85 86 public function container( $content, $settings, $class = '', $template = '', $fullwidth = false, $ajax = true ) {
86 87 $rand = wp_rand();
87 88 $layout_id = 'rtsb-container-' . $rand;
88 89 $metas = RenderHelpers::meta_dataset( $settings, $template );
89 - $layout = $metas['layout'];
90 + $raw_layout = esc_html( $metas['layout'] );
91 + $layout = RenderHelpers::filter_layout( $raw_layout, $template );
90 92 $style_attr = '--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) );
91 93 $view_mode = $settings['view_mode'] ?? 'grid';
92 94 $view_mode = isset( $_GET['displayview'] ) ? sanitize_text_field( wp_unslash( $_GET['displayview'] ) ) : $view_mode; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
93 95 $tooltip_attr = 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ? esc_attr( $metas['tooltip_position_list'] ) : $metas['tooltip_position'];
@@ -92,8 +94,9 @@
92 94 $view_mode = isset( $_GET['displayview'] ) ? sanitize_text_field( wp_unslash( $_GET['displayview'] ) ) : $view_mode; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
93 95 $tooltip_attr = 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ? esc_attr( $metas['tooltip_position_list'] ) : $metas['tooltip_position'];
94 96 $has_pro = rtsb()->has_pro();
95 97 $has_filters = $has_pro && ! empty( $settings['tax_filter'] );
98 + $grid_style = $settings['grid_style'] ?? 'even';
96 99
97 100 // Set default layout.
98 101 if ( ! $has_pro && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
99 102 $layout = RenderHelpers::set_default_layout( $layout );
@@ -98,8 +101,13 @@
98 101 if ( ! $has_pro && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
99 102 $layout = RenderHelpers::set_default_layout( $layout );
100 103 }
101 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 +
102 110 // Container classes.
103 111 $container_classes = RenderHelpers::prepare_container_classes( $metas, $settings, $class );
104 112
105 113 // Pagination attributes.
@@ -108,9 +116,9 @@
108 116 // Attributes for the container.
109 117 $container_attributes = [
110 118 'id' => $layout_id,
111 119 'class' => $container_classes,
112 - 'style' => $style_attr,
120 + 'style' => apply_filters( 'rtsb/product/container/attr', $style_attr, $settings ),
113 121 'data-layout' => $layout,
114 122 'data-tooltip-position' => $tooltip_attr,
115 123 ];
116 124
@@ -117,8 +125,14 @@
117 125 if ( ! empty( $pagination_attributes ) ) {
118 126 $container_attributes['data-rtsb-ajax'] = $pagination_attributes;
119 127 }
120 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 +
121 135 // Adding render attributes.
122 136 $this->add_attribute( 'rtsb_container_attr_' . $rand, $container_attributes );
123 137
124 138 // Start rendering.
@@ -140,8 +154,9 @@
140 154 $script_generator = [];
141 155 $script_generator['layout'] = $layout_id;
142 156 $script_generator['rand'] = absint( $rand );
143 157 $script_generator['isCarousel'] = preg_match( '/slider/', $layout );
158 + $script_generator['isMasonry'] = $has_pro && 'masonry' === $grid_style;
144 159
145 160 // Register scripts in the wp_footer action.
146 161 add_action(
147 162 'wp_footer',
@@ -163,13 +178,15 @@
163 178 *
164 179 * @return string
165 180 */
166 181 public function row( $content, $settings, $hook_html = '', $raw_settings = [] ) {
167 - $layout = $settings['layout'];
182 + $raw_layout = esc_html( $settings['layout'] );
183 + $layout = RenderHelpers::filter_layout( $raw_layout );
184 +
168 185 $rand = wp_rand();
169 - $is_carousel = preg_match( '/slider/', $layout );
186 + $is_carousel = preg_match( '/slider/', $raw_layout );
170 187
171 - if ( preg_match( '/category/', $layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
188 + if ( preg_match( '/category/', $raw_layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
172 189 $is_carousel = true;
173 190 }
174 191
175 192 // Row classes.
@@ -185,9 +202,8 @@
185 202 $this->html = ! empty( $settings['show_filter'] ) ? $hook_html . $start_wrapper : $start_wrapper;
186 203
187 204 if ( $is_carousel ) {
188 205 $slider_data = RenderHelpers::slider_data( $settings, $raw_settings );
189 -
190 206 // Adding slider render attributes.
191 207 $this->add_attribute(
192 208 'rtsb_slider_attr_' . $rand,
193 209 [
@@ -237,12 +253,21 @@
237 253 }
238 254
239 255 $i = 0;
240 256
241 - foreach ( $products as $product ) {
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 ) {
242 268 $i++;
243 - $GLOBALS['product'] = $product;
244 -
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.
245 270 /**
246 271 * Before product template render hook.
247 272 */
248 273 do_action( 'rtsb_before_product_template_render' );
@@ -247,12 +272,12 @@
247 272 */
248 273 do_action( 'rtsb_before_product_template_render' );
249 274
250 275 // Loop arg.
251 - $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
276 + $arg = $this->arg_dataset( $settings, $_product, $settings['lazy_load'], $i );
252 277
253 278 // Get template.
254 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
279 + $html .= Fns::load_template( $template . $layout, $arg, true );
255 280
256 281 /**
257 282 * After product template render hook.
258 283 */
@@ -259,9 +284,11 @@
259 284 do_action( 'rtsb_after_product_template_render' );
260 285
261 286 unset( $GLOBALS['product'] );
262 287 }
263 -
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 + }
264 291 return $html;
265 292 }
266 293
267 294 /**
@@ -279,19 +306,20 @@
279 306 if ( empty( $query ) ) {
280 307 return null;
281 308 }
282 309
283 - $i = 0;
310 + $i = 0;
311 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
284 312
285 313 while ( $query->have_posts() ) {
286 314 $query->the_post();
287 315 $i++;
288 -
316 + global $product;
289 317 // Loop arg.
290 - $arg = $this->arg_dataset( $settings, wc_get_product( get_the_ID() ), $settings['lazy_load'], $i );
318 + $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
291 319
292 320 // Get template.
293 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
321 + $html .= Fns::load_template( $template . $layout, $arg, true );
294 322 }
295 323
296 324 return $html;
297 325 }
@@ -364,14 +392,20 @@
364 392 * Before Product query hook.
365 393 *
366 394 * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::modify_wc_query_args 10
367 395 */
396 +
368 397 do_action( 'rtsb/elements/render/before_query', $settings, $args );
369 -
370 398 // Query.
371 399 $product_query = new WC_Product_Query( $args );
372 - $products = $pagination ? $product_query->get_products()->products : $product_query->get_products();
373 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 +
374 408 if ( ! empty( $products ) ) {
375 409 // Start rendering.
376 410 $hook_html .= $this->render_start( $metas, $settings, $temp_args );
377 411
@@ -428,17 +462,21 @@
428 462 $this->get_settings = $settings;
429 463 $rand = wp_rand();
430 464 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
431 465
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';
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';
434 469
435 470 $pagination = $metas['pagination'];
436 471
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 -
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;
441 479 if ( $is_preview ) {
442 480 $main_query = clone $wp_query;
443 481 $main_post = clone $post;
444 482
@@ -459,12 +497,30 @@
459 497 $wp_query = new WP_Query( $wp_query_args ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
460 498
461 499 wc_set_loop_prop( 'total', $wp_query->found_posts );
462 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;
463 515 }
464 516
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>';
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>';
467 523 }
468 524
469 525 // column_per_row.
470 526 if ( woocommerce_product_loop() ) {
@@ -474,28 +530,44 @@
474 530 $this->row( $this->wc_product_loop( $wp_query, $metas, $template ), $metas, '', $settings );
475 531 }
476 532
477 533 // Pagination.
478 - $this->html .= '<div class="rtsb-archive-pagination-wrap">';
534 + $this->html .= '<div class="rtsb-archive-pagination-wrap" data-per-page="' . absint( $posts_per_page ) . '">';
479 535
480 536 if ( $pagination ) {
481 537 if ( Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) ) {
482 538 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 - );
539 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
490 540
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>";
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 + }
498 570 }
499 571 } else {
500 572 $pagination_args = [
501 573 'query' => $wp_query,
@@ -512,9 +584,9 @@
512 584 } else {
513 585 $this->no_products_msg( $metas );
514 586 }
515 587
516 - if ( $is_preview ) {
588 + if ( $is_preview || ! empty( $restore_query ) ) {
517 589 // phpcs:disable
518 590 $wp_query = $main_query;
519 591 $post = $main_post;
520 592
@@ -522,8 +594,11 @@
522 594 wp_reset_postdata();
523 595 // phpcs:enable
524 596 }
525 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 +
526 601 // Container.
527 602 $this->container( $this->html, $settings, 'rtsb-products-container', $template );
528 603
529 604 return $this->html;
@@ -577,8 +652,13 @@
577 652 *
578 653 * @return string
579 654 */
580 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 = '';
581 661 $this->get_settings = $settings;
582 662
583 663 $metas = RenderHelpers::meta_dataset( $settings, $template, $this->get_settings_for_display() );
584 664 $taxonomy = $metas['category_source'];
@@ -586,8 +666,11 @@
586 666
587 667 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
588 668 $term = $metas['tag_term'];
589 669 }
670 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
671 + $term = $metas['brand_term'];
672 + }
590 673
591 674 // Query.
592 675 if ( $is_single ) {
593 676 if ( empty( $term ) ) {
@@ -605,9 +688,9 @@
605 688 if ( ! is_wp_error( $taxonomy_query ) && ! empty( $taxonomy_query ) ) {
606 689 // Row.
607 690 $this->row( $this->category_loop( $taxonomy_query, $metas, $template, $is_single ), $metas );
608 691 } else {
609 - $this->html .= '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
692 + $this->html = '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
610 693 }
611 694
612 695 // Container.
613 696 $this->container( $this->html, $settings, 'rtsb-categories-container', $is_single );
@@ -688,8 +771,9 @@
688 771 *
689 772 * @return array
690 773 */
691 774 public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
775 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
692 776 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
693 777 return [];
694 778 }
695 779
@@ -695,9 +779,9 @@
695 779
696 780 global $product;
697 781
698 782 if ( ! $product instanceof WC_Product && $post_id ) {
699 - $product = wc_get_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.
700 784 }
701 785
702 786 $post_id = $product->get_id();
703 787 $arg = $this->build_product_arg(
@@ -788,9 +872,13 @@
788 872 $arg['image_link'] = $meta['image_link'];
789 873 $arg['grid'] .= 'rtsb-col-grid ';
790 874
791 875 if ( ! $fullwidth ) {
792 - $arg['class'] .= ' even-grid-item ';
876 + if ( 'masonry' === $meta['grid_type'] ) {
877 + $arg['class'] .= 'masonry-grid-item ';
878 + } else {
879 + $arg['class'] .= 'even-grid-item ';
880 + }
793 881 }
794 882
795 883 if ( ! empty( $meta['active_cat_slider'] ) ) {
796 884 $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
@@ -864,8 +952,9 @@
864 952 $arg['anchor_class'] = null;
865 953 $arg['hover_img_html'] = null;
866 954 $arg['s_link'] = [];
867 955 $arg['add_to_cart'] = '';
956 + $arg['p_counter'] = absint( $i );
868 957 $arg['p_id'] = $post_id;
869 958 $arg['product'] = $product;
870 959 $arg['p_link'] = $product->get_permalink();
871 960 $arg['p_sale'] = '';
@@ -896,10 +985,15 @@
896 985 $is_list = preg_match( '/list/', $layout );
897 986 $is_carousel = preg_match( '/slider/', $layout );
898 987
899 988 if ( ! $is_carousel ) {
900 - $arg['grid'] .= 'rtsb-col-grid ';
901 - $arg['class'] .= ' even-grid-item ';
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 + }
902 996 } else {
903 997 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
904 998 }
905 999
@@ -930,8 +1024,10 @@
930 1024 }
931 1025
932 1026 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
933 1027
1028 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
1029 +
934 1030 if ( ! $meta['show_cart_text'] ) {
935 1031 $meta['add_to_cart_text'] = '';
936 1032 $meta['add_to_cart_success_text'] = '';
937 1033 }
@@ -957,12 +1053,28 @@
957 1053 'text' => $meta['add_to_cart_text'],
958 1054 'success' => $meta['add_to_cart_success_text'],
959 1055 'icon_html' => $cart_icon,
960 1056 'alignment' => $meta['add_to_cart_alignment'],
1057 + 'product' => $product,
961 1058 ]
962 1059 );
963 1060 }
964 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 + }
965 1077 }
966 1078
967 1079 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
968 1080 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -1027,9 +1139,8 @@
1027 1139 ];
1028 1140
1029 1141 return $arg;
1030 1142 }
1031 -
1032 1143 /**
1033 1144 * Renders add to cart button.
1034 1145 *
1035 1146 * @param array $args Cart args.
@@ -1043,9 +1154,10 @@
1043 1154 'type' => $type,
1044 1155 'text' => $text,
1045 1156 'success' => $success,
1046 1157 'icon_html' => $icon_html,
1047 - 'alignment' => $alignment
1158 + 'alignment' => $alignment,
1159 + 'product' => $product
1048 1160 ) = $args;
1049 1161
1050 1162 $content = null;
1051 1163 $rand = wp_rand();
@@ -1058,9 +1170,8 @@
1058 1170
1059 1171 $cart_attr = [
1060 1172 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1061 1173 'href' => esc_url( $link ),
1062 - 'rel' => 'nofollow',
1063 1174 'data-quantity' => '1',
1064 1175 'data-product_id' => absint( $id ),
1065 1176 'data-id' => absint( $id ),
1066 1177 ];
@@ -1066,12 +1177,13 @@
1066 1177 ];
1067 1178
1068 1179 if ( empty( $text ) ) {
1069 1180 $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' ),
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' ),
1074 1186 ];
1075 1187
1076 1188 $cart_attr['title'] = $tooltip_mapping[ $type ];
1077 1189 }
@@ -1076,18 +1188,26 @@
1076 1188 $cart_attr['title'] = $tooltip_mapping[ $type ];
1077 1189 }
1078 1190
1079 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 );
1080 1197
1081 - if ( 'variable' === $type || 'grouped' === $type ) {
1082 1198 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1083 1199
1084 - $text_btn = 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' );
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 + );
1085 1206
1086 1207 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1087 1208 $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>';
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>';
1090 1210 } elseif ( 'external' === $type ) {
1091 1211 if ( ! empty( $ext_link ) ) {
1092 1212 $cart_attr['target'] = '_blank';
1093 1213 }
@@ -1097,20 +1217,20 @@
1097 1217 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1098 1218
1099 1219 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1100 1220 $content .= $icon_html;
1101 - $content .= '<span class="text">' . ( ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ) ) . '</span>';
1221 + $content .= '<span class="button-text text">' . ( ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ) ) . '</span>';
1102 1222 } else {
1103 1223 $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1104 1224 $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1105 1225 $cart_attr['data-type'] = $type;
1226 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1106 1227
1107 1228 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1108 1229
1109 1230 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1110 1231 $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>';
1232 + $content .= '<span class="button-text text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '">' . esc_html( $text ) . '</span>';
1113 1233 }
1114 1234
1115 1235 $content .= '</a>';
1116 1236 $content .= '</div>';
@@ -1214,8 +1334,17 @@
1214 1334 $total_page,
1215 1335 'wc' === $query_type ? $per_page : $posts_per_page
1216 1336 );
1217 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 + }
1218 1347 } else {
1219 1348 ob_start();
1220 1349 do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1221 1350 $html_utility .= ob_get_clean();
@@ -1371,8 +1500,46 @@
1371 1500 return $this;
1372 1501 }
1373 1502
1374 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 + /**
1375 1542 * Get render attribute string.
1376 1543 *
1377 1544 * @param string $element The element.
1378 1545 *
@@ -1382,7 +1549,54 @@
1382 1549 if ( empty( $this->attributes[ $element ] ) ) {
1383 1550 return '';
1384 1551 }
1385 1552
1386 - return Utils::render_html_attributes( $this->attributes[ $element ] );
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 );
1387 1601 }
1388 1602 }