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 +895 -406 2.1.23.2.6 View file →
@@ -1,9 +1,9 @@
1 1 <?php
2 2 /**
3 3 * Elementor Render Class.
4 4 *
5 - * This class contains render logics & output.
5 + * This class contains render logics & output. It utilizes WC_Products_Query().
6 6 *
7 7 * @package RadiusTheme\SB
8 8 */
9 9
@@ -8,10 +8,14 @@
8 8 */
9 9
10 10 namespace RadiusTheme\SB\Elementor\Render;
11 11
12 +use RadiusTheme\SBPRO\Modules\CatalogMode\CatalogModeFns;
13 +use WC_Query;
12 14 use WP_Query;
13 15 use WC_Product;
16 +use Elementor\Utils;
17 +use WC_Product_Query;
14 18 use RadiusTheme\SB\Helpers\Fns;
15 19 use RadiusTheme\SB\Models\QueryArgs;
16 20 use RadiusTheme\SB\Helpers\BuilderFns;
17 21 use RadiusTheme\SB\Traits\SingletonTrait;
@@ -51,8 +55,15 @@
51 55 */
52 56 public $get_settings;
53 57
54 58 /**
59 + * Element attributes.
60 + *
61 + * @var array
62 + */
63 + private $attributes = [];
64 +
65 + /**
55 66 * Get elementor settings.
56 67 *
57 68 * @return array
58 69 */
@@ -67,140 +78,81 @@
67 78 * @param array $settings Settings.
68 79 * @param string $class Class.
69 80 * @param string $template Template name.
70 81 * @param bool $fullwidth Fullwidth check.
71 - * @param bool $ajax_attr Ajax attributes.
82 + * @param bool $ajax Ajax attributes.
72 83 *
73 84 * @return string
74 85 */
75 - public function container( $content, $settings, $class = '', $template = '', $fullwidth = false, $ajax_attr = true ) {
76 - $layout_id = 'rtsb-container-' . absint( wp_rand() );
77 - $container_attr = null;
78 - $cart_txt_class = '';
79 - $container_class = 'rtsb-elementor-container products rtsb-pos-r ' . esc_attr( $class );
80 - $metas = RenderHelpers::meta_dataset( $settings, $template );
86 + public function container( $content, $settings, $class = '', $template = '', $fullwidth = false, $ajax = true ) {
87 + $rand = wp_rand();
88 + $layout_id = 'rtsb-container-' . $rand;
89 + $metas = RenderHelpers::meta_dataset( $settings, $template );
90 + $raw_layout = esc_html( $metas['layout'] );
91 + $layout = RenderHelpers::filter_layout( $raw_layout, $template );
92 + $style_attr = '--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) );
93 + $view_mode = $settings['view_mode'] ?? 'grid';
94 + $view_mode = isset( $_GET['displayview'] ) ? sanitize_text_field( wp_unslash( $_GET['displayview'] ) ) : $view_mode; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
95 + $tooltip_attr = 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ? esc_attr( $metas['tooltip_position_list'] ) : $metas['tooltip_position'];
96 + $has_pro = rtsb()->has_pro();
97 + $has_filters = $has_pro && ! empty( $settings['tax_filter'] );
98 + $grid_style = $settings['grid_style'] ?? 'even';
81 99
82 - $layout = $metas['layout'];
83 - $animation = $metas['hover_animation'];
84 - $animation .= ' gallery-hover-' . ( ! empty( $settings['gallery_hover_animation'] ) ? $settings['gallery_hover_animation'] : 'fade' );
85 -
86 - $is_cat = preg_match( '/category/', $layout );
87 - $is_carousel = preg_match( '/slider/', $layout );
88 -
89 - if ( ! $is_cat ) {
90 - $cart_txt_class = $metas['show_cart_text'] ? ' has-cart-text' : ' no-cart-text';
91 - }
92 -
93 - if ( $is_carousel ) {
94 - $fullwidth = true;
95 - }
96 -
97 - $badge_class = [
98 - 'position' => $metas['badge_position'],
99 - 'alignment' => $metas['badge_alignment'],
100 - ];
101 -
102 - if ( ! rtsb()->has_pro() && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
100 + // Set default layout.
101 + if ( ! $has_pro && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
103 102 $layout = RenderHelpers::set_default_layout( $layout );
104 103 }
105 104
106 - $has_tab_filters = rtsb()->has_pro() && ! empty( $settings['tax_filter'] );
107 -
108 - if ( $is_carousel ) {
109 - $pagination_attr = $has_tab_filters ? RenderHelpers::pagination_data( $settings, $template ) : '';
110 - } elseif ( $is_cat ) {
111 - $pagination_attr = '';
112 - } else {
113 - $ajax_pagination = ! empty( $settings['show_pagination'] ) && 'pagination' !== $settings['pagination_type'];
114 - $page = Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' );
115 - $condition = rtsb()->has_pro() && $ajax_attr && ( $ajax_pagination || $has_tab_filters || $page );
116 - $pagination_attr = $condition ? RenderHelpers::pagination_data( $settings, $template ) : '';
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 ) );
117 108 }
118 109
119 - if ( ! in_array( 'clear-btn', $metas['visibility'], true ) ) {
120 - $container_class .= ' no-clear-btn';
121 - }
110 + // Container classes.
111 + $container_classes = RenderHelpers::prepare_container_classes( $metas, $settings, $class );
122 112
123 - $container_class .= ! empty( $metas['pagination'] ) ? ' rtsb-has-pagination' : ' rtsb-no-pagination';
124 - $style_attr = ' style="--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) ) . '"';
125 - $tooltip_attr = ' data-tooltip-position="' . esc_attr( $metas['tooltip_position'] ) . '"';
113 + // Pagination attributes.
114 + $pagination_attributes = RenderHelpers::prepare_pagination_attr( $layout, $has_filters, $template, $settings, $ajax );
126 115
127 - $view_mode = $settings['view_mode'] ?? 'grid';
116 + // Attributes for the container.
117 + $container_attributes = [
118 + 'id' => $layout_id,
119 + 'class' => $container_classes,
120 + 'style' => apply_filters( 'rtsb/product/container/attr', $style_attr, $settings ),
121 + 'data-layout' => $layout,
122 + 'data-tooltip-position' => $tooltip_attr,
123 + ];
128 124
129 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended
130 - if ( isset( $_GET['displayview'] ) ) {
131 - $view_mode = sanitize_text_field( wp_unslash( $_GET['displayview'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
125 + if ( ! empty( $pagination_attributes ) ) {
126 + $container_attributes['data-rtsb-ajax'] = $pagination_attributes;
132 127 }
133 128
134 - if ( 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ) {
135 - $tooltip_attr = ' data-tooltip-position="' . esc_attr( $metas['tooltip_position_list'] ) . '"';
136 - }
129 + // Adding render attributes.
130 + $this->add_attribute( 'rtsb_container_attr_' . $rand, $container_attributes );
137 131
138 - $container_attr .= ' data-layout="' . esc_attr( $layout ) . '"' . $style_attr . $tooltip_attr . $pagination_attr;
139 - $container_class .= ' badge-' . esc_attr( $badge_class['position'] ) . ' badge-' . esc_attr( $badge_class['alignment'] ) . esc_attr( $cart_txt_class ) . ' img-hover-' . esc_attr( $animation ) . ' excerpt-' . esc_attr( $metas['cat_excerpt_position'] );
132 + // Start rendering.
133 + $this->html = '<div ' . $this->get_attribute_string( 'rtsb_container_attr_' . $rand ) . '>';
140 134
141 - if ( $metas['show_overlay'] ) {
142 - $container_class .= ' has-overlay';
143 - }
135 + // Section title.
136 + $this->html .= $this->render_section_title( $settings );
144 137
145 - if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
146 - $container_class .= ' show-single-cat';
147 - }
148 -
149 - if ( ! empty( $settings['show_compare_text'] ) ) {
150 - $container_class .= ' has-compare-text';
151 - }
152 -
153 - if ( ! empty( $settings['show_quick_view_text'] ) ) {
154 - $container_class .= ' has-quick-view-text';
155 - }
156 -
157 - if ( ! empty( $settings['show_wishlist_text'] ) ) {
158 - $container_class .= ' has-wishlist-text';
159 - }
160 -
161 - if ( empty( $settings['show_compare_icon'] ) ) {
162 - $container_class .= ' no-compare-icon';
163 - }
164 -
165 - if ( empty( $settings['show_quick_view_icon'] ) ) {
166 - $container_class .= ' no-quick-view-icon';
167 - }
168 -
169 - if ( empty( $settings['show_wishlist_icon'] ) ) {
170 - $container_class .= ' no-wishlist-icon';
171 - }
172 -
173 - if ( empty( $settings['show_quick_checkout_icon'] ) ) {
174 - $container_class .= ' no-quick_checkout-icon';
175 - }
176 - if ( $is_cat && empty( $settings['show_overlay'] ) ) {
177 - $container_class .= ' no-category-bg-overlay';
178 - }
179 -
180 - $container_class = apply_filters( 'rtsb/product/container/class', $container_class, $settings );
181 - $this->html = '<div class="' . esc_attr( $container_class ) . '" id="' . esc_attr( $layout_id ) . '" ' . $container_attr . '>';
182 -
183 - if ( ! empty( $settings['query_products'] ) && count( $settings['query_products'] ) && 'yes' === $settings['show_section_title'] ) {
184 - $this->html .= '<div class="rtsb-section-title-wrapper">';
185 - $this->html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' class="rtsb-section-title" >';
186 - $this->html .= esc_html( $settings['section_title_text'] );
187 - $this->html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
188 - $this->html .= '</div>';
189 - }
190 -
138 + // Content.
191 139 $this->html .= $content;
192 140
141 + // End rendering.
193 142 $this->html .= '</div><!-- .rtsb-elementor-container -->';
194 143
195 144 // Action button icon reset.
196 145 $this->action_button_icon_set_default();
197 146
147 + // Script generator.
198 148 $script_generator = [];
199 149 $script_generator['layout'] = $layout_id;
200 - $script_generator['rand'] = absint( wp_rand() );
201 - $script_generator['isCarousel'] = $is_carousel;
150 + $script_generator['rand'] = absint( $rand );
151 + $script_generator['isCarousel'] = preg_match( '/slider/', $layout );
152 + $script_generator['isMasonry'] = $has_pro && 'masonry' === $grid_style;
202 153
154 + // Register scripts in the wp_footer action.
203 155 add_action(
204 156 'wp_footer',
205 157 static function () use ( $script_generator ) {
206 158 RenderHelpers::register_scripts( $script_generator );
@@ -220,39 +172,42 @@
220 172 *
221 173 * @return string
222 174 */
223 175 public function row( $content, $settings, $hook_html = '', $raw_settings = [] ) {
224 - $masonry_class = null;
225 - $loader_class = ' rtsb-pre-loader';
176 + $raw_layout = esc_html( $settings['layout'] );
177 + $layout = RenderHelpers::filter_layout( $raw_layout );
226 178
227 - $layout = $settings['layout'];
228 - $grid_type = $settings['grid_type'];
229 - $is_carousel = preg_match( '/slider/', $layout );
179 + $rand = wp_rand();
180 + $is_carousel = preg_match( '/slider/', $raw_layout );
230 181
231 - if ( 'even' === $grid_type ) {
232 - $masonry_class = ' rtsb-even';
233 - } elseif ( 'masonry' === $grid_type ) {
234 - $masonry_class = ' rtsb-masonry';
182 + if ( preg_match( '/category/', $raw_layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
183 + $is_carousel = true;
235 184 }
236 185
237 - if ( ! rtsb()->has_pro() || $is_carousel ) {
238 - $masonry_class = ' rtsb-even';
239 - }
186 + // Row classes.
187 + $row_classes = RenderHelpers::prepare_row_classes( $settings );
240 188
241 - $row_start_class = apply_filters( 'rtsb/custom/layout/row/classes', esc_attr( 'rtsb-row rtsb-content-loader element-loading rtsb-' . $layout . $masonry_class . $loader_class ), $settings );
242 - $row_wrapper = '<div data-title="' . esc_html__( 'Loading ...', 'shopbuilder' ) . '" class="' . $row_start_class . '">';
243 - $start_wrapper = apply_filters( 'rtsb/custom/layout/row/start', $row_wrapper, $row_start_class );
244 - if ( ! empty( $settings['show_filter'] ) ) {
245 - $this->html = $hook_html;
246 - $this->html .= $start_wrapper;
247 - } else {
248 - $this->html = $start_wrapper;
249 - }
189 + // Adding render attributes.
190 + $this->add_attribute( 'rtsb_row_attr_' . $rand, 'class', $row_classes );
250 191
192 + // Start rendering.
193 + $row_wrapper = '<div ' . $this->get_attribute_string( 'rtsb_row_attr_' . $rand ) . '>';
194 + $start_wrapper = apply_filters( 'rtsb/elementor/row/start', $row_wrapper, $raw_settings );
195 +
196 + $this->html = ! empty( $settings['show_filter'] ) ? $hook_html . $start_wrapper : $start_wrapper;
197 +
251 198 if ( $is_carousel ) {
252 199 $slider_data = RenderHelpers::slider_data( $settings, $raw_settings );
200 + // Adding slider render attributes.
201 + $this->add_attribute(
202 + 'rtsb_slider_attr_' . $rand,
203 + [
204 + 'class' => 'rtsb-col-grid ' . $slider_data['class'],
205 + 'data-options' => $slider_data['data'],
206 + ]
207 + );
253 208
254 - $this->html .= '<div class="rtsb-col-grid ' . esc_attr( $slider_data['class'] ) . '" ' . $slider_data['data'] . '>';
209 + $this->html .= '<div ' . $this->get_attribute_string( 'rtsb_slider_attr_' . $rand ) . '>';
255 210 $this->html .= '<div class="swiper-wrapper">';
256 211 }
257 212
258 213 $this->html .= $content;
@@ -259,32 +214,18 @@
259 214
260 215 if ( $is_carousel ) {
261 216 $this->html .= '</div><!-- .swiper-wrapper -->';
262 217
263 - $left_icon = $settings['left_arrow_icon'];
264 - $right_icon = $settings['right_arrow_icon'];
265 -
266 - if ( ! empty( $settings['slider_nav'] ) ) {
267 - $this->html .=
268 - '<div class="swiper-nav">
269 - <div class="swiper-arrow swiper-button-next">
270 - ' . Fns::icons_manager( $right_icon ) . '
271 - </div>
272 - <div class="swiper-arrow swiper-button-prev">
273 - ' . Fns::icons_manager( $left_icon ) . '
274 - </div>
275 - </div>';
276 - }
277 -
278 - $this->html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
279 -
218 + // Slider buttons.
219 + $this->html .= $this->render_slider_buttons( $settings );
280 220 $this->html .= '</div><!-- .rtsb-carousel-slider -->';
281 221 }
282 222
283 223 $row_end = '</div><!-- .rtsb-row -->';
284 224
285 - $this->html .= apply_filters( 'rtsb/custom/layout/row/end', $row_end );
225 + $this->html .= apply_filters( 'rtsb/elementor/row/end', $row_end );
286 226
227 + // Preloader.
287 228 $this->html .= RenderHelpers::pre_loader( $layout );
288 229
289 230 return $this->html;
290 231 }
@@ -291,8 +232,63 @@
291 232
292 233 /**
293 234 * Product loop.
294 235 *
236 + * @param object $products Products.
237 + * @param array $settings Settings.
238 + * @param string $template Template.
239 + *
240 + * @return string|null
241 + */
242 + public function product_loop( $products, $settings, $template ) {
243 + $html = null;
244 +
245 + if ( empty( $products ) ) {
246 + return null;
247 + }
248 +
249 + $i = 0;
250 +
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 ) {
262 + $i++;
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.
264 + /**
265 + * Before product template render hook.
266 + */
267 + do_action( 'rtsb_before_product_template_render' );
268 +
269 + // Loop arg.
270 + $arg = $this->arg_dataset( $settings, $_product, $settings['lazy_load'], $i );
271 +
272 + // Get template.
273 + $html .= Fns::load_template( $template . $layout, $arg, true );
274 +
275 + /**
276 + * After product template render hook.
277 + */
278 + do_action( 'rtsb_after_product_template_render' );
279 +
280 + unset( $GLOBALS['product'] );
281 + }
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 + }
285 + return $html;
286 + }
287 +
288 + /**
289 + * WC Product loop.
290 + *
295 291 * @param object $query The query.
296 292 * @param array $settings Settings.
297 293 * @param string $template Template.
298 294 *
@@ -297,9 +293,9 @@
297 293 * @param string $template Template.
298 294 *
299 295 * @return string|null
300 296 */
301 - public function product_loop( $query, $settings, $template ) {
297 + public function wc_product_loop( $query, $settings, $template ) {
302 298 $html = null;
303 299
304 300 if ( empty( $query ) ) {
305 301 return null;
@@ -304,19 +300,20 @@
304 300 if ( empty( $query ) ) {
305 301 return null;
306 302 }
307 303
308 - $i = 0;
304 + $i = 0;
305 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
309 306
310 307 while ( $query->have_posts() ) {
311 308 $query->the_post();
312 309 $i++;
313 -
310 + global $product;
314 311 // Loop arg.
315 - $arg = $this->arg_dataset( $settings, get_the_ID(), $settings['lazy_load'], $i );
312 + $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
316 313
317 314 // Get template.
318 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
315 + $html .= Fns::load_template( $template . $layout, $arg, true );
319 316 }
320 317
321 318 return $html;
322 319 }
@@ -321,9 +318,9 @@
321 318 return $html;
322 319 }
323 320
324 321 /**
325 - * Product loop.
322 + * Category loop.
326 323 *
327 324 * @param object $query The query.
328 325 * @param array $settings Settings.
329 326 * @param string $template Template.
@@ -333,11 +330,13 @@
333 330 */
334 331 public function category_loop( $query, $settings, $template, $is_single = false ) {
335 332 $html = null;
336 333 $category_loop = false;
334 +
337 335 if ( 'category-layout3' === $settings['raw_settings']['layout'] || 'category-single-layout2' === $settings['raw_settings']['layout'] ) {
338 336 $category_loop = true;
339 337 }
338 +
340 339 if ( $is_single ) {
341 340 // Loop arg.
342 341 $arg = $this->cat_arg_dataset( $settings, $query, true, false, $category_loop );
343 342
@@ -374,42 +373,56 @@
374 373
375 374 // Query Args.
376 375 $args = ( new QueryArgs() )->buildArgs( $metas, 'product', $is_carousel );
377 376
378 - $temp_args = $args;
379 - $args = apply_filters( 'rtsb/elementor/args_before_loop', $args, $metas );
377 + $temp_args = $args;
378 + $default_args = [
379 + 'visibility' => 'visible',
380 + 'status' => 'publish',
381 + ];
382 + $custom_args = apply_filters( 'rtsb/elementor/args_before_loop', $args, $metas );
383 + $args = wp_parse_args( $custom_args, $default_args );
380 384
385 + /**
386 + * Before Product query hook.
387 + *
388 + * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::modify_wc_query_args 10
389 + */
390 +
391 + do_action( 'rtsb/elements/render/before_query', $settings, $args );
381 392 // Query.
382 - $product_query = new WP_Query( $args );
393 + $product_query = new WC_Product_Query( $args );
383 394
384 - if ( $product_query->have_posts() ) {
385 - // Elementor Render hook before row.
386 - ob_start();
387 - do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $product_query, $temp_args );
388 - $hook_html .= ob_get_clean();
395 + /**
396 + * Product query hook.
397 + */
398 + do_action( 'rtsb/elements/render/product_query', $product_query, $this );
389 399
400 + $products = $pagination ? $product_query->get_products()->products : $product_query->get_products();
401 +
402 + if ( ! empty( $products ) ) {
403 + // Start rendering.
404 + $hook_html .= $this->render_start( $metas, $settings, $temp_args );
405 +
390 406 // Row.
391 - $this->row( $this->product_loop( $product_query, $metas, $template ), $metas, $hook_html, $settings );
407 + $this->row( $this->product_loop( $products, $metas, $template ), $metas, $hook_html, $settings );
392 408
393 409 // Pagination.
394 410 if ( $pagination ) {
395 - $this->html .= RenderHelpers::render_pagination(
396 - $product_query,
397 - $metas,
398 - $settings['posts_limit'],
399 - $settings['pagination_per_page']
400 - );
411 + $pagination_args = [
412 + 'query' => $product_query,
413 + 'meta' => $metas,
414 + 'limit' => $settings['posts_limit'],
415 + 'per_page' => $settings['pagination_per_page'],
416 + ];
417 +
418 + $this->html .= $this->render_products_pagination( $pagination_args );
401 419 }
402 420
403 - // Elementor Render hook before row.
404 - ob_start();
405 - do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $product_query );
406 - $this->html .= ob_get_clean();
407 -
421 + // End rendering.
422 + $this->html .= $this->render_end( $metas, $settings, $products );
408 423 } else {
409 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div></div>';
410 -
411 - $this->row( $content, $metas );
424 + $this->no_products_msg( $metas );
412 425 }
413 426
414 427 wp_reset_postdata();
415 428
@@ -414,8 +427,15 @@
414 427 wp_reset_postdata();
415 428
416 429 unset( $args['tax_query'] );
417 430
431 + /**
432 + * After Product query hook.
433 + *
434 + * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::reset_wc_query_args 10
435 + */
436 + do_action( 'rtsb/elements/render/after_query', $settings, $args );
437 +
418 438 // Container.
419 439 $this->container( $this->html, $settings, 'rtsb-products-container', $template, $is_carousel );
420 440
421 441 return $this->html;
@@ -420,9 +440,8 @@
420 440
421 441 return $this->html;
422 442 }
423 443
424 -
425 444 /**
426 445 * Render Product View.
427 446 *
428 447 * @param string $template Template name.
@@ -434,24 +453,25 @@
434 453 public function wc_loop_for_product_view( $template, $settings, $widget ) {
435 454 global $wp_query, $post;
436 455
437 456 $this->get_settings = $settings;
457 + $rand = wp_rand();
438 458 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
439 459
440 - $settings['rtsb_order'] = ! empty( $wp_query->query_vars['order'] ) ? $wp_query->query_vars['order'] : 'ASC';
441 - $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';
442 463
443 464 $pagination = $metas['pagination'];
444 465
445 466 $page_type = BuilderFns::builder_type( get_the_ID() );
446 467 $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
447 - $posts_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
448 -
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.
449 469 if ( $is_preview ) {
450 470 $main_query = clone $wp_query;
451 471 $main_post = clone $post;
452 472
453 - $ordering = ( new \WC_Query() )->get_catalog_ordering_args();
473 + $ordering = ( new WC_Query() )->get_catalog_ordering_args();
454 474 $wp_query_args = [
455 475 'post_type' => 'product',
456 476 'posts_per_page' => $posts_per_page,
457 477 ];
@@ -464,21 +484,23 @@
464 484 $wp_query_args['order'] = $ordering['order'];
465 485 }
466 486
467 487 $wp_query = new WP_Query( $wp_query_args ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
488 +
468 489 wc_set_loop_prop( 'total', $wp_query->found_posts );
469 490 wc_set_loop_prop( 'total_pages', $wp_query->max_num_pages );
470 491 }
471 492
493 + if ( Fns::product_has_applied_filters( 'shop' ) || Fns::product_has_applied_filters( 'archive' ) || 'rtsb_builder' === get_post_type( get_the_ID() ) ) {
494 + echo '<div class="rtsb-active-filters-wrapper"></div>';
495 + }
496 +
472 497 // column_per_row.
473 498 if ( woocommerce_product_loop() ) {
474 499 if ( wc_get_loop_prop( 'total' ) ) {
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>';
477 - }
478 500
479 501 // Row.
480 - $this->row( $this->product_loop( $wp_query, $metas, $template ), $metas );
502 + $this->row( $this->wc_product_loop( $wp_query, $metas, $template ), $metas, '', $settings );
481 503 }
482 504
483 505 // Pagination.
484 506 $this->html .= '<div class="rtsb-archive-pagination-wrap">';
@@ -483,40 +505,67 @@
483 505 // Pagination.
484 506 $this->html .= '<div class="rtsb-archive-pagination-wrap">';
485 507
486 508 if ( $pagination ) {
487 - 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', '' ) ) ) {
488 510 if ( $wp_query->max_num_pages > 1 ) {
489 - $this->html .= "
490 - <div class='rtsb-load-more rtsb-pos-r'>
491 - <button data-paged='2' data-max-page='{$wp_query->max_num_pages}'><span>" . esc_html__( 'Load More', 'shopbuilder' ) . "</span></button>
492 - <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
493 - </div>
494 - ";
511 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
512 +
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 + }
495 542 }
496 543 } else {
497 - $this->html .= RenderHelpers::render_pagination(
498 - $wp_query,
499 - $metas,
500 - wc_get_loop_prop( 'total' ),
501 - $posts_per_page
502 - );
544 + $pagination_args = [
545 + 'query' => $wp_query,
546 + 'meta' => $metas,
547 + 'limit' => wc_get_loop_prop( 'total' ),
548 + 'per_page' => $posts_per_page,
549 + ];
550 +
551 + $this->html .= $this->render_products_pagination( $pagination_args, 'wp' );
503 552 }
504 553 }
505 554
506 555 $this->html .= '</div>';
507 556 } else {
508 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div></div>';
509 -
510 - $this->row( $content, $metas );
557 + $this->no_products_msg( $metas );
511 558 }
512 559
513 560 if ( $is_preview ) {
561 + // phpcs:disable
514 562 $wp_query = $main_query;
515 563 $post = $main_post;
516 564
517 565 wp_reset_query();
518 566 wp_reset_postdata();
567 + // phpcs:enable
519 568 }
520 569
521 570 // Container.
522 571 $this->container( $this->html, $settings, 'rtsb-products-container', $template );
@@ -524,12 +573,13 @@
524 573 return $this->html;
525 574 }
526 575
527 576 /**
528 - * Render Product View.
577 + * Loop Setup for Product View.
529 578 *
530 579 * @param string $template Template name.
531 580 * @param array $settings Control settings.
581 + * @param object $widget Widget object.
532 582 *
533 583 * @return string
534 584 */
535 585 public function setup_loop_for_product_view( $template, $settings, $widget ) {
@@ -546,13 +596,13 @@
546 596
547 597 setup_postdata( $GLOBALS['post'] =& $post_object ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, Squiz.PHP.DisallowMultipleAssignments.Found
548 598
549 599 // Loop arg.
550 - $arg = $this->arg_dataset( $metas, $_product->get_id() );
600 + $arg = $this->arg_dataset( $metas, $_product );
551 601 // Get template.
552 602 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
553 603
554 - $this->row( $html, $metas );
604 + $this->row( $html, $metas, '', $settings );
555 605 }
556 606
557 607 // Container.
558 608 $this->container( $this->html, $settings, 'rtsb-products-container', '', false, false );
@@ -580,8 +630,11 @@
580 630
581 631 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
582 632 $term = $metas['tag_term'];
583 633 }
634 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
635 + $term = $metas['brand_term'];
636 + }
584 637
585 638 // Query.
586 639 if ( $is_single ) {
587 640 if ( empty( $term ) ) {
@@ -648,8 +701,34 @@
648 701
649 702 /**
650 703 * Builds an array with meta values.
651 704 *
705 + * @param array $meta Meta values.
706 + * @param object $product Product object.
707 + * @param bool $lazy_load Lazy Load.
708 + * @param int $i Integer.
709 + *
710 + * @return array
711 + */
712 + public function arg_dataset( array $meta, object $product, bool $lazy_load = false, $i = 0 ) {
713 + $post_id = $product->get_id();
714 + $arg = $this->build_product_arg(
715 + [
716 + 'product' => $product,
717 + 'id' => $product->get_id(),
718 + 'type' => $product->get_type(),
719 + 'meta' => $meta,
720 + 'lazy_load' => $lazy_load,
721 + 'i' => $i,
722 + ]
723 + );
724 +
725 + return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
726 + }
727 +
728 + /**
729 + * Builds an array with meta values for WP_Query.
730 + *
652 731 * @param array $meta Meta values.
653 732 * @param int $post_id Post ID.
654 733 * @param bool $lazy_load Lazy Load.
655 734 * @param int $i Integer.
@@ -655,22 +734,184 @@
655 734 * @param int $i Integer.
656 735 *
657 736 * @return array
658 737 */
659 - public function arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
738 + public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
739 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
660 740 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
661 741 return [];
662 742 }
663 743
664 - global $post, $product;
744 + global $product;
665 745
666 746 if ( ! $product instanceof WC_Product && $post_id ) {
667 - $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.
668 748 }
669 749
670 - $arg = [];
671 - $p_type = $product->get_type();
750 + $post_id = $product->get_id();
751 + $arg = $this->build_product_arg(
752 + [
753 + 'product' => $product,
754 + 'id' => $post_id,
755 + 'type' => $product->get_type(),
756 + 'meta' => $meta,
757 + 'lazy_load' => $lazy_load,
758 + 'i' => $i,
759 + ]
760 + );
672 761
762 + return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
763 + }
764 +
765 + /**
766 + * Builds an array with meta values.
767 + *
768 + * @param array $meta Meta values.
769 + * @param Object $query Category object.
770 + * @param bool $fullwidth Fullwidth check.
771 + * @param bool $lazy_load Lazy Load.
772 + * @param bool $category_loop Category loop.
773 + *
774 + * @return array
775 + */
776 + public function cat_arg_dataset( $meta, $query = null, $fullwidth = false, $lazy_load = false, $category_loop = false ) {
777 + if ( empty( $meta ) ) {
778 + return [];
779 + }
780 +
781 + $arg = [];
782 +
783 + $products = null;
784 + $arg['class'] = null;
785 + $arg['grid'] = null;
786 + $arg['anchor_class'] = null;
787 + $arg['p_sale'] = null;
788 + $arg['count'] = max( $query->count, 0 );
789 + $arg['count_position'] = $meta['count_position'];
790 + $arg['excerpt_position'] = $meta['cat_excerpt_position'];
791 + $arg['cat_link'] = get_term_link( $query );
792 + $arg['target'] = '_self';
793 + $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
794 +
795 + if ( ! empty( $meta['tag_term'] ) ) {
796 + $arg['tag_term'] = $meta['tag_term'];
797 + }
798 +
799 + if ( ! empty( $meta['custom_link']['url'] ) ) {
800 + $arg['cat_link'] = $meta['custom_link']['url'];
801 + $arg['target'] = $meta['custom_link']['is_external'] ? '_blank' : '_self';
802 + }
803 +
804 + $title = $query->name;
805 + $excerpt = $query->description;
806 +
807 + if ( in_array( 'badges', $meta['visibility'], true ) ) {
808 + $arg['p_sale'] = $meta['custom_badge_text'];
809 + }
810 +
811 + $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
812 +
813 + if ( $arg['count'] >= 0 ) {
814 + $prefix = ! empty( $meta['before_count'] ) ? $meta['before_count'] : '';
815 + $suffix = ! empty( $meta['after_count'] ) ? $meta['after_count'] : '';
816 +
817 + $arg['count'] = $prefix . $arg['count'] . $suffix;
818 + }
819 +
820 + if ( $meta['show_custom_title'] && ! empty( $meta['cat_custom_title'] ) ) {
821 + $title = $meta['cat_custom_title'];
822 + }
823 +
824 + if ( $meta['show_custom_excerpt'] && ! empty( $meta['cat_custom_excerpt'] ) ) {
825 + $excerpt = $meta['cat_custom_excerpt'];
826 + }
827 +
828 + $arg['c_img'] = $meta['c_img'];
829 + $arg['items'] = $meta['visibility'];
830 + $arg['title_tag'] = $meta['title_tag'];
831 + $arg['title_class'] = 'category-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
832 + $arg['title'] = Fns::text_truncation( $title, $meta['title_limit_custom'] );
833 + $arg['excerpt_limit'] = $meta['excerpt_limit'];
834 + $arg['excerpt'] = wpautop( Fns::text_truncation( $excerpt, $meta['excerpt_limit_custom'] ) );
835 + $arg['title_link'] = $meta['title_link'];
836 + $arg['image_link'] = $meta['image_link'];
837 + $arg['grid'] .= 'rtsb-col-grid ';
838 +
839 + if ( ! $fullwidth ) {
840 + if ( 'masonry' === $meta['grid_type'] ) {
841 + $arg['class'] .= 'masonry-grid-item ';
842 + } else {
843 + $arg['class'] .= 'even-grid-item ';
844 + }
845 + }
846 +
847 + if ( ! empty( $meta['active_cat_slider'] ) ) {
848 + $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
849 + }
850 +
851 + $arg['class'] .= 'rtsb-category-grid';
852 +
853 + ob_start();
854 + do_action( 'rtsb/categories/category_image_override', $meta, $query->term_id );
855 + $override_category_image = ob_get_clean();
856 +
857 + $arg['img_html'] = $meta['c_img'] ? Fns::get_product_image_html(
858 + 'category',
859 + $query->term_id,
860 + $meta['f_img_size'],
861 + $meta['default_img_id'],
862 + $meta['custom_img_size'],
863 + $lazy_load,
864 + false,
865 + false,
866 + $override_category_image
867 + ) : null;
868 +
869 + if ( $meta['show_custom_image'] && ! empty( $meta['custom_image'] ) ) {
870 + $arg['img_html'] = Fns::get_product_image_html(
871 + 'custom',
872 + null,
873 + $meta['f_img_size'],
874 + $meta['custom_image'],
875 + $meta['custom_img_size'],
876 + $lazy_load
877 + );
878 + }
879 +
880 + $arg['excerpt_class'] = 'category-info' . ( 'block' === $meta['count_position'] ? ' block-count' : ' inline-count' ) . ( 'above' === $meta['cat_excerpt_position'] ? ' excerpt-above' : '' ) . ( empty( $arg['excerpt'] ) ? ' no-excerpt' : '' );
881 +
882 + if ( $category_loop ) {
883 + $query_args = [
884 + 'product_category_id' => $query->term_id,
885 + 'limit' => -1,
886 + 'order' => 'DESC',
887 + 'orderby' => 'date',
888 + ];
889 +
890 + $products_query = new WC_Product_Query( $query_args );
891 + $products = $products_query->get_products();
892 + }
893 +
894 + return apply_filters( 'rtsb/elementor/render/cat_arg_dataset', $arg, $meta, $query, $fullwidth, $lazy_load, $products );
895 + }
896 +
897 + /**
898 + * Building product args.
899 + *
900 + * @param array $args Args.
901 + * @return array
902 + */
903 + public function build_product_arg( $args ) {
904 + list(
905 + 'product' => $product,
906 + 'id' => $post_id,
907 + 'type' => $p_type,
908 + 'meta' => $meta,
909 + 'lazy_load' => $lazy_load,
910 + 'i' => $i,
911 + ) = $args;
912 +
913 + $arg = [];
673 914 $arg['class'] = null;
674 915 $arg['grid'] = null;
675 916 $arg['anchor_class'] = null;
676 917 $arg['hover_img_html'] = null;
@@ -675,26 +916,28 @@
675 916 $arg['anchor_class'] = null;
676 917 $arg['hover_img_html'] = null;
677 918 $arg['s_link'] = [];
678 919 $arg['add_to_cart'] = '';
920 + $arg['p_counter'] = absint( $i );
679 921 $arg['p_id'] = $post_id;
680 - $arg['p_link'] = get_permalink();
922 + $arg['product'] = $product;
923 + $arg['p_link'] = $product->get_permalink();
681 924 $arg['p_sale'] = '';
682 925 $arg['rating_args'] = [];
683 926 $arg['has_attributes'] = $product->is_type( 'variable' ) && $product->get_attributes();
684 927 $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
685 928 $cart_icon_html = '';
929 + $layout = $meta['layout'];
686 930
687 - $layout = $meta['layout'];
688 -
931 + // Building the arg.
689 932 $arg['target'] = '_self';
690 933 $arg['f_img'] = $meta['f_img'];
691 934 $arg['items'] = $meta['visibility'];
692 935 $arg['title_tag'] = $meta['title_tag'];
693 936 $arg['title_class'] = 'product-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
694 - $arg['title'] = Fns::text_truncation( get_the_title(), $meta['title_limit_custom'] );
937 + $arg['title'] = Fns::text_truncation( $product->get_title(), $meta['title_limit_custom'] );
695 938 $arg['excerpt_limit'] = $meta['excerpt_limit'];
696 - $arg['excerpt'] = wpautop( Fns::text_truncation( do_shortcode( $post->post_excerpt ), $meta['excerpt_limit_custom'] ) );
939 + $arg['excerpt'] = wpautop( Fns::text_truncation( do_shortcode( get_post_field( 'post_excerpt', $post_id ) ), $meta['excerpt_limit_custom'] ) );
697 940 $arg['title_link'] = $meta['title_link'];
698 941 $arg['image_link'] = $meta['image_link'];
699 942 $arg['out_of_stock'] = $meta['out_of_stock_badge_text'];
700 943 $arg['action_btn_preset'] = $meta['btn_preset'];
@@ -706,10 +949,15 @@
706 949 $is_list = preg_match( '/list/', $layout );
707 950 $is_carousel = preg_match( '/slider/', $layout );
708 951
709 952 if ( ! $is_carousel ) {
710 - $arg['grid'] .= 'rtsb-col-grid ';
711 - $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 + }
712 960 } else {
713 961 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
714 962 }
715 963
@@ -729,17 +977,21 @@
729 977
730 978 $arg['class'] .= ' animated rtFadeIn';
731 979
732 980 if ( in_array( 'badges', $meta['visibility'], true ) ) {
733 - $arg['p_sale'] = Fns::get_sale_badge(
981 + $badge_module = ! empty( $meta['enable_badges_module'] );
982 + $arg['p_sale'] = Fns::is_module_active( 'product_badges' ) && $badge_module ? 'rtsb_yes' : Fns::get_promo_badge(
983 + $product,
734 984 $meta['sale_badge_type'],
735 985 $meta['sale_badge_text'],
736 - $meta['out_of_stock_badge_text']
986 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
737 987 );
738 988 }
739 989
740 990 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
741 991
992 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
993 +
742 994 if ( ! $meta['show_cart_text'] ) {
743 995 $meta['add_to_cart_text'] = '';
744 996 $meta['add_to_cart_success_text'] = '';
745 997 }
@@ -752,23 +1004,41 @@
752 1004 if ( ! empty( $meta['add_to_cart_success_icon'] ) ) {
753 1005 $cart_icon_html .= Fns::icons_manager( $meta['add_to_cart_success_icon'], 'cart-success-icon' );
754 1006 }
755 1007
756 - $cart_icon = '<span class="icon">' . $cart_icon_html . '</span>';
1008 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
757 1009
758 1010 if ( $product->is_purchasable() || 'grouped' === $p_type || 'external' === $p_type ) {
759 1011 if ( $product->is_in_stock() ) {
760 1012 $arg['add_to_cart'] = $this->render_add_to_cart_button(
761 - get_permalink(),
762 - $post_id,
763 - $p_type,
764 - $meta['add_to_cart_text'],
765 - $meta['add_to_cart_success_text'],
766 - $cart_icon,
767 - $meta['add_to_cart_alignment']
1013 + [
1014 + 'link' => $product->get_permalink(),
1015 + 'id' => $post_id,
1016 + 'type' => $p_type,
1017 + 'text' => $meta['add_to_cart_text'],
1018 + 'success' => $meta['add_to_cart_success_text'],
1019 + 'icon_html' => $cart_icon,
1020 + 'alignment' => $meta['add_to_cart_alignment'],
1021 + 'product' => $product,
1022 + ]
768 1023 );
769 1024 }
770 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 + }
771 1041 }
772 1042
773 1043 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
774 1044 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -831,249 +1101,468 @@
831 1101 'hover_img_html' => $arg['hover_img_html'],
832 1102 'meta' => $meta,
833 1103 ];
834 1104
835 - return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
1105 + return $arg;
836 1106 }
837 -
838 1107 /**
839 - * Builds an array with meta values.
1108 + * Renders add to cart button.
840 1109 *
841 - * @param array $meta Meta values.
842 - * @param Object $query Category object.
843 - * @param bool $fullwidth Fullwidth check.
844 - * @param bool $lazy_load Lazy Load.
1110 + * @param array $args Cart args.
845 1111 *
846 - * @return array
1112 + * @return string
847 1113 */
848 - public function cat_arg_dataset( $meta, $query = null, $fullwidth = false, $lazy_load = false, $category_loop = false ) {
849 - if ( empty( $meta ) ) {
850 - return [];
851 - }
1114 + public function render_add_to_cart_button( $args ) {
1115 + list(
1116 + 'link' => $link,
1117 + 'id' => $id,
1118 + 'type' => $type,
1119 + 'text' => $text,
1120 + 'success' => $success,
1121 + 'icon_html' => $icon_html,
1122 + 'alignment' => $alignment,
1123 + 'product' => $product
1124 + ) = $args;
852 1125
853 - $arg = [];
1126 + $content = null;
1127 + $rand = wp_rand();
1128 + $ext_link = get_post_meta( $id, '_product_url', true );
1129 + $btn_txt = get_post_meta( $id, '_button_text', true );
854 1130
855 - $products = null;
856 - $arg['class'] = null;
857 - $arg['grid'] = null;
858 - $arg['anchor_class'] = null;
859 - $arg['p_sale'] = null;
860 - $arg['count'] = max( $query->count, 0 );
861 - $arg['count_position'] = $meta['count_position'];
862 - $arg['excerpt_position'] = $meta['cat_excerpt_position'];
863 - $arg['cat_link'] = get_term_link( $query );
864 - $arg['target'] = '_self';
865 - $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
1131 + ob_start();
1132 + do_action( 'rtsb/before/cart/button' );
1133 + $content .= ob_get_clean();
866 1134
867 - if ( ! empty( $meta['tag_term'] ) ) {
868 - $arg['tag_term'] = $meta['tag_term'];
1135 + $cart_attr = [
1136 + 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1137 + 'href' => esc_url( $link ),
1138 + 'data-quantity' => '1',
1139 + 'data-product_id' => absint( $id ),
1140 + 'data-id' => absint( $id ),
1141 + ];
1142 +
1143 + if ( empty( $text ) ) {
1144 + $tooltip_mapping = [
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' ),
1150 + ];
1151 +
1152 + $cart_attr['title'] = $tooltip_mapping[ $type ];
869 1153 }
870 1154
871 - if ( ! empty( $meta['custom_link']['url'] ) ) {
872 - $arg['cat_link'] = $meta['custom_link']['url'];
873 - $arg['target'] = $meta['custom_link']['is_external'] ? '_blank' : '_self';
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 ';
874 1158 }
1159 + if ( 'variable' === $type || 'grouped' === $type ) {
1160 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
875 1161
876 - $title = $query->name;
877 - $excerpt = $query->description;
1162 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
878 1163
879 - if ( in_array( 'badges', $meta['visibility'], true ) ) {
880 - $arg['p_sale'] = $meta['custom_badge_text'];
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 + );
1170 +
1171 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1172 + $content .= $icon_html;
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>';
1174 + $content .= '<span></span>';
1175 + } elseif ( 'external' === $type ) {
1176 + if ( ! empty( $ext_link ) ) {
1177 + $cart_attr['target'] = '_blank';
1178 + }
1179 +
1180 + $cart_attr['href'] = ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link );
1181 +
1182 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1183 +
1184 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1185 + $content .= $icon_html;
1186 + $content .= '<span class="text">' . ( ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ) ) . '</span>';
1187 + } else {
1188 + $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1189 + $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1190 + $cart_attr['data-type'] = $type;
1191 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1192 +
1193 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1194 +
1195 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1196 + $content .= $icon_html;
1197 + $content .= '<span class="text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '">' . esc_html( $text ) . '</span>';
1198 + $content .= '<span></span>';
881 1199 }
882 1200
883 - $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
1201 + $content .= '</a>';
1202 + $content .= '</div>';
884 1203
885 - if ( $arg['count'] >= 0 ) {
886 - $prefix = ! empty( $meta['before_count'] ) ? $meta['before_count'] : '';
887 - $suffix = ! empty( $meta['after_count'] ) ? $meta['after_count'] : '';
1204 + ob_start();
1205 + do_action( 'rtsb/after/cart/button' );
1206 + $content .= ob_get_clean();
888 1207
889 - $arg['count'] = $prefix . $arg['count'] . $suffix;
1208 + return apply_filters( 'rtsb/render/cart/button', $content, $args );
1209 + }
1210 +
1211 + /**
1212 + * Button classes.
1213 + *
1214 + * @param array $params Button params.
1215 + *
1216 + * @return array
1217 + */
1218 + public function button_classes( $params ) {
1219 + $params['classes'] = array_merge( $params['classes'], [ 'rtsb-action-btn', 'tipsy' ] );
1220 +
1221 + return $params;
1222 + }
1223 +
1224 + /**
1225 + * Render the section title.
1226 + *
1227 + * @param array $settings The Elementor settings.
1228 + *
1229 + * @return string
1230 + */
1231 + public function render_section_title( $settings ) {
1232 + $html = '';
1233 + $rand = wp_rand();
1234 +
1235 + if ( empty( $settings['show_section_title'] ) || ( empty( $settings['query_products'] ) && 0 === count( $settings['query_products'] ) ) ) {
1236 + return $html;
890 1237 }
891 1238
892 - if ( $meta['show_custom_title'] && ! empty( $meta['cat_custom_title'] ) ) {
893 - $title = $meta['cat_custom_title'];
894 - }
1239 + // Set attributes for the section title.
1240 + $this->add_attribute( 'rtsb_section_title_wrapper_' . $rand, 'class', 'rtsb-section-title-wrapper' );
1241 + $this->add_attribute( 'rtsb_section_title_' . $rand, 'class', 'rtsb-section-title' );
895 1242
896 - if ( $meta['show_custom_excerpt'] && ! empty( $meta['cat_custom_excerpt'] ) ) {
897 - $excerpt = $meta['cat_custom_excerpt'];
1243 + // Start rendering.
1244 + $html .= '<div ' . $this->get_attribute_string( 'rtsb_section_title_wrapper_' . $rand ) . '>';
1245 + $html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' ' . $this->get_attribute_string( 'rtsb_section_title_' . $rand ) . '>';
1246 + $html .= esc_html( $settings['section_title_text'] );
1247 + $html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
1248 + $html .= '</div>';
1249 +
1250 + return $html;
1251 + }
1252 +
1253 + /**
1254 + * Renders products pagination
1255 + *
1256 + * @param array $args Pagination args.
1257 + * @param string $query_type Query type.
1258 + *
1259 + * @return string
1260 + */
1261 + public function render_products_pagination( $args, $query_type = 'wc' ) {
1262 + list(
1263 + 'query' => $query,
1264 + 'meta' => $meta,
1265 + 'limit' => $limit,
1266 + 'per_page' => $per_page
1267 + ) = $args;
1268 +
1269 + $html_utility = null;
1270 + $html = null;
1271 + $ajax = false;
1272 + $is_grid = preg_match( '/grid-layout/', $meta['layout'] ) || preg_match( '/list-layout/', $meta['layout'] );
1273 + $posts_per_page = 'wp' === $query_type ? $query->query_vars['posts_per_page'] : null;
1274 + $total_page = 'wp' === $query_type ? $query->max_num_pages : $query->get_products()->max_num_pages;
1275 +
1276 + if ( $limit ) {
1277 + if ( 'wc' === $query_type ) {
1278 + $found_post = $query->get_products()->total;
1279 + } elseif ( 'wp' === $query_type && ( empty( $wp_query->query['tax_query'] ) ) ) {
1280 + $found_post = $query->found_posts;
1281 + }
1282 +
1283 + if ( $per_page && $found_post > $per_page ) {
1284 + $found_post = $limit;
1285 + $total_page = ceil( $found_post / $per_page );
1286 + }
898 1287 }
899 1288
900 - $layout = $meta['layout'];
1289 + $total_page = absint( $total_page );
901 1290
902 - $arg['c_img'] = $meta['c_img'];
903 - $arg['items'] = $meta['visibility'];
904 - $arg['title_tag'] = $meta['title_tag'];
905 - $arg['title_class'] = 'category-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
906 - $arg['title'] = Fns::text_truncation( $title, $meta['title_limit_custom'] );
907 - $arg['excerpt_limit'] = $meta['excerpt_limit'];
908 - $arg['excerpt'] = wpautop( Fns::text_truncation( $excerpt, $meta['excerpt_limit_custom'] ) );
909 - $arg['title_link'] = $meta['title_link'];
910 - $arg['image_link'] = $meta['image_link'];
1291 + $args = [
1292 + 'total_page' => $total_page,
1293 + 'posts_per_page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1294 + 'ajax' => $ajax,
1295 + ];
911 1296
912 - if ( ! $fullwidth ) {
913 - $arg['grid'] .= 'rtsb-col-grid ';
914 - $arg['class'] .= ' even-grid-item ';
1297 + if ( 'pagination' === $meta['posts_loading_type'] ) {
1298 + if ( $is_grid ) {
1299 + $html_utility .= Fns::custom_pagination(
1300 + $total_page,
1301 + 'wc' === $query_type ? $per_page : $posts_per_page
1302 + );
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 + }
915 1313 } else {
916 - $arg['grid'] .= 'rtsb-col-grid ';
1314 + ob_start();
1315 + do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1316 + $html_utility .= ob_get_clean();
917 1317 }
918 1318
919 - $arg['class'] .= 'rtsb-category-grid';
1319 + if ( $html_utility ) {
1320 + $rand = wp_rand();
920 1321
921 - $arg['img_html'] = $meta['c_img'] ? Fns::get_product_image_html(
922 - 'category',
923 - $query->term_id,
924 - $meta['f_img_size'],
925 - $meta['default_img_id'],
926 - $meta['custom_img_size'],
927 - $lazy_load
928 - ) : null;
1322 + // Adding pagination render attributes.
1323 + $this->add_attribute(
1324 + 'rtsb_pagination_attr_' . $rand,
1325 + [
1326 + 'class' => 'rtsb-pagination-wrap element-loading',
1327 + 'data-total-pages' => $total_page,
1328 + 'data-posts-per-page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1329 + 'data-type' => $meta['posts_loading_type'],
1330 + ]
1331 + );
929 1332
930 - if ( $meta['show_custom_image'] && ! empty( $meta['custom_image'] ) ) {
931 - $arg['img_html'] = Fns::get_product_image_html(
932 - 'custom',
933 - null,
934 - $meta['f_img_size'],
935 - $meta['custom_image'],
936 - $meta['custom_img_size'],
937 - $lazy_load
938 - );
1333 + // Start rendering.
1334 + $html = '<div ' . $this->get_attribute_string( 'rtsb_pagination_attr_' . $rand ) . '>';
1335 + $html .= $html_utility;
1336 + $html .= '</div><!-- .rtsb-pagination-wrap -->';
939 1337 }
940 1338
941 - $arg['excerpt_class'] = 'category-info' . ( 'block' === $meta['count_position'] ? ' block-count' : ' inline-count' ) . ( 'above' === $meta['cat_excerpt_position'] ? ' excerpt-above' : '' ) . ( empty( $arg['excerpt'] ) ? ' no-excerpt' : '' );
1339 + return $html;
1340 + }
942 1341
943 - if ( $category_loop ) {
944 - $query_args = [
945 - 'product_category_id' => $query->term_id,
946 - 'limit' => -1,
947 - 'order' => 'DESC',
948 - 'orderby' => 'date',
949 - ];
1342 + /**
1343 + * Renders slider buttons
1344 + *
1345 + * @param array $settings Settings array.
1346 + *
1347 + * @return string
1348 + */
1349 + public function render_slider_buttons( $settings ) {
1350 + $html = '';
1351 + $left_icon = $settings['left_arrow_icon'];
1352 + $right_icon = $settings['right_arrow_icon'];
950 1353
951 - $products_query = new \WC_Product_Query( $query_args );
952 - $products = $products_query->get_products();
1354 + if ( ! empty( $settings['slider_nav'] ) ) {
1355 + $html .=
1356 + '<div class="swiper-nav">
1357 + <div class="swiper-arrow swiper-button-next">
1358 + ' . Fns::icons_manager( $right_icon ) . '
1359 + </div>
1360 + <div class="swiper-arrow swiper-button-prev">
1361 + ' . Fns::icons_manager( $left_icon ) . '
1362 + </div>
1363 + </div>';
953 1364 }
954 1365
955 - return apply_filters( 'rtsb/elementor/render/cat_arg_dataset', $arg, $meta, $query, $fullwidth, $lazy_load, $products );
1366 + $html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
1367 +
1368 + return $html;
956 1369 }
957 1370
958 1371 /**
959 - * Renders add to cart button.
1372 + * No products message.
960 1373 *
961 - * @param string $link Product link.
962 - * @param int $id Product ID.
963 - * @param string $type Product type.
964 - * @param string $text Custom text.
965 - * @param string $success Success text.
966 - * @param string $icon_html Custom icon.
967 - * @param string $alignment Icon alignment.
1374 + * @param array $metas Meta data.
968 1375 *
1376 + * @return void
1377 + */
1378 + public function no_products_msg( $metas ) {
1379 + $content =
1380 + '<div class="rtsb-notice woocommerce-no-products-found">
1381 + <div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div>
1382 + </div>';
1383 +
1384 + $this->row( $content, $metas );
1385 + }
1386 +
1387 + /**
1388 + * Start the rendering.
1389 + *
1390 + * @param array $metas Meta Data.
1391 + * @param array $settings Widget settings.
1392 + * @param array $temp_args Additional temporary arguments for rendering.
1393 + *
969 1394 * @return string
970 1395 */
971 - public function render_add_to_cart_button( $link, $id, $type, $text, $success, $icon_html, $alignment ) {
972 - $content = null;
973 - $tooltip_attr = '';
974 - $ext_link = get_post_meta( $id, '_product_url', true );
975 - $btn_txt = get_post_meta( $id, '_button_text', true );
1396 + public function render_start( $metas, $settings, $temp_args ) {
1397 + ob_start();
976 1398
977 - if ( empty( $text ) ) {
978 - $tooltip_attr = ' title="' . esc_attr__( 'Add to Cart', 'shopbuilder' ) . '"';
1399 + /**
1400 + * Before render row hook.
1401 + */
1402 + do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $temp_args, $this );
979 1403
980 - if ( 'variable' === $type ) {
981 - $tooltip_attr = ' title="' . esc_attr__( 'Select Options', 'shopbuilder' ) . '"';
1404 + return ob_get_clean();
1405 + }
1406 +
1407 + /**
1408 + * End the rendering.
1409 + *
1410 + * @param array $metas Meta Data.
1411 + * @param array $settings Widget settings.
1412 + * @param array $products Array of products being rendered.
1413 + *
1414 + * @return string
1415 + */
1416 + public function render_end( $metas, $settings, $products ) {
1417 + ob_start();
1418 +
1419 + /**
1420 + * After render row hook.
1421 + */
1422 + do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $products );
1423 +
1424 + return ob_get_clean();
1425 + }
1426 +
1427 + /**
1428 + * Add render attribute.
1429 + *
1430 + * @param array|string $element The HTML element.
1431 + * @param array|string $key Optional. Attribute key. Default is null.
1432 + * @param array|string $value Optional. Attribute value. Default is null.
1433 + * @param bool $overwrite Optional. Whether to overwrite existing.
1434 + *
1435 + * @return self
1436 + */
1437 + public function add_attribute( $element, $key = null, $value = null, $overwrite = false ) {
1438 + if ( is_array( $element ) ) {
1439 + foreach ( $element as $element_key => $attributes ) {
1440 + $this->add_attribute( $element_key, $attributes, null, $overwrite );
982 1441 }
983 1442
984 - if ( 'grouped' === $type ) {
985 - $tooltip_attr = ' title="' . esc_attr__( 'View Products', 'shopbuilder' ) . '"';
1443 + return $this;
1444 + }
1445 +
1446 + if ( is_array( $key ) ) {
1447 + foreach ( $key as $attribute_key => $attributes ) {
1448 + $this->add_attribute( $element, $attribute_key, $attributes, $overwrite );
986 1449 }
987 1450
988 - if ( 'external' === $type ) {
989 - $tooltip_txt = ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' );
990 - $tooltip_attr = ' title="' . esc_attr( $tooltip_txt ) . '"';
991 - }
1451 + return $this;
992 1452 }
993 1453
994 - ob_start();
995 - do_action( 'rtsb/before/cart/button' );
996 - $content .= ob_get_clean();
1454 + if ( empty( $this->attributes[ $element ][ $key ] ) ) {
1455 + $this->attributes[ $element ][ $key ] = [];
1456 + }
997 1457
998 - if ( 'variable' === $type ) {
999 - $data_cart_text = ! empty( $text ) ? esc_attr( $text ) : __( 'Add to Cart', 'shopbuilder' );
1000 - $variable_text = __( 'Select Options', 'shopbuilder' );
1458 + settype( $value, 'array' );
1001 1459
1002 - $content .= sprintf(
1003 - '<div class="rtsb-wc-add-to-cart-wrap">
1004 - <a href="%1$s" data-quantity="1" class="rtsb-action-btn variable-product tipsy icon-%4$s %5$s" data-id="%3$d" data-product_id="%3$s" rel="nofollow"%6$s>%7$s<span class="text" data-cart-text="%8$s" data-variable-text="%9$s" data-success="%10$s">%2$s</span><span></span></a>
1005 - </div>',
1006 - esc_url( $link ),
1007 - esc_html__( 'Select Options', 'shopbuilder' ),
1008 - absint( $id ),
1009 - esc_attr( $alignment ),
1010 - empty( $text ) ? 'no-text' : 'has-text',
1011 - $tooltip_attr,
1012 - $icon_html,
1013 - esc_attr( $data_cart_text ),
1014 - esc_attr( $variable_text ),
1015 - esc_html( $success ),
1016 - );
1017 - } elseif ( 'grouped' === $type ) {
1018 - $content .= sprintf(
1019 - '<div class="rtsb-wc-add-to-cart-wrap">
1020 - <a href="%1$s" data-quantity="1" class="rtsb-action-btn variable-product tipsy icon-%4$s %5$s" data-product_id="%3$s" rel="nofollow"%6$s>%7$s<span class="text">%2$s</span></a>
1021 - </div>',
1022 - esc_url( $link ),
1023 - esc_html__( 'View Products', 'shopbuilder' ),
1024 - absint( $id ),
1025 - esc_attr( $alignment ),
1026 - empty( $text ) ? 'no-text' : 'has-text',
1027 - $tooltip_attr,
1028 - $icon_html,
1029 - );
1030 - } elseif ( 'external' === $type ) {
1031 - $content .= sprintf(
1032 - '<div class="rtsb-wc-add-to-cart-wrap">
1033 - <a href="%1$s" data-quantity="1" class="rtsb-action-btn variable-product tipsy icon-%4$s %5$s" data-product_id="%3$s" rel="nofollow"%6$s %8$s>%7$s<span class="text">%2$s</span></a>
1034 - </div>',
1035 - ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link ),
1036 - ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1037 - absint( $id ),
1038 - esc_attr( $alignment ),
1039 - empty( $text ) ? 'no-text' : 'has-text',
1040 - $tooltip_attr,
1041 - $icon_html,
1042 - ! empty( $ext_link ) ? ' target="_blank"' : ''
1043 - );
1460 + if ( $overwrite ) {
1461 + $this->attributes[ $element ][ $key ] = $value;
1044 1462 } else {
1045 - $content .= sprintf(
1046 - '<a href="%1$s?add-to-cart=%2$d" data-quantity="1" class="rtsb-action-btn rtsb-add-to-cart-btn tipsy icon-%6$s %7$s" data-id="%2$d" data-type="%3$s"%8$s>%4$s<span class="text %10$s" data-success="%9$s">%5$s</span><span></span></a>',
1047 - esc_url( $link ),
1048 - absint( $id ),
1049 - esc_attr( $type ),
1050 - $icon_html,
1051 - esc_html( $text ),
1052 - esc_attr( $alignment ),
1053 - empty( $text ) ? 'no-text' : 'has-text',
1054 - $tooltip_attr,
1055 - esc_html( $success ),
1056 - ! empty( $success ) ? 'has-success-text' : 'no-success-text',
1057 - );
1463 + $this->attributes[ $element ][ $key ] = array_merge( $this->attributes[ $element ][ $key ], $value );
1058 1464 }
1059 1465
1060 - ob_start();
1061 - do_action( 'rtsb/after/cart/button' );
1062 - $content .= ob_get_clean();
1466 + return $this;
1467 + }
1063 1468
1064 - return $content;
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;
1065 1505 }
1066 1506
1067 1507 /**
1068 - * Button classes.
1508 + * Get render attribute string.
1069 1509 *
1070 - * @param array $params Button params.
1510 + * @param string $element The element.
1071 1511 *
1072 - * @return array
1512 + * @return string
1073 1513 */
1074 - public function button_classes( $params ) {
1075 - $params['classes'] = array_merge( $params['classes'], [ 'rtsb-action-btn', 'tipsy' ] );
1514 + public function get_attribute_string( $element ) {
1515 + if ( empty( $this->attributes[ $element ] ) ) {
1516 + return '';
1517 + }
1076 1518
1077 - return $params;
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 );
1078 1567 }
1079 1568 }