PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.3.0
ShopBuilder – WooCommerce Builder For Elementor v3.3.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 +271 -63 2.1.73.3.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
@@ -140,8 +148,9 @@
140 148 $script_generator = [];
141 149 $script_generator['layout'] = $layout_id;
142 150 $script_generator['rand'] = absint( $rand );
143 151 $script_generator['isCarousel'] = preg_match( '/slider/', $layout );
152 + $script_generator['isMasonry'] = $has_pro && 'masonry' === $grid_style;
144 153
145 154 // Register scripts in the wp_footer action.
146 155 add_action(
147 156 'wp_footer',
@@ -163,13 +172,15 @@
163 172 *
164 173 * @return string
165 174 */
166 175 public function row( $content, $settings, $hook_html = '', $raw_settings = [] ) {
167 - $layout = $settings['layout'];
176 + $raw_layout = esc_html( $settings['layout'] );
177 + $layout = RenderHelpers::filter_layout( $raw_layout );
178 +
168 179 $rand = wp_rand();
169 - $is_carousel = preg_match( '/slider/', $layout );
180 + $is_carousel = preg_match( '/slider/', $raw_layout );
170 181
171 - if ( preg_match( '/category/', $layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
182 + if ( preg_match( '/category/', $raw_layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
172 183 $is_carousel = true;
173 184 }
174 185
175 186 // Row classes.
@@ -185,9 +196,8 @@
185 196 $this->html = ! empty( $settings['show_filter'] ) ? $hook_html . $start_wrapper : $start_wrapper;
186 197
187 198 if ( $is_carousel ) {
188 199 $slider_data = RenderHelpers::slider_data( $settings, $raw_settings );
189 -
190 200 // Adding slider render attributes.
191 201 $this->add_attribute(
192 202 'rtsb_slider_attr_' . $rand,
193 203 [
@@ -237,12 +247,21 @@
237 247 }
238 248
239 249 $i = 0;
240 250
241 - foreach ( $products as $product ) {
251 + global $product;
252 +
253 + $isGlobalProduct = false;
254 +
255 + if ( $product instanceof WC_Product ) {
256 + $isGlobalProduct = $product;
257 + }
258 +
259 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
260 +
261 + foreach ( $products as $_product ) {
242 262 $i++;
243 - $GLOBALS['product'] = $product;
244 -
263 + $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 264 /**
246 265 * Before product template render hook.
247 266 */
248 267 do_action( 'rtsb_before_product_template_render' );
@@ -247,12 +266,12 @@
247 266 */
248 267 do_action( 'rtsb_before_product_template_render' );
249 268
250 269 // Loop arg.
251 - $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
270 + $arg = $this->arg_dataset( $settings, $_product, $settings['lazy_load'], $i );
252 271
253 272 // Get template.
254 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
273 + $html .= Fns::load_template( $template . $layout, $arg, true );
255 274
256 275 /**
257 276 * After product template render hook.
258 277 */
@@ -259,9 +278,11 @@
259 278 do_action( 'rtsb_after_product_template_render' );
260 279
261 280 unset( $GLOBALS['product'] );
262 281 }
263 -
282 + if ( $isGlobalProduct ) {
283 + $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.
284 + }
264 285 return $html;
265 286 }
266 287
267 288 /**
@@ -279,19 +300,20 @@
279 300 if ( empty( $query ) ) {
280 301 return null;
281 302 }
282 303
283 - $i = 0;
304 + $i = 0;
305 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
284 306
285 307 while ( $query->have_posts() ) {
286 308 $query->the_post();
287 309 $i++;
288 -
310 + global $product;
289 311 // Loop arg.
290 - $arg = $this->arg_dataset( $settings, wc_get_product( get_the_ID() ), $settings['lazy_load'], $i );
312 + $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
291 313
292 314 // Get template.
293 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
315 + $html .= Fns::load_template( $template . $layout, $arg, true );
294 316 }
295 317
296 318 return $html;
297 319 }
@@ -364,14 +386,20 @@
364 386 * Before Product query hook.
365 387 *
366 388 * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::modify_wc_query_args 10
367 389 */
390 +
368 391 do_action( 'rtsb/elements/render/before_query', $settings, $args );
369 -
370 392 // Query.
371 393 $product_query = new WC_Product_Query( $args );
372 - $products = $pagination ? $product_query->get_products()->products : $product_query->get_products();
373 394
395 + /**
396 + * Product query hook.
397 + */
398 + do_action( 'rtsb/elements/render/product_query', $product_query, $this );
399 +
400 + $products = $pagination ? $product_query->get_products()->products : $product_query->get_products();
401 +
374 402 if ( ! empty( $products ) ) {
375 403 // Start rendering.
376 404 $hook_html .= $this->render_start( $metas, $settings, $temp_args );
377 405
@@ -428,17 +456,21 @@
428 456 $this->get_settings = $settings;
429 457 $rand = wp_rand();
430 458 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
431 459
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';
460 + $order = strtoupper( $wp_query->query_vars['order'] ?? 'ASC' );
461 + $settings['rtsb_orderby'] = RenderHelpers::escaping_product_orderby( $wp_query->query_vars['orderby'] ?? 'menu_order' );
462 + $settings['rtsb_order'] = in_array( $order, [ 'ASC','DESC' ], true ) ? $order : 'ASC';
434 463
435 464 $pagination = $metas['pagination'];
436 465
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 -
466 + $page_type = BuilderFns::builder_type( get_the_ID() );
467 + $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
468 + $raw_settings = $widget->get_settings_for_display();
469 + $widget_per_page = ! empty( $raw_settings['posts_per_page'] ) ? absint( $raw_settings['posts_per_page'] ) : 0;
470 + $wc_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
471 + $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.
472 + $restore_query = false;
441 473 if ( $is_preview ) {
442 474 $main_query = clone $wp_query;
443 475 $main_post = clone $post;
444 476
@@ -459,12 +491,30 @@
459 491 $wp_query = new WP_Query( $wp_query_args ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
460 492
461 493 wc_set_loop_prop( 'total', $wp_query->found_posts );
462 494 wc_set_loop_prop( 'total_pages', $wp_query->max_num_pages );
495 + } elseif ( ! is_admin() && ( is_shop() || is_product_taxonomy() ) && absint( $wp_query->get( 'posts_per_page' ) ) !== $posts_per_page ) {
496 + // Apply widget's posts_per_page to the main query on the live frontend.
497 + $main_query = clone $wp_query;
498 + $main_post = clone $post;
499 +
500 + $query_vars = $wp_query->query_vars;
501 + $query_vars['posts_per_page'] = $posts_per_page;
502 +
503 + $wp_query = new WP_Query( $query_vars ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
504 +
505 + wc_set_loop_prop( 'total', $wp_query->found_posts );
506 + wc_set_loop_prop( 'total_pages', $wp_query->max_num_pages );
507 +
508 + $restore_query = true;
463 509 }
464 510
465 511 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>';
512 + $rtsb_active_filters_html = Fns::get_active_filters_html();
513 + $rtsb_wrapper_style = '' !== $rtsb_active_filters_html ? ' style="' . esc_attr( 'display:block;' ) . '"' : '';
514 + echo '<div class="rtsb-active-filters-wrapper"' . $rtsb_wrapper_style . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Style value is escaped above.
515 + Fns::print_html( $rtsb_active_filters_html, true );
516 + echo '</div>';
467 517 }
468 518
469 519 // column_per_row.
470 520 if ( woocommerce_product_loop() ) {
@@ -474,28 +524,44 @@
474 524 $this->row( $this->wc_product_loop( $wp_query, $metas, $template ), $metas, '', $settings );
475 525 }
476 526
477 527 // Pagination.
478 - $this->html .= '<div class="rtsb-archive-pagination-wrap">';
528 + $this->html .= '<div class="rtsb-archive-pagination-wrap" data-per-page="' . absint( $posts_per_page ) . '">';
479 529
480 530 if ( $pagination ) {
481 - if ( Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' ) ) {
531 + if ( Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) ) {
482 532 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 - );
533 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
490 534
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>";
535 + if ( 'pagination_ajax' === $posts_loading_type ) {
536 + $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, true );
537 + } elseif ( 'pagination' === $posts_loading_type ) {
538 + $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, false );
539 + } elseif ( 'load_on_scroll' === $posts_loading_type ) {
540 + $this->html .= "<div class='rtsb-infinite-scroll' data-trigger='1' data-paged='2' data-max-page='{$wp_query->max_num_pages}'>
541 + <div class='rtsb-infinite-action'>
542 + <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
543 + </div>
544 + </div>";
545 + } else {
546 + $load_more_text = ! empty( $metas['load_more_text'] ) ? $metas['load_more_text'] : esc_html__( 'Load More', 'shopbuilder' );
547 +
548 + $this->add_attribute(
549 + 'rtsb_load_more_btn_attr_' . $rand,
550 + [
551 + 'data-paged' => '2',
552 + 'data-max-page' => $wp_query->max_num_pages,
553 + ]
554 + );
555 +
556 + $this->html .=
557 + "<div class='rtsb-load-more rtsb-pos-r'>
558 + <button {$this->get_attribute_string( 'rtsb_load_more_btn_attr_' . $rand )}>
559 + <span>" . esc_html( $load_more_text ) . "</span>
560 + </button>
561 + <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
562 + </div>";
563 + }
498 564 }
499 565 } else {
500 566 $pagination_args = [
501 567 'query' => $wp_query,
@@ -512,9 +578,9 @@
512 578 } else {
513 579 $this->no_products_msg( $metas );
514 580 }
515 581
516 - if ( $is_preview ) {
582 + if ( $is_preview || ! empty( $restore_query ) ) {
517 583 // phpcs:disable
518 584 $wp_query = $main_query;
519 585 $post = $main_post;
520 586
@@ -522,8 +588,11 @@
522 588 wp_reset_postdata();
523 589 // phpcs:enable
524 590 }
525 591
592 + // Ensure the container's data-rtsb-ajax uses the same posts_per_page as the initial render.
593 + $settings['pagination_per_page'] = $posts_per_page;
594 +
526 595 // Container.
527 596 $this->container( $this->html, $settings, 'rtsb-products-container', $template );
528 597
529 598 return $this->html;
@@ -577,8 +646,13 @@
577 646 *
578 647 * @return string
579 648 */
580 649 public function category_view( $template, $settings, $is_single = false ) {
650 + // Reset the shared singleton's html buffer so leftover markup from a
651 + // previously rendered widget (e.g. a products grid that ran earlier
652 + // in the same request) doesn't get folded into the categories
653 + // container by container() below.
654 + $this->html = '';
581 655 $this->get_settings = $settings;
582 656
583 657 $metas = RenderHelpers::meta_dataset( $settings, $template, $this->get_settings_for_display() );
584 658 $taxonomy = $metas['category_source'];
@@ -586,8 +660,11 @@
586 660
587 661 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
588 662 $term = $metas['tag_term'];
589 663 }
664 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
665 + $term = $metas['brand_term'];
666 + }
590 667
591 668 // Query.
592 669 if ( $is_single ) {
593 670 if ( empty( $term ) ) {
@@ -605,9 +682,9 @@
605 682 if ( ! is_wp_error( $taxonomy_query ) && ! empty( $taxonomy_query ) ) {
606 683 // Row.
607 684 $this->row( $this->category_loop( $taxonomy_query, $metas, $template, $is_single ), $metas );
608 685 } else {
609 - $this->html .= '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
686 + $this->html = '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
610 687 }
611 688
612 689 // Container.
613 690 $this->container( $this->html, $settings, 'rtsb-categories-container', $is_single );
@@ -688,8 +765,9 @@
688 765 *
689 766 * @return array
690 767 */
691 768 public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
769 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
692 770 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
693 771 return [];
694 772 }
695 773
@@ -695,9 +773,9 @@
695 773
696 774 global $product;
697 775
698 776 if ( ! $product instanceof WC_Product && $post_id ) {
699 - $product = wc_get_product( $post_id );
777 + $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 778 }
701 779
702 780 $post_id = $product->get_id();
703 781 $arg = $this->build_product_arg(
@@ -788,9 +866,13 @@
788 866 $arg['image_link'] = $meta['image_link'];
789 867 $arg['grid'] .= 'rtsb-col-grid ';
790 868
791 869 if ( ! $fullwidth ) {
792 - $arg['class'] .= ' even-grid-item ';
870 + if ( 'masonry' === $meta['grid_type'] ) {
871 + $arg['class'] .= 'masonry-grid-item ';
872 + } else {
873 + $arg['class'] .= 'even-grid-item ';
874 + }
793 875 }
794 876
795 877 if ( ! empty( $meta['active_cat_slider'] ) ) {
796 878 $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
@@ -864,8 +946,9 @@
864 946 $arg['anchor_class'] = null;
865 947 $arg['hover_img_html'] = null;
866 948 $arg['s_link'] = [];
867 949 $arg['add_to_cart'] = '';
950 + $arg['p_counter'] = absint( $i );
868 951 $arg['p_id'] = $post_id;
869 952 $arg['product'] = $product;
870 953 $arg['p_link'] = $product->get_permalink();
871 954 $arg['p_sale'] = '';
@@ -896,10 +979,15 @@
896 979 $is_list = preg_match( '/list/', $layout );
897 980 $is_carousel = preg_match( '/slider/', $layout );
898 981
899 982 if ( ! $is_carousel ) {
900 - $arg['grid'] .= 'rtsb-col-grid ';
901 - $arg['class'] .= ' even-grid-item ';
983 + $arg['grid'] .= 'rtsb-col-grid ';
984 +
985 + if ( 'masonry' === $meta['grid_type'] ) {
986 + $arg['class'] .= 'masonry-grid-item ';
987 + } else {
988 + $arg['class'] .= 'even-grid-item ';
989 + }
902 990 } else {
903 991 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
904 992 }
905 993
@@ -924,14 +1012,16 @@
924 1012 $arg['p_sale'] = Fns::is_module_active( 'product_badges' ) && $badge_module ? 'rtsb_yes' : Fns::get_promo_badge(
925 1013 $product,
926 1014 $meta['sale_badge_type'],
927 1015 $meta['sale_badge_text'],
928 - $meta['out_of_stock_badge_text']
1016 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
929 1017 );
930 1018 }
931 1019
932 1020 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
933 1021
1022 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
1023 +
934 1024 if ( ! $meta['show_cart_text'] ) {
935 1025 $meta['add_to_cart_text'] = '';
936 1026 $meta['add_to_cart_success_text'] = '';
937 1027 }
@@ -957,12 +1047,28 @@
957 1047 'text' => $meta['add_to_cart_text'],
958 1048 'success' => $meta['add_to_cart_success_text'],
959 1049 'icon_html' => $cart_icon,
960 1050 'alignment' => $meta['add_to_cart_alignment'],
1051 + 'product' => $product,
961 1052 ]
962 1053 );
963 1054 }
964 1055 }
1056 + if ( Fns::is_catalog_mode() && empty( $arg['add_to_cart'] ) ) {
1057 + if ( ! empty( $meta['add_to_cart_icon'] ) ) {
1058 + $cart_icon_html = Fns::icons_manager( $meta['add_to_cart_icon'], 'cart-icon' );
1059 + }
1060 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
1061 + $arg['add_to_cart'] = CatalogModeFns::render_elementor_widget_catalog_mode_button(
1062 + [
1063 + 'id' => $post_id,
1064 + 'type' => $p_type,
1065 + 'text' => $meta['add_to_cart_text'],
1066 + 'icon_html' => $cart_icon,
1067 + 'alignment' => $meta['add_to_cart_alignment'],
1068 + ]
1069 + );
1070 + }
965 1071 }
966 1072
967 1073 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
968 1074 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -1027,9 +1133,8 @@
1027 1133 ];
1028 1134
1029 1135 return $arg;
1030 1136 }
1031 -
1032 1137 /**
1033 1138 * Renders add to cart button.
1034 1139 *
1035 1140 * @param array $args Cart args.
@@ -1043,9 +1148,10 @@
1043 1148 'type' => $type,
1044 1149 'text' => $text,
1045 1150 'success' => $success,
1046 1151 'icon_html' => $icon_html,
1047 - 'alignment' => $alignment
1152 + 'alignment' => $alignment,
1153 + 'product' => $product
1048 1154 ) = $args;
1049 1155
1050 1156 $content = null;
1051 1157 $rand = wp_rand();
@@ -1058,9 +1164,8 @@
1058 1164
1059 1165 $cart_attr = [
1060 1166 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1061 1167 'href' => esc_url( $link ),
1062 - 'rel' => 'nofollow',
1063 1168 'data-quantity' => '1',
1064 1169 'data-product_id' => absint( $id ),
1065 1170 'data-id' => absint( $id ),
1066 1171 ];
@@ -1066,12 +1171,13 @@
1066 1171 ];
1067 1172
1068 1173 if ( empty( $text ) ) {
1069 1174 $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' ),
1175 + 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1176 + 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1177 + 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1178 + 'external' => ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1179 + 'rtsb-gift-card' => esc_attr__( 'Select Gift Card', 'shopbuilder' ),
1074 1180 ];
1075 1181
1076 1182 $cart_attr['title'] = $tooltip_mapping[ $type ];
1077 1183 }
@@ -1076,18 +1182,26 @@
1076 1182 $cart_attr['title'] = $tooltip_mapping[ $type ];
1077 1183 }
1078 1184
1079 1185 $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1186 + if ( 'variable' === $type ) {
1187 + $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
1188 + }
1189 + if ( 'variable' === $type || 'grouped' === $type ) {
1190 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1080 1191
1081 - if ( 'variable' === $type || 'grouped' === $type ) {
1082 1192 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1083 1193
1084 - $text_btn = 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' );
1194 + $text_btn = apply_filters(
1195 + 'rtsb/elementor/render/add_to_cart_text',
1196 + 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' ),
1197 + $product,
1198 + $type
1199 + );
1085 1200
1086 1201 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1087 1202 $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>';
1203 + $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 1204 } elseif ( 'external' === $type ) {
1091 1205 if ( ! empty( $ext_link ) ) {
1092 1206 $cart_attr['target'] = '_blank';
1093 1207 }
@@ -1097,20 +1211,20 @@
1097 1211 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1098 1212
1099 1213 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1100 1214 $content .= $icon_html;
1101 - $content .= '<span class="text">' . ( ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ) ) . '</span>';
1215 + $content .= '<span class="button-text text">' . ( ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ) ) . '</span>';
1102 1216 } else {
1103 1217 $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1104 1218 $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1105 1219 $cart_attr['data-type'] = $type;
1220 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1106 1221
1107 1222 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1108 1223
1109 1224 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1110 1225 $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>';
1226 + $content .= '<span class="button-text text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '">' . esc_html( $text ) . '</span>';
1113 1227 }
1114 1228
1115 1229 $content .= '</a>';
1116 1230 $content .= '</div>';
@@ -1214,8 +1328,17 @@
1214 1328 $total_page,
1215 1329 'wc' === $query_type ? $per_page : $posts_per_page
1216 1330 );
1217 1331 }
1332 + } elseif ( 'pagination_ajax' === $meta['posts_loading_type'] && ! rtsb()->has_pro() ) {
1333 + // Free version fallback: render non-AJAX number pagination so filter
1334 + // params in the URL are preserved via get_pagenum_link().
1335 + if ( $is_grid ) {
1336 + $html_utility .= Fns::custom_pagination(
1337 + $total_page,
1338 + 'wc' === $query_type ? $per_page : $posts_per_page
1339 + );
1340 + }
1218 1341 } else {
1219 1342 ob_start();
1220 1343 do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1221 1344 $html_utility .= ob_get_clean();
@@ -1371,8 +1494,46 @@
1371 1494 return $this;
1372 1495 }
1373 1496
1374 1497 /**
1498 + * Add link render attributes.
1499 + *
1500 + * @param array|string $element The HTML element.
1501 + * @param array $url_control Array of link settings.
1502 + * @param bool $overwrite Optional. Whether to overwrite existing
1503 + * attribute. Default is false, not to overwrite.
1504 + *
1505 + * @return Render
1506 + */
1507 + public function add_link_attributes( $element, array $url_control, $overwrite = false ) {
1508 + $attributes = [];
1509 +
1510 + if ( ! empty( $url_control['url'] ) ) {
1511 + $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] );
1512 +
1513 + $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols );
1514 + }
1515 +
1516 + if ( ! empty( $url_control['is_external'] ) ) {
1517 + $attributes['target'] = '_blank';
1518 + }
1519 +
1520 + if ( ! empty( $url_control['nofollow'] ) ) {
1521 + $attributes['rel'] = 'nofollow';
1522 + }
1523 +
1524 + if ( ! empty( $url_control['custom_attributes'] ) ) {
1525 + $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) );
1526 + }
1527 +
1528 + if ( $attributes ) {
1529 + $this->add_attribute( $element, $attributes, null, $overwrite );
1530 + }
1531 +
1532 + return $this;
1533 + }
1534 +
1535 + /**
1375 1536 * Get render attribute string.
1376 1537 *
1377 1538 * @param string $element The element.
1378 1539 *
@@ -1382,7 +1543,54 @@
1382 1543 if ( empty( $this->attributes[ $element ] ) ) {
1383 1544 return '';
1384 1545 }
1385 1546
1386 - return Utils::render_html_attributes( $this->attributes[ $element ] );
1547 + if ( class_exists( '\Elementor\Utils' ) ) {
1548 + return Utils::render_html_attributes( $this->attributes[ $element ] );
1549 + }
1550 +
1551 + // Fallback.
1552 + return $this->render_fallback_attributes( $this->attributes[ $element ] );
1553 + }
1554 +
1555 + /**
1556 + * Function to render link attributes.
1557 + *
1558 + * @param string $id Element ID.
1559 + * @param array $control_name Control name.
1560 + * @param string $class_name Class name.
1561 + *
1562 + * @return string
1563 + */
1564 + public function render_link_attributes( $id, $control_name, $class_name = 'logo-img-link' ) {
1565 + $this->add_attribute( $id, 'class', $class_name );
1566 +
1567 + if ( ! empty( $control_name['url'] ) ) {
1568 + $this->add_link_attributes( $id, $control_name );
1569 + } else {
1570 + $this->add_attribute( $id, 'role', 'button' );
1571 + }
1572 +
1573 + return $this->get_attribute_string( $id );
1574 + }
1575 +
1576 + /**
1577 + * Render fallback attributes.
1578 + *
1579 + * @param array $attributes The attributes.
1580 + *
1581 + * @return string
1582 + */
1583 + private function render_fallback_attributes( $attributes ) {
1584 + $rendered_attributes = [];
1585 +
1586 + foreach ( $attributes as $attribute_key => $attribute_values ) {
1587 + if ( is_array( $attribute_values ) ) {
1588 + $attribute_values = implode( ' ', $attribute_values );
1589 + }
1590 +
1591 + $rendered_attributes[] = sprintf( '%1$s="%2$s"', $attribute_key, esc_attr( $attribute_values ) );
1592 + }
1593 +
1594 + return implode( ' ', $rendered_attributes );
1387 1595 }
1388 1596 }