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 +212 -44 2.1.153.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,14 +386,20 @@
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 - $products = $pagination ? $product_query->get_products()->products : $product_query->get_products();
377 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 +
378 402 if ( ! empty( $products ) ) {
379 403 // Start rendering.
380 404 $hook_html .= $this->render_start( $metas, $settings, $temp_args );
381 405
@@ -432,17 +456,17 @@
432 456 $this->get_settings = $settings;
433 457 $rand = wp_rand();
434 458 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
435 459
436 - $settings['rtsb_order'] = ! empty( $wp_query->query_vars['order'] ) ? $wp_query->query_vars['order'] : 'ASC';
437 - $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';
438 463
439 464 $pagination = $metas['pagination'];
440 465
441 466 $page_type = BuilderFns::builder_type( get_the_ID() );
442 467 $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
443 - $posts_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
444 -
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.
445 469 if ( $is_preview ) {
446 470 $main_query = clone $wp_query;
447 471 $main_post = clone $post;
448 472
@@ -483,23 +507,39 @@
483 507
484 508 if ( $pagination ) {
485 509 if ( Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) ) {
486 510 if ( $wp_query->max_num_pages > 1 ) {
487 - $this->add_attribute(
488 - 'rtsb_load_more_btn_attr_' . $rand,
489 - [
490 - 'data-paged' => '2',
491 - 'data-max-page' => $wp_query->max_num_pages,
492 - ]
493 - );
511 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
494 512
495 - $this->html .=
496 - "<div class='rtsb-load-more rtsb-pos-r'>
497 - <button {$this->get_attribute_string( 'rtsb_load_more_btn_attr_' . $rand )}>
498 - <span>" . esc_html__( 'Load More', 'shopbuilder' ) . "</span>
499 - </button>
500 - <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
501 - </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 + }
502 542 }
503 543 } else {
504 544 $pagination_args = [
505 545 'query' => $wp_query,
@@ -590,8 +630,11 @@
590 630
591 631 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
592 632 $term = $metas['tag_term'];
593 633 }
634 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
635 + $term = $metas['brand_term'];
636 + }
594 637
595 638 // Query.
596 639 if ( $is_single ) {
597 640 if ( empty( $term ) ) {
@@ -692,8 +735,9 @@
692 735 *
693 736 * @return array
694 737 */
695 738 public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
739 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
696 740 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
697 741 return [];
698 742 }
699 743
@@ -699,9 +743,9 @@
699 743
700 744 global $product;
701 745
702 746 if ( ! $product instanceof WC_Product && $post_id ) {
703 - $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.
704 748 }
705 749
706 750 $post_id = $product->get_id();
707 751 $arg = $this->build_product_arg(
@@ -792,9 +836,13 @@
792 836 $arg['image_link'] = $meta['image_link'];
793 837 $arg['grid'] .= 'rtsb-col-grid ';
794 838
795 839 if ( ! $fullwidth ) {
796 - $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 + }
797 845 }
798 846
799 847 if ( ! empty( $meta['active_cat_slider'] ) ) {
800 848 $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
@@ -901,10 +949,15 @@
901 949 $is_list = preg_match( '/list/', $layout );
902 950 $is_carousel = preg_match( '/slider/', $layout );
903 951
904 952 if ( ! $is_carousel ) {
905 - $arg['grid'] .= 'rtsb-col-grid ';
906 - $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 + }
907 960 } else {
908 961 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
909 962 }
910 963
@@ -969,8 +1022,23 @@
969 1022 ]
970 1023 );
971 1024 }
972 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 + }
973 1041 }
974 1042
975 1043 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
976 1044 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -1035,9 +1103,8 @@
1035 1103 ];
1036 1104
1037 1105 return $arg;
1038 1106 }
1039 -
1040 1107 /**
1041 1108 * Renders add to cart button.
1042 1109 *
1043 1110 * @param array $args Cart args.
@@ -1067,9 +1134,8 @@
1067 1134
1068 1135 $cart_attr = [
1069 1136 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1070 1137 'href' => esc_url( $link ),
1071 - 'rel' => 'nofollow',
1072 1138 'data-quantity' => '1',
1073 1139 'data-product_id' => absint( $id ),
1074 1140 'data-id' => absint( $id ),
1075 1141 ];
@@ -1075,12 +1141,13 @@
1075 1141 ];
1076 1142
1077 1143 if ( empty( $text ) ) {
1078 1144 $tooltip_mapping = [
1079 - 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1080 - 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1081 - 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1082 - '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' ),
1083 1150 ];
1084 1151
1085 1152 $cart_attr['title'] = $tooltip_mapping[ $type ];
1086 1153 }
@@ -1085,15 +1152,22 @@
1085 1152 $cart_attr['title'] = $tooltip_mapping[ $type ];
1086 1153 }
1087 1154
1088 1155 $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1089 -
1156 + if ( 'variable' === $type ) {
1157 + $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
1158 + }
1090 1159 if ( 'variable' === $type || 'grouped' === $type ) {
1091 1160 $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1092 1161
1093 1162 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1094 1163
1095 - $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 + );
1096 1170
1097 1171 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1098 1172 $content .= $icon_html;
1099 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>';
@@ -1226,8 +1300,17 @@
1226 1300 $total_page,
1227 1301 'wc' === $query_type ? $per_page : $posts_per_page
1228 1302 );
1229 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 + }
1230 1313 } else {
1231 1314 ob_start();
1232 1315 do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1233 1316 $html_utility .= ob_get_clean();
@@ -1383,8 +1466,46 @@
1383 1466 return $this;
1384 1467 }
1385 1468
1386 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 + /**
1387 1508 * Get render attribute string.
1388 1509 *
1389 1510 * @param string $element The element.
1390 1511 *
@@ -1394,7 +1515,54 @@
1394 1515 if ( empty( $this->attributes[ $element ] ) ) {
1395 1516 return '';
1396 1517 }
1397 1518
1398 - 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 );
1399 1567 }
1400 1568 }