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 +281 -66 2.1.33.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 - if ( Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' ) ) {
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
@@ -919,18 +1013,21 @@
919 1013
920 1014 $arg['class'] .= ' animated rtFadeIn';
921 1015
922 1016 if ( in_array( 'badges', $meta['visibility'], true ) ) {
923 - $arg['p_sale'] = Fns::get_promo_badge(
1017 + $badge_module = ! empty( $meta['enable_badges_module'] );
1018 + $arg['p_sale'] = Fns::is_module_active( 'product_badges' ) && $badge_module ? 'rtsb_yes' : Fns::get_promo_badge(
924 1019 $product,
925 1020 $meta['sale_badge_type'],
926 1021 $meta['sale_badge_text'],
927 - $meta['out_of_stock_badge_text']
1022 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
928 1023 );
929 1024 }
930 1025
931 1026 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
932 1027
1028 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
1029 +
933 1030 if ( ! $meta['show_cart_text'] ) {
934 1031 $meta['add_to_cart_text'] = '';
935 1032 $meta['add_to_cart_success_text'] = '';
936 1033 }
@@ -956,12 +1053,28 @@
956 1053 'text' => $meta['add_to_cart_text'],
957 1054 'success' => $meta['add_to_cart_success_text'],
958 1055 'icon_html' => $cart_icon,
959 1056 'alignment' => $meta['add_to_cart_alignment'],
1057 + 'product' => $product,
960 1058 ]
961 1059 );
962 1060 }
963 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 + }
964 1077 }
965 1078
966 1079 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
967 1080 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -1026,9 +1139,8 @@
1026 1139 ];
1027 1140
1028 1141 return $arg;
1029 1142 }
1030 -
1031 1143 /**
1032 1144 * Renders add to cart button.
1033 1145 *
1034 1146 * @param array $args Cart args.
@@ -1042,9 +1154,10 @@
1042 1154 'type' => $type,
1043 1155 'text' => $text,
1044 1156 'success' => $success,
1045 1157 'icon_html' => $icon_html,
1046 - 'alignment' => $alignment
1158 + 'alignment' => $alignment,
1159 + 'product' => $product
1047 1160 ) = $args;
1048 1161
1049 1162 $content = null;
1050 1163 $rand = wp_rand();
@@ -1057,9 +1170,8 @@
1057 1170
1058 1171 $cart_attr = [
1059 1172 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1060 1173 'href' => esc_url( $link ),
1061 - 'rel' => 'nofollow',
1062 1174 'data-quantity' => '1',
1063 1175 'data-product_id' => absint( $id ),
1064 1176 'data-id' => absint( $id ),
1065 1177 ];
@@ -1065,12 +1177,13 @@
1065 1177 ];
1066 1178
1067 1179 if ( empty( $text ) ) {
1068 1180 $tooltip_mapping = [
1069 - 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1070 - 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1071 - 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1072 - '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' ),
1073 1186 ];
1074 1187
1075 1188 $cart_attr['title'] = $tooltip_mapping[ $type ];
1076 1189 }
@@ -1075,18 +1188,26 @@
1075 1188 $cart_attr['title'] = $tooltip_mapping[ $type ];
1076 1189 }
1077 1190
1078 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 );
1079 1197
1080 - if ( 'variable' === $type || 'grouped' === $type ) {
1081 1198 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1082 1199
1083 - $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 + );
1084 1206
1085 1207 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1086 1208 $content .= $icon_html;
1087 - $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>';
1088 - $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>';
1089 1210 } elseif ( 'external' === $type ) {
1090 1211 if ( ! empty( $ext_link ) ) {
1091 1212 $cart_attr['target'] = '_blank';
1092 1213 }
@@ -1096,20 +1217,20 @@
1096 1217 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1097 1218
1098 1219 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1099 1220 $content .= $icon_html;
1100 - $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>';
1101 1222 } else {
1102 1223 $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1103 1224 $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1104 1225 $cart_attr['data-type'] = $type;
1226 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1105 1227
1106 1228 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1107 1229
1108 1230 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1109 1231 $content .= $icon_html;
1110 - $content .= '<span class="text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '">' . esc_html( $text ) . '</span>';
1111 - $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>';
1112 1233 }
1113 1234
1114 1235 $content .= '</a>';
1115 1236 $content .= '</div>';
@@ -1213,8 +1334,17 @@
1213 1334 $total_page,
1214 1335 'wc' === $query_type ? $per_page : $posts_per_page
1215 1336 );
1216 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 + }
1217 1347 } else {
1218 1348 ob_start();
1219 1349 do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1220 1350 $html_utility .= ob_get_clean();
@@ -1280,9 +1410,9 @@
1280 1410 * @return void
1281 1411 */
1282 1412 public function no_products_msg( $metas ) {
1283 1413 $content =
1284 - '<div class="woocommerce-no-products-found">
1414 + '<div class="rtsb-notice woocommerce-no-products-found">
1285 1415 <div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div>
1286 1416 </div>';
1287 1417
1288 1418 $this->row( $content, $metas );
@@ -1370,8 +1500,46 @@
1370 1500 return $this;
1371 1501 }
1372 1502
1373 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 + /**
1374 1542 * Get render attribute string.
1375 1543 *
1376 1544 * @param string $element The element.
1377 1545 *
@@ -1381,7 +1549,54 @@
1381 1549 if ( empty( $this->attributes[ $element ] ) ) {
1382 1550 return '';
1383 1551 }
1384 1552
1385 - 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 );
1386 1601 }
1387 1602 }