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 +205 -43 2.2.03.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;
@@ -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.
@@ -141,8 +148,9 @@
141 148 $script_generator = [];
142 149 $script_generator['layout'] = $layout_id;
143 150 $script_generator['rand'] = absint( $rand );
144 151 $script_generator['isCarousel'] = preg_match( '/slider/', $layout );
152 + $script_generator['isMasonry'] = $has_pro && 'masonry' === $grid_style;
145 153
146 154 // Register scripts in the wp_footer action.
147 155 add_action(
148 156 'wp_footer',
@@ -188,9 +196,8 @@
188 196 $this->html = ! empty( $settings['show_filter'] ) ? $hook_html . $start_wrapper : $start_wrapper;
189 197
190 198 if ( $is_carousel ) {
191 199 $slider_data = RenderHelpers::slider_data( $settings, $raw_settings );
192 -
193 200 // Adding slider render attributes.
194 201 $this->add_attribute(
195 202 'rtsb_slider_attr_' . $rand,
196 203 [
@@ -240,13 +247,21 @@
240 247 }
241 248
242 249 $i = 0;
243 250
244 - 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 ) {
245 262 $i++;
246 - $GLOBALS['product'] = $product;
247 - $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
248 -
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.
249 264 /**
250 265 * Before product template render hook.
251 266 */
252 267 do_action( 'rtsb_before_product_template_render' );
@@ -251,9 +266,9 @@
251 266 */
252 267 do_action( 'rtsb_before_product_template_render' );
253 268
254 269 // Loop arg.
255 - $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
270 + $arg = $this->arg_dataset( $settings, $_product, $settings['lazy_load'], $i );
256 271
257 272 // Get template.
258 273 $html .= Fns::load_template( $template . $layout, $arg, true );
259 274
@@ -263,9 +278,11 @@
263 278 do_action( 'rtsb_after_product_template_render' );
264 279
265 280 unset( $GLOBALS['product'] );
266 281 }
267 -
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 + }
268 285 return $html;
269 286 }
270 287
271 288 /**
@@ -283,19 +300,20 @@
283 300 if ( empty( $query ) ) {
284 301 return null;
285 302 }
286 303
287 - $i = 0;
304 + $i = 0;
305 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
288 306
289 307 while ( $query->have_posts() ) {
290 308 $query->the_post();
291 309 $i++;
292 -
310 + global $product;
293 311 // Loop arg.
294 - $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 );
295 313
296 314 // Get template.
297 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
315 + $html .= Fns::load_template( $template . $layout, $arg, true );
298 316 }
299 317
300 318 return $html;
301 319 }
@@ -368,10 +386,10 @@
368 386 * Before Product query hook.
369 387 *
370 388 * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::modify_wc_query_args 10
371 389 */
390 +
372 391 do_action( 'rtsb/elements/render/before_query', $settings, $args );
373 -
374 392 // Query.
375 393 $product_query = new WC_Product_Query( $args );
376 394
377 395 /**
@@ -438,17 +456,17 @@
438 456 $this->get_settings = $settings;
439 457 $rand = wp_rand();
440 458 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
441 459
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';
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';
444 463
445 464 $pagination = $metas['pagination'];
446 465
447 466 $page_type = BuilderFns::builder_type( get_the_ID() );
448 467 $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 -
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.
451 469 if ( $is_preview ) {
452 470 $main_query = clone $wp_query;
453 471 $main_post = clone $post;
454 472
@@ -489,23 +507,39 @@
489 507
490 508 if ( $pagination ) {
491 509 if ( Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) ) {
492 510 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 - );
511 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
500 512
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>";
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 + }
508 542 }
509 543 } else {
510 544 $pagination_args = [
511 545 'query' => $wp_query,
@@ -596,8 +630,11 @@
596 630
597 631 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
598 632 $term = $metas['tag_term'];
599 633 }
634 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
635 + $term = $metas['brand_term'];
636 + }
600 637
601 638 // Query.
602 639 if ( $is_single ) {
603 640 if ( empty( $term ) ) {
@@ -698,8 +735,9 @@
698 735 *
699 736 * @return array
700 737 */
701 738 public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
739 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
702 740 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
703 741 return [];
704 742 }
705 743
@@ -705,9 +743,9 @@
705 743
706 744 global $product;
707 745
708 746 if ( ! $product instanceof WC_Product && $post_id ) {
709 - $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.
710 748 }
711 749
712 750 $post_id = $product->get_id();
713 751 $arg = $this->build_product_arg(
@@ -798,9 +836,13 @@
798 836 $arg['image_link'] = $meta['image_link'];
799 837 $arg['grid'] .= 'rtsb-col-grid ';
800 838
801 839 if ( ! $fullwidth ) {
802 - $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 + }
803 845 }
804 846
805 847 if ( ! empty( $meta['active_cat_slider'] ) ) {
806 848 $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
@@ -907,10 +949,15 @@
907 949 $is_list = preg_match( '/list/', $layout );
908 950 $is_carousel = preg_match( '/slider/', $layout );
909 951
910 952 if ( ! $is_carousel ) {
911 - $arg['grid'] .= 'rtsb-col-grid ';
912 - $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 + }
913 960 } else {
914 961 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
915 962 }
916 963
@@ -975,8 +1022,23 @@
975 1022 ]
976 1023 );
977 1024 }
978 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 + }
979 1041 }
980 1042
981 1043 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
982 1044 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -1041,9 +1103,8 @@
1041 1103 ];
1042 1104
1043 1105 return $arg;
1044 1106 }
1045 -
1046 1107 /**
1047 1108 * Renders add to cart button.
1048 1109 *
1049 1110 * @param array $args Cart args.
@@ -1073,9 +1134,8 @@
1073 1134
1074 1135 $cart_attr = [
1075 1136 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1076 1137 'href' => esc_url( $link ),
1077 - 'rel' => 'nofollow',
1078 1138 'data-quantity' => '1',
1079 1139 'data-product_id' => absint( $id ),
1080 1140 'data-id' => absint( $id ),
1081 1141 ];
@@ -1081,12 +1141,13 @@
1081 1141 ];
1082 1142
1083 1143 if ( empty( $text ) ) {
1084 1144 $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' ),
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' ),
1089 1150 ];
1090 1151
1091 1152 $cart_attr['title'] = $tooltip_mapping[ $type ];
1092 1153 }
@@ -1091,15 +1152,22 @@
1091 1152 $cart_attr['title'] = $tooltip_mapping[ $type ];
1092 1153 }
1093 1154
1094 1155 $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1095 -
1156 + if ( 'variable' === $type ) {
1157 + $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
1158 + }
1096 1159 if ( 'variable' === $type || 'grouped' === $type ) {
1097 1160 $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1098 1161
1099 1162 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1100 1163
1101 - $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 + );
1102 1170
1103 1171 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1104 1172 $content .= $icon_html;
1105 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>';
@@ -1232,8 +1300,17 @@
1232 1300 $total_page,
1233 1301 'wc' === $query_type ? $per_page : $posts_per_page
1234 1302 );
1235 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 + }
1236 1313 } else {
1237 1314 ob_start();
1238 1315 do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1239 1316 $html_utility .= ob_get_clean();
@@ -1389,8 +1466,46 @@
1389 1466 return $this;
1390 1467 }
1391 1468
1392 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 + /**
1393 1508 * Get render attribute string.
1394 1509 *
1395 1510 * @param string $element The element.
1396 1511 *
@@ -1400,7 +1515,54 @@
1400 1515 if ( empty( $this->attributes[ $element ] ) ) {
1401 1516 return '';
1402 1517 }
1403 1518
1404 - 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 );
1405 1567 }
1406 1568 }