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 +251 -55 2.2.03.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;
@@ -93,8 +94,9 @@
93 94 $view_mode = isset( $_GET['displayview'] ) ? sanitize_text_field( wp_unslash( $_GET['displayview'] ) ) : $view_mode; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
94 95 $tooltip_attr = 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ? esc_attr( $metas['tooltip_position_list'] ) : $metas['tooltip_position'];
95 96 $has_pro = rtsb()->has_pro();
96 97 $has_filters = $has_pro && ! empty( $settings['tax_filter'] );
98 + $grid_style = $settings['grid_style'] ?? 'even';
97 99
98 100 // Set default layout.
99 101 if ( ! $has_pro && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
100 102 $layout = RenderHelpers::set_default_layout( $layout );
@@ -99,8 +101,13 @@
99 101 if ( ! $has_pro && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
100 102 $layout = RenderHelpers::set_default_layout( $layout );
101 103 }
102 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 +
103 110 // Container classes.
104 111 $container_classes = RenderHelpers::prepare_container_classes( $metas, $settings, $class );
105 112
106 113 // Pagination attributes.
@@ -118,8 +125,14 @@
118 125 if ( ! empty( $pagination_attributes ) ) {
119 126 $container_attributes['data-rtsb-ajax'] = $pagination_attributes;
120 127 }
121 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 +
122 135 // Adding render attributes.
123 136 $this->add_attribute( 'rtsb_container_attr_' . $rand, $container_attributes );
124 137
125 138 // Start rendering.
@@ -141,8 +154,9 @@
141 154 $script_generator = [];
142 155 $script_generator['layout'] = $layout_id;
143 156 $script_generator['rand'] = absint( $rand );
144 157 $script_generator['isCarousel'] = preg_match( '/slider/', $layout );
158 + $script_generator['isMasonry'] = $has_pro && 'masonry' === $grid_style;
145 159
146 160 // Register scripts in the wp_footer action.
147 161 add_action(
148 162 'wp_footer',
@@ -188,9 +202,8 @@
188 202 $this->html = ! empty( $settings['show_filter'] ) ? $hook_html . $start_wrapper : $start_wrapper;
189 203
190 204 if ( $is_carousel ) {
191 205 $slider_data = RenderHelpers::slider_data( $settings, $raw_settings );
192 -
193 206 // Adding slider render attributes.
194 207 $this->add_attribute(
195 208 'rtsb_slider_attr_' . $rand,
196 209 [
@@ -240,13 +253,21 @@
240 253 }
241 254
242 255 $i = 0;
243 256
244 - 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 ) {
245 268 $i++;
246 - $GLOBALS['product'] = $product;
247 - $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
248 -
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.
249 270 /**
250 271 * Before product template render hook.
251 272 */
252 273 do_action( 'rtsb_before_product_template_render' );
@@ -251,9 +272,9 @@
251 272 */
252 273 do_action( 'rtsb_before_product_template_render' );
253 274
254 275 // Loop arg.
255 - $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
276 + $arg = $this->arg_dataset( $settings, $_product, $settings['lazy_load'], $i );
256 277
257 278 // Get template.
258 279 $html .= Fns::load_template( $template . $layout, $arg, true );
259 280
@@ -263,9 +284,11 @@
263 284 do_action( 'rtsb_after_product_template_render' );
264 285
265 286 unset( $GLOBALS['product'] );
266 287 }
267 -
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 + }
268 291 return $html;
269 292 }
270 293
271 294 /**
@@ -283,19 +306,20 @@
283 306 if ( empty( $query ) ) {
284 307 return null;
285 308 }
286 309
287 - $i = 0;
310 + $i = 0;
311 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
288 312
289 313 while ( $query->have_posts() ) {
290 314 $query->the_post();
291 315 $i++;
292 -
316 + global $product;
293 317 // Loop arg.
294 - $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 );
295 319
296 320 // Get template.
297 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
321 + $html .= Fns::load_template( $template . $layout, $arg, true );
298 322 }
299 323
300 324 return $html;
301 325 }
@@ -368,10 +392,10 @@
368 392 * Before Product query hook.
369 393 *
370 394 * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::modify_wc_query_args 10
371 395 */
396 +
372 397 do_action( 'rtsb/elements/render/before_query', $settings, $args );
373 -
374 398 // Query.
375 399 $product_query = new WC_Product_Query( $args );
376 400
377 401 /**
@@ -438,17 +462,21 @@
438 462 $this->get_settings = $settings;
439 463 $rand = wp_rand();
440 464 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
441 465
442 - $settings['rtsb_order'] = ! empty( $wp_query->query_vars['order'] ) ? $wp_query->query_vars['order'] : 'ASC';
443 - $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';
444 469
445 470 $pagination = $metas['pagination'];
446 471
447 - $page_type = BuilderFns::builder_type( get_the_ID() );
448 - $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
449 - $posts_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
450 -
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;
451 479 if ( $is_preview ) {
452 480 $main_query = clone $wp_query;
453 481 $main_post = clone $post;
454 482
@@ -469,12 +497,30 @@
469 497 $wp_query = new WP_Query( $wp_query_args ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
470 498
471 499 wc_set_loop_prop( 'total', $wp_query->found_posts );
472 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;
473 515 }
474 516
475 - if ( Fns::product_has_applied_filters( 'shop' ) || Fns::product_has_applied_filters( 'archive' ) || 'rtsb_builder' === get_post_type( get_the_ID() ) ) {
476 - 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>';
477 523 }
478 524
479 525 // column_per_row.
480 526 if ( woocommerce_product_loop() ) {
@@ -484,28 +530,44 @@
484 530 $this->row( $this->wc_product_loop( $wp_query, $metas, $template ), $metas, '', $settings );
485 531 }
486 532
487 533 // Pagination.
488 - $this->html .= '<div class="rtsb-archive-pagination-wrap">';
534 + $this->html .= '<div class="rtsb-archive-pagination-wrap" data-per-page="' . absint( $posts_per_page ) . '">';
489 535
490 536 if ( $pagination ) {
491 537 if ( Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) ) {
492 538 if ( $wp_query->max_num_pages > 1 ) {
493 - $this->add_attribute(
494 - 'rtsb_load_more_btn_attr_' . $rand,
495 - [
496 - 'data-paged' => '2',
497 - 'data-max-page' => $wp_query->max_num_pages,
498 - ]
499 - );
539 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
500 540
501 - $this->html .=
502 - "<div class='rtsb-load-more rtsb-pos-r'>
503 - <button {$this->get_attribute_string( 'rtsb_load_more_btn_attr_' . $rand )}>
504 - <span>" . esc_html__( 'Load More', 'shopbuilder' ) . "</span>
505 - </button>
506 - <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
507 - </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 + }
508 570 }
509 571 } else {
510 572 $pagination_args = [
511 573 'query' => $wp_query,
@@ -522,9 +584,9 @@
522 584 } else {
523 585 $this->no_products_msg( $metas );
524 586 }
525 587
526 - if ( $is_preview ) {
588 + if ( $is_preview || ! empty( $restore_query ) ) {
527 589 // phpcs:disable
528 590 $wp_query = $main_query;
529 591 $post = $main_post;
530 592
@@ -532,8 +594,11 @@
532 594 wp_reset_postdata();
533 595 // phpcs:enable
534 596 }
535 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 +
536 601 // Container.
537 602 $this->container( $this->html, $settings, 'rtsb-products-container', $template );
538 603
539 604 return $this->html;
@@ -587,8 +652,13 @@
587 652 *
588 653 * @return string
589 654 */
590 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 = '';
591 661 $this->get_settings = $settings;
592 662
593 663 $metas = RenderHelpers::meta_dataset( $settings, $template, $this->get_settings_for_display() );
594 664 $taxonomy = $metas['category_source'];
@@ -596,8 +666,11 @@
596 666
597 667 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
598 668 $term = $metas['tag_term'];
599 669 }
670 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
671 + $term = $metas['brand_term'];
672 + }
600 673
601 674 // Query.
602 675 if ( $is_single ) {
603 676 if ( empty( $term ) ) {
@@ -615,9 +688,9 @@
615 688 if ( ! is_wp_error( $taxonomy_query ) && ! empty( $taxonomy_query ) ) {
616 689 // Row.
617 690 $this->row( $this->category_loop( $taxonomy_query, $metas, $template, $is_single ), $metas );
618 691 } else {
619 - $this->html .= '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
692 + $this->html = '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
620 693 }
621 694
622 695 // Container.
623 696 $this->container( $this->html, $settings, 'rtsb-categories-container', $is_single );
@@ -698,8 +771,9 @@
698 771 *
699 772 * @return array
700 773 */
701 774 public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
775 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
702 776 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
703 777 return [];
704 778 }
705 779
@@ -705,9 +779,9 @@
705 779
706 780 global $product;
707 781
708 782 if ( ! $product instanceof WC_Product && $post_id ) {
709 - $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.
710 784 }
711 785
712 786 $post_id = $product->get_id();
713 787 $arg = $this->build_product_arg(
@@ -798,9 +872,13 @@
798 872 $arg['image_link'] = $meta['image_link'];
799 873 $arg['grid'] .= 'rtsb-col-grid ';
800 874
801 875 if ( ! $fullwidth ) {
802 - $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 + }
803 881 }
804 882
805 883 if ( ! empty( $meta['active_cat_slider'] ) ) {
806 884 $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
@@ -907,10 +985,15 @@
907 985 $is_list = preg_match( '/list/', $layout );
908 986 $is_carousel = preg_match( '/slider/', $layout );
909 987
910 988 if ( ! $is_carousel ) {
911 - $arg['grid'] .= 'rtsb-col-grid ';
912 - $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 + }
913 996 } else {
914 997 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
915 998 }
916 999
@@ -975,8 +1058,23 @@
975 1058 ]
976 1059 );
977 1060 }
978 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 + }
979 1077 }
980 1078
981 1079 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
982 1080 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -1041,9 +1139,8 @@
1041 1139 ];
1042 1140
1043 1141 return $arg;
1044 1142 }
1045 -
1046 1143 /**
1047 1144 * Renders add to cart button.
1048 1145 *
1049 1146 * @param array $args Cart args.
@@ -1073,9 +1170,8 @@
1073 1170
1074 1171 $cart_attr = [
1075 1172 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1076 1173 'href' => esc_url( $link ),
1077 - 'rel' => 'nofollow',
1078 1174 'data-quantity' => '1',
1079 1175 'data-product_id' => absint( $id ),
1080 1176 'data-id' => absint( $id ),
1081 1177 ];
@@ -1081,12 +1177,13 @@
1081 1177 ];
1082 1178
1083 1179 if ( empty( $text ) ) {
1084 1180 $tooltip_mapping = [
1085 - 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1086 - 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1087 - 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1088 - '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' ),
1089 1186 ];
1090 1187
1091 1188 $cart_attr['title'] = $tooltip_mapping[ $type ];
1092 1189 }
@@ -1091,20 +1188,26 @@
1091 1188 $cart_attr['title'] = $tooltip_mapping[ $type ];
1092 1189 }
1093 1190
1094 1191 $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1095 -
1192 + if ( 'variable' === $type ) {
1193 + $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
1194 + }
1096 1195 if ( 'variable' === $type || 'grouped' === $type ) {
1097 1196 $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1098 1197
1099 1198 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1100 1199
1101 - $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 + );
1102 1206
1103 1207 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1104 1208 $content .= $icon_html;
1105 - $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>';
1106 - $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>';
1107 1210 } elseif ( 'external' === $type ) {
1108 1211 if ( ! empty( $ext_link ) ) {
1109 1212 $cart_attr['target'] = '_blank';
1110 1213 }
@@ -1114,9 +1217,9 @@
1114 1217 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1115 1218
1116 1219 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1117 1220 $content .= $icon_html;
1118 - $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>';
1119 1222 } else {
1120 1223 $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1121 1224 $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1122 1225 $cart_attr['data-type'] = $type;
@@ -1125,10 +1228,9 @@
1125 1228 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1126 1229
1127 1230 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1128 1231 $content .= $icon_html;
1129 - $content .= '<span class="text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '">' . esc_html( $text ) . '</span>';
1130 - $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>';
1131 1233 }
1132 1234
1133 1235 $content .= '</a>';
1134 1236 $content .= '</div>';
@@ -1232,8 +1334,17 @@
1232 1334 $total_page,
1233 1335 'wc' === $query_type ? $per_page : $posts_per_page
1234 1336 );
1235 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 + }
1236 1347 } else {
1237 1348 ob_start();
1238 1349 do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1239 1350 $html_utility .= ob_get_clean();
@@ -1389,8 +1500,46 @@
1389 1500 return $this;
1390 1501 }
1391 1502
1392 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 + /**
1393 1542 * Get render attribute string.
1394 1543 *
1395 1544 * @param string $element The element.
1396 1545 *
@@ -1400,7 +1549,54 @@
1400 1549 if ( empty( $this->attributes[ $element ] ) ) {
1401 1550 return '';
1402 1551 }
1403 1552
1404 - 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 );
1405 1601 }
1406 1602 }