PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.6
ShopBuilder – WooCommerce Builder For Elementor v3.2.6
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 +232 -52 2.1.83.2.6 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,17 @@
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 466 $page_type = BuilderFns::builder_type( get_the_ID() );
438 467 $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 -
468 + $posts_per_page = apply_filters( 'loop_shop_per_page', wc_get_default_products_per_row() * wc_get_default_product_rows_per_page() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- woocommerce default hooks used.
441 469 if ( $is_preview ) {
442 470 $main_query = clone $wp_query;
443 471 $main_post = clone $post;
444 472
@@ -477,25 +505,41 @@
477 505 // Pagination.
478 506 $this->html .= '<div class="rtsb-archive-pagination-wrap">';
479 507
480 508 if ( $pagination ) {
481 - if ( Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' ) ) {
509 + if ( Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) ) {
482 510 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 - );
511 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
490 512
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>";
513 + if ( 'pagination_ajax' === $posts_loading_type ) {
514 + $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, true );
515 + } elseif ( 'pagination' === $posts_loading_type ) {
516 + $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, false );
517 + } elseif ( 'load_on_scroll' === $posts_loading_type ) {
518 + $this->html .= "<div class='rtsb-infinite-scroll' data-trigger='1' data-paged='2' data-max-page='{$wp_query->max_num_pages}'>
519 + <div class='rtsb-infinite-action'>
520 + <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
521 + </div>
522 + </div>";
523 + } else {
524 + $load_more_text = ! empty( $metas['load_more_text'] ) ? $metas['load_more_text'] : esc_html__( 'Load More', 'shopbuilder' );
525 +
526 + $this->add_attribute(
527 + 'rtsb_load_more_btn_attr_' . $rand,
528 + [
529 + 'data-paged' => '2',
530 + 'data-max-page' => $wp_query->max_num_pages,
531 + ]
532 + );
533 +
534 + $this->html .=
535 + "<div class='rtsb-load-more rtsb-pos-r'>
536 + <button {$this->get_attribute_string( 'rtsb_load_more_btn_attr_' . $rand )}>
537 + <span>" . esc_html( $load_more_text ) . "</span>
538 + </button>
539 + <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
540 + </div>";
541 + }
498 542 }
499 543 } else {
500 544 $pagination_args = [
501 545 'query' => $wp_query,
@@ -586,8 +630,11 @@
586 630
587 631 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
588 632 $term = $metas['tag_term'];
589 633 }
634 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
635 + $term = $metas['brand_term'];
636 + }
590 637
591 638 // Query.
592 639 if ( $is_single ) {
593 640 if ( empty( $term ) ) {
@@ -688,8 +735,9 @@
688 735 *
689 736 * @return array
690 737 */
691 738 public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
739 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
692 740 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
693 741 return [];
694 742 }
695 743
@@ -695,9 +743,9 @@
695 743
696 744 global $product;
697 745
698 746 if ( ! $product instanceof WC_Product && $post_id ) {
699 - $product = wc_get_product( $post_id );
747 + $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 748 }
701 749
702 750 $post_id = $product->get_id();
703 751 $arg = $this->build_product_arg(
@@ -788,9 +836,13 @@
788 836 $arg['image_link'] = $meta['image_link'];
789 837 $arg['grid'] .= 'rtsb-col-grid ';
790 838
791 839 if ( ! $fullwidth ) {
792 - $arg['class'] .= ' even-grid-item ';
840 + if ( 'masonry' === $meta['grid_type'] ) {
841 + $arg['class'] .= 'masonry-grid-item ';
842 + } else {
843 + $arg['class'] .= 'even-grid-item ';
844 + }
793 845 }
794 846
795 847 if ( ! empty( $meta['active_cat_slider'] ) ) {
796 848 $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
@@ -864,8 +916,9 @@
864 916 $arg['anchor_class'] = null;
865 917 $arg['hover_img_html'] = null;
866 918 $arg['s_link'] = [];
867 919 $arg['add_to_cart'] = '';
920 + $arg['p_counter'] = absint( $i );
868 921 $arg['p_id'] = $post_id;
869 922 $arg['product'] = $product;
870 923 $arg['p_link'] = $product->get_permalink();
871 924 $arg['p_sale'] = '';
@@ -896,10 +949,15 @@
896 949 $is_list = preg_match( '/list/', $layout );
897 950 $is_carousel = preg_match( '/slider/', $layout );
898 951
899 952 if ( ! $is_carousel ) {
900 - $arg['grid'] .= 'rtsb-col-grid ';
901 - $arg['class'] .= ' even-grid-item ';
953 + $arg['grid'] .= 'rtsb-col-grid ';
954 +
955 + if ( 'masonry' === $meta['grid_type'] ) {
956 + $arg['class'] .= 'masonry-grid-item ';
957 + } else {
958 + $arg['class'] .= 'even-grid-item ';
959 + }
902 960 } else {
903 961 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
904 962 }
905 963
@@ -924,14 +982,16 @@
924 982 $arg['p_sale'] = Fns::is_module_active( 'product_badges' ) && $badge_module ? 'rtsb_yes' : Fns::get_promo_badge(
925 983 $product,
926 984 $meta['sale_badge_type'],
927 985 $meta['sale_badge_text'],
928 - $meta['out_of_stock_badge_text']
986 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
929 987 );
930 988 }
931 989
932 990 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
933 991
992 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
993 +
934 994 if ( ! $meta['show_cart_text'] ) {
935 995 $meta['add_to_cart_text'] = '';
936 996 $meta['add_to_cart_success_text'] = '';
937 997 }
@@ -957,12 +1017,28 @@
957 1017 'text' => $meta['add_to_cart_text'],
958 1018 'success' => $meta['add_to_cart_success_text'],
959 1019 'icon_html' => $cart_icon,
960 1020 'alignment' => $meta['add_to_cart_alignment'],
1021 + 'product' => $product,
961 1022 ]
962 1023 );
963 1024 }
964 1025 }
1026 + if ( Fns::is_catalog_mode() && empty( $arg['add_to_cart'] ) ) {
1027 + if ( ! empty( $meta['add_to_cart_icon'] ) ) {
1028 + $cart_icon_html = Fns::icons_manager( $meta['add_to_cart_icon'], 'cart-icon' );
1029 + }
1030 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
1031 + $arg['add_to_cart'] = CatalogModeFns::render_elementor_widget_catalog_mode_button(
1032 + [
1033 + 'id' => $post_id,
1034 + 'type' => $p_type,
1035 + 'text' => $meta['add_to_cart_text'],
1036 + 'icon_html' => $cart_icon,
1037 + 'alignment' => $meta['add_to_cart_alignment'],
1038 + ]
1039 + );
1040 + }
965 1041 }
966 1042
967 1043 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
968 1044 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -1027,9 +1103,8 @@
1027 1103 ];
1028 1104
1029 1105 return $arg;
1030 1106 }
1031 -
1032 1107 /**
1033 1108 * Renders add to cart button.
1034 1109 *
1035 1110 * @param array $args Cart args.
@@ -1043,9 +1118,10 @@
1043 1118 'type' => $type,
1044 1119 'text' => $text,
1045 1120 'success' => $success,
1046 1121 'icon_html' => $icon_html,
1047 - 'alignment' => $alignment
1122 + 'alignment' => $alignment,
1123 + 'product' => $product
1048 1124 ) = $args;
1049 1125
1050 1126 $content = null;
1051 1127 $rand = wp_rand();
@@ -1058,9 +1134,8 @@
1058 1134
1059 1135 $cart_attr = [
1060 1136 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1061 1137 'href' => esc_url( $link ),
1062 - 'rel' => 'nofollow',
1063 1138 'data-quantity' => '1',
1064 1139 'data-product_id' => absint( $id ),
1065 1140 'data-id' => absint( $id ),
1066 1141 ];
@@ -1066,12 +1141,13 @@
1066 1141 ];
1067 1142
1068 1143 if ( empty( $text ) ) {
1069 1144 $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' ),
1145 + 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1146 + 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1147 + 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1148 + 'external' => ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1149 + 'rtsb-gift-card' => esc_attr__( 'Select Gift Card', 'shopbuilder' ),
1074 1150 ];
1075 1151
1076 1152 $cart_attr['title'] = $tooltip_mapping[ $type ];
1077 1153 }
@@ -1076,13 +1152,22 @@
1076 1152 $cart_attr['title'] = $tooltip_mapping[ $type ];
1077 1153 }
1078 1154
1079 1155 $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1156 + if ( 'variable' === $type ) {
1157 + $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
1158 + }
1159 + if ( 'variable' === $type || 'grouped' === $type ) {
1160 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1080 1161
1081 - if ( 'variable' === $type || 'grouped' === $type ) {
1082 1162 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1083 1163
1084 - $text_btn = 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' );
1164 + $text_btn = apply_filters(
1165 + 'rtsb/elementor/render/add_to_cart_text',
1166 + 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' ),
1167 + $product,
1168 + $type
1169 + );
1085 1170
1086 1171 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1087 1172 $content .= $icon_html;
1088 1173 $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>';
@@ -1102,8 +1187,9 @@
1102 1187 } else {
1103 1188 $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1104 1189 $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1105 1190 $cart_attr['data-type'] = $type;
1191 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1106 1192
1107 1193 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1108 1194
1109 1195 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
@@ -1214,8 +1300,17 @@
1214 1300 $total_page,
1215 1301 'wc' === $query_type ? $per_page : $posts_per_page
1216 1302 );
1217 1303 }
1304 + } elseif ( 'pagination_ajax' === $meta['posts_loading_type'] && ! rtsb()->has_pro() ) {
1305 + // Free version fallback: render non-AJAX number pagination so filter
1306 + // params in the URL are preserved via get_pagenum_link().
1307 + if ( $is_grid ) {
1308 + $html_utility .= Fns::custom_pagination(
1309 + $total_page,
1310 + 'wc' === $query_type ? $per_page : $posts_per_page
1311 + );
1312 + }
1218 1313 } else {
1219 1314 ob_start();
1220 1315 do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1221 1316 $html_utility .= ob_get_clean();
@@ -1371,8 +1466,46 @@
1371 1466 return $this;
1372 1467 }
1373 1468
1374 1469 /**
1470 + * Add link render attributes.
1471 + *
1472 + * @param array|string $element The HTML element.
1473 + * @param array $url_control Array of link settings.
1474 + * @param bool $overwrite Optional. Whether to overwrite existing
1475 + * attribute. Default is false, not to overwrite.
1476 + *
1477 + * @return Render
1478 + */
1479 + public function add_link_attributes( $element, array $url_control, $overwrite = false ) {
1480 + $attributes = [];
1481 +
1482 + if ( ! empty( $url_control['url'] ) ) {
1483 + $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] );
1484 +
1485 + $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols );
1486 + }
1487 +
1488 + if ( ! empty( $url_control['is_external'] ) ) {
1489 + $attributes['target'] = '_blank';
1490 + }
1491 +
1492 + if ( ! empty( $url_control['nofollow'] ) ) {
1493 + $attributes['rel'] = 'nofollow';
1494 + }
1495 +
1496 + if ( ! empty( $url_control['custom_attributes'] ) ) {
1497 + $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) );
1498 + }
1499 +
1500 + if ( $attributes ) {
1501 + $this->add_attribute( $element, $attributes, null, $overwrite );
1502 + }
1503 +
1504 + return $this;
1505 + }
1506 +
1507 + /**
1375 1508 * Get render attribute string.
1376 1509 *
1377 1510 * @param string $element The element.
1378 1511 *
@@ -1382,7 +1515,54 @@
1382 1515 if ( empty( $this->attributes[ $element ] ) ) {
1383 1516 return '';
1384 1517 }
1385 1518
1386 - return Utils::render_html_attributes( $this->attributes[ $element ] );
1519 + if ( class_exists( '\Elementor\Utils' ) ) {
1520 + return Utils::render_html_attributes( $this->attributes[ $element ] );
1521 + }
1522 +
1523 + // Fallback.
1524 + return $this->render_fallback_attributes( $this->attributes[ $element ] );
1525 + }
1526 +
1527 + /**
1528 + * Function to render link attributes.
1529 + *
1530 + * @param string $id Element ID.
1531 + * @param array $control_name Control name.
1532 + * @param string $class_name Class name.
1533 + *
1534 + * @return string
1535 + */
1536 + public function render_link_attributes( $id, $control_name, $class_name = 'logo-img-link' ) {
1537 + $this->add_attribute( $id, 'class', $class_name );
1538 +
1539 + if ( ! empty( $control_name['url'] ) ) {
1540 + $this->add_link_attributes( $id, $control_name );
1541 + } else {
1542 + $this->add_attribute( $id, 'role', 'button' );
1543 + }
1544 +
1545 + return $this->get_attribute_string( $id );
1546 + }
1547 +
1548 + /**
1549 + * Render fallback attributes.
1550 + *
1551 + * @param array $attributes The attributes.
1552 + *
1553 + * @return string
1554 + */
1555 + private function render_fallback_attributes( $attributes ) {
1556 + $rendered_attributes = [];
1557 +
1558 + foreach ( $attributes as $attribute_key => $attribute_values ) {
1559 + if ( is_array( $attribute_values ) ) {
1560 + $attribute_values = implode( ' ', $attribute_values );
1561 + }
1562 +
1563 + $rendered_attributes[] = sprintf( '%1$s="%2$s"', $attribute_key, esc_attr( $attribute_values ) );
1564 + }
1565 +
1566 + return implode( ' ', $rendered_attributes );
1387 1567 }
1388 1568 }