PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.4
ShopBuilder – WooCommerce Builder For Elementor v3.2.4
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 +194 -39 2.1.73.2.4 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,9 +505,9 @@
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 511 $this->add_attribute(
484 512 'rtsb_load_more_btn_attr_' . $rand,
485 513 [
@@ -489,9 +517,9 @@
489 517 );
490 518
491 519 $this->html .=
492 520 "<div class='rtsb-load-more rtsb-pos-r'>
493 - <button '{$this->get_attribute_string( 'rtsb_load_more_btn_attr_' . $rand )}'>
521 + <button {$this->get_attribute_string( 'rtsb_load_more_btn_attr_' . $rand )}>
494 522 <span>" . esc_html__( 'Load More', 'shopbuilder' ) . "</span>
495 523 </button>
496 524 <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
497 525 </div>";
@@ -586,8 +614,11 @@
586 614
587 615 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
588 616 $term = $metas['tag_term'];
589 617 }
618 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
619 + $term = $metas['brand_term'];
620 + }
590 621
591 622 // Query.
592 623 if ( $is_single ) {
593 624 if ( empty( $term ) ) {
@@ -688,8 +719,9 @@
688 719 *
689 720 * @return array
690 721 */
691 722 public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
723 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
692 724 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
693 725 return [];
694 726 }
695 727
@@ -695,9 +727,9 @@
695 727
696 728 global $product;
697 729
698 730 if ( ! $product instanceof WC_Product && $post_id ) {
699 - $product = wc_get_product( $post_id );
731 + $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 732 }
701 733
702 734 $post_id = $product->get_id();
703 735 $arg = $this->build_product_arg(
@@ -788,9 +820,13 @@
788 820 $arg['image_link'] = $meta['image_link'];
789 821 $arg['grid'] .= 'rtsb-col-grid ';
790 822
791 823 if ( ! $fullwidth ) {
792 - $arg['class'] .= ' even-grid-item ';
824 + if ( 'masonry' === $meta['grid_type'] ) {
825 + $arg['class'] .= 'masonry-grid-item ';
826 + } else {
827 + $arg['class'] .= 'even-grid-item ';
828 + }
793 829 }
794 830
795 831 if ( ! empty( $meta['active_cat_slider'] ) ) {
796 832 $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
@@ -864,8 +900,9 @@
864 900 $arg['anchor_class'] = null;
865 901 $arg['hover_img_html'] = null;
866 902 $arg['s_link'] = [];
867 903 $arg['add_to_cart'] = '';
904 + $arg['p_counter'] = absint( $i );
868 905 $arg['p_id'] = $post_id;
869 906 $arg['product'] = $product;
870 907 $arg['p_link'] = $product->get_permalink();
871 908 $arg['p_sale'] = '';
@@ -896,10 +933,15 @@
896 933 $is_list = preg_match( '/list/', $layout );
897 934 $is_carousel = preg_match( '/slider/', $layout );
898 935
899 936 if ( ! $is_carousel ) {
900 - $arg['grid'] .= 'rtsb-col-grid ';
901 - $arg['class'] .= ' even-grid-item ';
937 + $arg['grid'] .= 'rtsb-col-grid ';
938 +
939 + if ( 'masonry' === $meta['grid_type'] ) {
940 + $arg['class'] .= 'masonry-grid-item ';
941 + } else {
942 + $arg['class'] .= 'even-grid-item ';
943 + }
902 944 } else {
903 945 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
904 946 }
905 947
@@ -924,14 +966,16 @@
924 966 $arg['p_sale'] = Fns::is_module_active( 'product_badges' ) && $badge_module ? 'rtsb_yes' : Fns::get_promo_badge(
925 967 $product,
926 968 $meta['sale_badge_type'],
927 969 $meta['sale_badge_text'],
928 - $meta['out_of_stock_badge_text']
970 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
929 971 );
930 972 }
931 973
932 974 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
933 975
976 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
977 +
934 978 if ( ! $meta['show_cart_text'] ) {
935 979 $meta['add_to_cart_text'] = '';
936 980 $meta['add_to_cart_success_text'] = '';
937 981 }
@@ -957,12 +1001,28 @@
957 1001 'text' => $meta['add_to_cart_text'],
958 1002 'success' => $meta['add_to_cart_success_text'],
959 1003 'icon_html' => $cart_icon,
960 1004 'alignment' => $meta['add_to_cart_alignment'],
1005 + 'product' => $product,
961 1006 ]
962 1007 );
963 1008 }
964 1009 }
1010 + if ( Fns::is_catalog_mode() && empty( $arg['add_to_cart'] ) ) {
1011 + if ( ! empty( $meta['add_to_cart_icon'] ) ) {
1012 + $cart_icon_html = Fns::icons_manager( $meta['add_to_cart_icon'], 'cart-icon' );
1013 + }
1014 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
1015 + $arg['add_to_cart'] = CatalogModeFns::render_elementor_widget_catalog_mode_button(
1016 + [
1017 + 'id' => $post_id,
1018 + 'type' => $p_type,
1019 + 'text' => $meta['add_to_cart_text'],
1020 + 'icon_html' => $cart_icon,
1021 + 'alignment' => $meta['add_to_cart_alignment'],
1022 + ]
1023 + );
1024 + }
965 1025 }
966 1026
967 1027 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
968 1028 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -1027,9 +1087,8 @@
1027 1087 ];
1028 1088
1029 1089 return $arg;
1030 1090 }
1031 -
1032 1091 /**
1033 1092 * Renders add to cart button.
1034 1093 *
1035 1094 * @param array $args Cart args.
@@ -1043,9 +1102,10 @@
1043 1102 'type' => $type,
1044 1103 'text' => $text,
1045 1104 'success' => $success,
1046 1105 'icon_html' => $icon_html,
1047 - 'alignment' => $alignment
1106 + 'alignment' => $alignment,
1107 + 'product' => $product
1048 1108 ) = $args;
1049 1109
1050 1110 $content = null;
1051 1111 $rand = wp_rand();
@@ -1058,9 +1118,8 @@
1058 1118
1059 1119 $cart_attr = [
1060 1120 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1061 1121 'href' => esc_url( $link ),
1062 - 'rel' => 'nofollow',
1063 1122 'data-quantity' => '1',
1064 1123 'data-product_id' => absint( $id ),
1065 1124 'data-id' => absint( $id ),
1066 1125 ];
@@ -1066,12 +1125,13 @@
1066 1125 ];
1067 1126
1068 1127 if ( empty( $text ) ) {
1069 1128 $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' ),
1129 + 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1130 + 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1131 + 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1132 + 'external' => ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1133 + 'rtsb-gift-card' => esc_attr__( 'Select Gift Card', 'shopbuilder' ),
1074 1134 ];
1075 1135
1076 1136 $cart_attr['title'] = $tooltip_mapping[ $type ];
1077 1137 }
@@ -1076,13 +1136,22 @@
1076 1136 $cart_attr['title'] = $tooltip_mapping[ $type ];
1077 1137 }
1078 1138
1079 1139 $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1140 + if ( 'variable' === $type ) {
1141 + $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
1142 + }
1143 + if ( 'variable' === $type || 'grouped' === $type ) {
1144 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1080 1145
1081 - if ( 'variable' === $type || 'grouped' === $type ) {
1082 1146 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1083 1147
1084 - $text_btn = 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' );
1148 + $text_btn = apply_filters(
1149 + 'rtsb/elementor/render/add_to_cart_text',
1150 + 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' ),
1151 + $product,
1152 + $type
1153 + );
1085 1154
1086 1155 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1087 1156 $content .= $icon_html;
1088 1157 $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 +1171,9 @@
1102 1171 } else {
1103 1172 $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1104 1173 $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1105 1174 $cart_attr['data-type'] = $type;
1175 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1106 1176
1107 1177 $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1108 1178
1109 1179 $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
@@ -1371,8 +1441,46 @@
1371 1441 return $this;
1372 1442 }
1373 1443
1374 1444 /**
1445 + * Add link render attributes.
1446 + *
1447 + * @param array|string $element The HTML element.
1448 + * @param array $url_control Array of link settings.
1449 + * @param bool $overwrite Optional. Whether to overwrite existing
1450 + * attribute. Default is false, not to overwrite.
1451 + *
1452 + * @return Render
1453 + */
1454 + public function add_link_attributes( $element, array $url_control, $overwrite = false ) {
1455 + $attributes = [];
1456 +
1457 + if ( ! empty( $url_control['url'] ) ) {
1458 + $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] );
1459 +
1460 + $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols );
1461 + }
1462 +
1463 + if ( ! empty( $url_control['is_external'] ) ) {
1464 + $attributes['target'] = '_blank';
1465 + }
1466 +
1467 + if ( ! empty( $url_control['nofollow'] ) ) {
1468 + $attributes['rel'] = 'nofollow';
1469 + }
1470 +
1471 + if ( ! empty( $url_control['custom_attributes'] ) ) {
1472 + $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) );
1473 + }
1474 +
1475 + if ( $attributes ) {
1476 + $this->add_attribute( $element, $attributes, null, $overwrite );
1477 + }
1478 +
1479 + return $this;
1480 + }
1481 +
1482 + /**
1375 1483 * Get render attribute string.
1376 1484 *
1377 1485 * @param string $element The element.
1378 1486 *
@@ -1382,7 +1490,54 @@
1382 1490 if ( empty( $this->attributes[ $element ] ) ) {
1383 1491 return '';
1384 1492 }
1385 1493
1386 - return Utils::render_html_attributes( $this->attributes[ $element ] );
1494 + if ( class_exists( '\Elementor\Utils' ) ) {
1495 + return Utils::render_html_attributes( $this->attributes[ $element ] );
1496 + }
1497 +
1498 + // Fallback.
1499 + return $this->render_fallback_attributes( $this->attributes[ $element ] );
1500 + }
1501 +
1502 + /**
1503 + * Function to render link attributes.
1504 + *
1505 + * @param string $id Element ID.
1506 + * @param array $control_name Control name.
1507 + * @param string $class_name Class name.
1508 + *
1509 + * @return string
1510 + */
1511 + public function render_link_attributes( $id, $control_name, $class_name = 'logo-img-link' ) {
1512 + $this->add_attribute( $id, 'class', $class_name );
1513 +
1514 + if ( ! empty( $control_name['url'] ) ) {
1515 + $this->add_link_attributes( $id, $control_name );
1516 + } else {
1517 + $this->add_attribute( $id, 'role', 'button' );
1518 + }
1519 +
1520 + return $this->get_attribute_string( $id );
1521 + }
1522 +
1523 + /**
1524 + * Render fallback attributes.
1525 + *
1526 + * @param array $attributes The attributes.
1527 + *
1528 + * @return string
1529 + */
1530 + private function render_fallback_attributes( $attributes ) {
1531 + $rendered_attributes = [];
1532 +
1533 + foreach ( $attributes as $attribute_key => $attribute_values ) {
1534 + if ( is_array( $attribute_values ) ) {
1535 + $attribute_values = implode( ' ', $attribute_values );
1536 + }
1537 +
1538 + $rendered_attributes[] = sprintf( '%1$s="%2$s"', $attribute_key, esc_attr( $attribute_values ) );
1539 + }
1540 +
1541 + return implode( ' ', $rendered_attributes );
1387 1542 }
1388 1543 }