PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.4.0
ShopBuilder – WooCommerce Builder For Elementor v3.4.0
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
← All changes | app/Elementor/Render/Render.php +955 -365 2.0.1 → 3.4.0 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,9 +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;
15 +use WC_Product;
16 +use Elementor\Utils;
17 +use WC_Product_Query;
13 18 use RadiusTheme\SB\Helpers\Fns;
14 19 use RadiusTheme\SB\Models\QueryArgs;
15 20 use RadiusTheme\SB\Helpers\BuilderFns;
16 21 use RadiusTheme\SB\Traits\SingletonTrait;
@@ -50,8 +55,15 @@
50 55 */
51 56 public $get_settings;
52 57
53 58 /**
59 + * Element attributes.
60 + *
61 + * @var array
62 + */
63 + private $attributes = [];
64 +
65 + /**
54 66 * Get elementor settings.
55 67 *
56 68 * @return array
57 69 */
@@ -66,100 +78,87 @@
66 78 * @param array $settings Settings.
67 79 * @param string $class Class.
68 80 * @param string $template Template name.
69 81 * @param bool $fullwidth Fullwidth check.
82 + * @param bool $ajax Ajax attributes.
70 83 *
71 84 * @return string
72 85 */
73 - public function container( $content, $settings, $class = '', $template = '', $fullwidth = false ) {
74 - $layout_id = 'rtsb-container-' . absint( wp_rand() );
75 - $container_attr = null;
76 - $cart_txt_class = '';
77 - $container_class = 'rtsb-elementor-container products rtsb-pos-r ' . esc_attr( $class );
78 - $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';
79 99
80 - $layout = $metas['layout'];
81 - $animation = $metas['hover_animation'];
82 -
83 - $is_cat = preg_match( '/category/', $layout );
84 - $is_carousel = preg_match( '/slider/', $layout );
85 -
86 - if ( ! $is_cat ) {
87 - $cart_txt_class = $metas['show_cart_text'] ? ' has-cart-text' : ' no-cart-text';
88 - }
89 -
90 - if ( $is_carousel ) {
91 - $fullwidth = true;
92 - }
93 -
94 - $badge_class = [
95 - 'position' => $metas['badge_position'],
96 - 'alignment' => $metas['badge_alignment'],
97 - ];
98 -
99 - if ( ! 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 ) ) {
100 102 $layout = RenderHelpers::set_default_layout( $layout );
101 103 }
102 104
103 - if ( $is_carousel ) {
104 - $pagination_attr = ! empty( $metas['show_filter'] ) ? RenderHelpers::pagination_data( $settings, $template ) : '';
105 - } elseif ( $is_cat ) {
106 - $pagination_attr = '';
107 - } else {
108 - $pagination_attr = 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 ) );
109 108 }
110 109
111 - if ( ! in_array( 'clear-btn', $metas['visibility'], true ) ) {
112 - $container_class .= ' no-clear-btn';
113 - }
110 + // Container classes.
111 + $container_classes = RenderHelpers::prepare_container_classes( $metas, $settings, $class );
114 112
115 - $style_attr = ' style="--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) ) . '"';
116 - $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 );
117 115
118 - $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 + ];
119 124
120 - if ( isset( $_GET['displayview'] ) ) {
121 - $view_mode = sanitize_text_field( wp_unslash( $_GET['displayview'] ) );
125 + if ( ! empty( $pagination_attributes ) ) {
126 + $container_attributes['data-rtsb-ajax'] = $pagination_attributes;
122 127 }
123 128
124 - if ( 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ) {
125 - $tooltip_attr = ' data-tooltip-position="' . esc_attr( $metas['tooltip_position_list'] ) . '"';
129 + // Opt-out of the scroll-to-top behaviour on Ajax filter/pagination when
130 + // the "Enable Scroll To Top?" switch is turned off.
131 + if ( isset( $settings['enable_ajax_scroll'] ) && empty( $settings['enable_ajax_scroll'] ) ) {
132 + $container_attributes['data-rtsb-disable-scroll'] = '1';
126 133 }
127 134
128 - $container_attr .= ' data-layout="' . esc_attr( $layout ) . '"' . $style_attr . $tooltip_attr . $pagination_attr;
129 - $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'] );
135 + // Adding render attributes.
136 + $this->add_attribute( 'rtsb_container_attr_' . $rand, $container_attributes );
130 137
131 - if ( $metas['show_overlay'] ) {
132 - $container_class .= ' has-overlay';
133 - }
138 + // Start rendering.
139 + $this->html = '<div ' . $this->get_attribute_string( 'rtsb_container_attr_' . $rand ) . '>';
134 140
135 - if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
136 - $container_class .= ' show-single-cat';
137 - }
141 + // Section title.
142 + $this->html .= $this->render_section_title( $settings );
138 143
139 - $container_class = apply_filters( 'rtsb/product/container/class', $container_class, $settings );
140 - $this->html = '<div class="' . esc_attr( $container_class ) . '" id="' . esc_attr( $layout_id ) . '" ' . $container_attr . '>';
141 -
142 - if ( ! empty( $settings['query_products'] ) && count( $settings['query_products'] ) && 'yes' === $settings['show_section_title'] ) {
143 - $this->html .= '<div class="rtsb-section-title-wrapper">';
144 - $this->html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' class="rtsb-section-title" >';
145 - $this->html .= esc_html( $settings['section_title_text'] );
146 - $this->html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
147 - $this->html .= '</div>';
148 - }
149 -
144 + // Content.
150 145 $this->html .= $content;
151 146
147 + // End rendering.
152 148 $this->html .= '</div><!-- .rtsb-elementor-container -->';
153 149
154 150 // Action button icon reset.
155 151 $this->action_button_icon_set_default();
156 152
153 + // Script generator.
157 154 $script_generator = [];
158 155 $script_generator['layout'] = $layout_id;
159 - $script_generator['rand'] = absint( wp_rand() );
160 - $script_generator['isCarousel'] = $is_carousel;
156 + $script_generator['rand'] = absint( $rand );
157 + $script_generator['isCarousel'] = preg_match( '/slider/', $layout );
158 + $script_generator['isMasonry'] = $has_pro && 'masonry' === $grid_style;
161 159
160 + // Register scripts in the wp_footer action.
162 161 add_action(
163 162 'wp_footer',
164 163 static function () use ( $script_generator ) {
165 164 RenderHelpers::register_scripts( $script_generator );
@@ -179,39 +178,42 @@
179 178 *
180 179 * @return string
181 180 */
182 181 public function row( $content, $settings, $hook_html = '', $raw_settings = [] ) {
183 - $masonry_class = null;
184 - $loader_class = ' rtsb-pre-loader';
182 + $raw_layout = esc_html( $settings['layout'] );
183 + $layout = RenderHelpers::filter_layout( $raw_layout );
185 184
186 - $layout = $settings['layout'];
187 - $grid_type = $settings['grid_type'];
188 - $is_carousel = preg_match( '/slider/', $layout );
185 + $rand = wp_rand();
186 + $is_carousel = preg_match( '/slider/', $raw_layout );
189 187
190 - if ( 'even' === $grid_type ) {
191 - $masonry_class = ' rtsb-even';
192 - } elseif ( 'masonry' === $grid_type ) {
193 - $masonry_class = ' rtsb-masonry';
188 + if ( preg_match( '/category/', $raw_layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
189 + $is_carousel = true;
194 190 }
195 191
196 - if ( ! rtsb()->has_pro() || $is_carousel ) {
197 - $masonry_class = ' rtsb-even';
198 - }
192 + // Row classes.
193 + $row_classes = RenderHelpers::prepare_row_classes( $settings );
199 194
200 - $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 );
201 - $row_wrapper = '<div data-title="' . esc_html__( 'Loading ...', 'shopbuilder' ) . '" class="' . $row_start_class . '">';
202 - $start_wrapper = apply_filters( 'rtsb/custom/layout/row/start', $row_wrapper, $row_start_class );
203 - if ( ! empty( $settings['show_filter'] ) ) {
204 - $this->html = $hook_html;
205 - $this->html .= $start_wrapper;
206 - } else {
207 - $this->html = $start_wrapper;
208 - }
195 + // Adding render attributes.
196 + $this->add_attribute( 'rtsb_row_attr_' . $rand, 'class', $row_classes );
209 197
198 + // Start rendering.
199 + $row_wrapper = '<div ' . $this->get_attribute_string( 'rtsb_row_attr_' . $rand ) . '>';
200 + $start_wrapper = apply_filters( 'rtsb/elementor/row/start', $row_wrapper, $raw_settings );
201 +
202 + $this->html = ! empty( $settings['show_filter'] ) ? $hook_html . $start_wrapper : $start_wrapper;
203 +
210 204 if ( $is_carousel ) {
211 205 $slider_data = RenderHelpers::slider_data( $settings, $raw_settings );
206 + // Adding slider render attributes.
207 + $this->add_attribute(
208 + 'rtsb_slider_attr_' . $rand,
209 + [
210 + 'class' => 'rtsb-col-grid ' . $slider_data['class'],
211 + 'data-options' => $slider_data['data'],
212 + ]
213 + );
212 214
213 - $this->html .= '<div class="rtsb-col-grid ' . esc_attr( $slider_data['class'] ) . '" ' . $slider_data['data'] . '>';
215 + $this->html .= '<div ' . $this->get_attribute_string( 'rtsb_slider_attr_' . $rand ) . '>';
214 216 $this->html .= '<div class="swiper-wrapper">';
215 217 }
216 218
217 219 $this->html .= $content;
@@ -218,32 +220,18 @@
218 220
219 221 if ( $is_carousel ) {
220 222 $this->html .= '</div><!-- .swiper-wrapper -->';
221 223
222 - $left_icon = $settings['left_arrow_icon'];
223 - $right_icon = $settings['right_arrow_icon'];
224 -
225 - if ( ! empty( $settings['slider_nav'] ) ) {
226 - $this->html .=
227 - '<div class="swiper-nav">
228 - <div class="swiper-arrow swiper-button-next">
229 - ' . Fns::icons_manager( $right_icon ) . '
230 - </div>
231 - <div class="swiper-arrow swiper-button-prev">
232 - ' . Fns::icons_manager( $left_icon ) . '
233 - </div>
234 - </div>';
235 - }
236 -
237 - $this->html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
238 -
224 + // Slider buttons.
225 + $this->html .= $this->render_slider_buttons( $settings );
239 226 $this->html .= '</div><!-- .rtsb-carousel-slider -->';
240 227 }
241 228
242 229 $row_end = '</div><!-- .rtsb-row -->';
243 230
244 - $this->html .= apply_filters( 'rtsb/custom/layout/row/end', $row_end );
231 + $this->html .= apply_filters( 'rtsb/elementor/row/end', $row_end );
245 232
233 + // Preloader.
246 234 $this->html .= RenderHelpers::pre_loader( $layout );
247 235
248 236 return $this->html;
249 237 }
@@ -250,8 +238,63 @@
250 238
251 239 /**
252 240 * Product loop.
253 241 *
242 + * @param object $products Products.
243 + * @param array $settings Settings.
244 + * @param string $template Template.
245 + *
246 + * @return string|null
247 + */
248 + public function product_loop( $products, $settings, $template ) {
249 + $html = null;
250 +
251 + if ( empty( $products ) ) {
252 + return null;
253 + }
254 +
255 + $i = 0;
256 +
257 + global $product;
258 +
259 + $isGlobalProduct = false;
260 +
261 + if ( $product instanceof WC_Product ) {
262 + $isGlobalProduct = $product;
263 + }
264 +
265 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
266 +
267 + foreach ( $products as $_product ) {
268 + $i++;
269 + $GLOBALS['product'] = $_product; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Global variable is intentionally used to share the current product context with hooks and templates during rendering.
270 + /**
271 + * Before product template render hook.
272 + */
273 + do_action( 'rtsb_before_product_template_render' );
274 +
275 + // Loop arg.
276 + $arg = $this->arg_dataset( $settings, $_product, $settings['lazy_load'], $i );
277 +
278 + // Get template.
279 + $html .= Fns::load_template( $template . $layout, $arg, true );
280 +
281 + /**
282 + * After product template render hook.
283 + */
284 + do_action( 'rtsb_after_product_template_render' );
285 +
286 + unset( $GLOBALS['product'] );
287 + }
288 + if ( $isGlobalProduct ) {
289 + $GLOBALS['product'] = $isGlobalProduct; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Global variable is intentionally used to share the current product context with hooks and templates during rendering.
290 + }
291 + return $html;
292 + }
293 +
294 + /**
295 + * WC Product loop.
296 + *
254 297 * @param object $query The query.
255 298 * @param array $settings Settings.
256 299 * @param string $template Template.
257 300 *
@@ -256,9 +299,9 @@
256 299 * @param string $template Template.
257 300 *
258 301 * @return string|null
259 302 */
260 - public function product_loop( $query, $settings, $template ) {
303 + public function wc_product_loop( $query, $settings, $template ) {
261 304 $html = null;
262 305
263 306 if ( empty( $query ) ) {
264 307 return null;
@@ -263,19 +306,20 @@
263 306 if ( empty( $query ) ) {
264 307 return null;
265 308 }
266 309
267 - $i = 0;
310 + $i = 0;
311 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
268 312
269 313 while ( $query->have_posts() ) {
270 314 $query->the_post();
271 315 $i++;
272 -
316 + global $product;
273 317 // Loop arg.
274 - $arg = $this->arg_dataset( $settings, get_the_ID(), $settings['lazy_load'], $i );
318 + $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
275 319
276 320 // Get template.
277 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
321 + $html .= Fns::load_template( $template . $layout, $arg, true );
278 322 }
279 323
280 324 return $html;
281 325 }
@@ -280,9 +324,9 @@
280 324 return $html;
281 325 }
282 326
283 327 /**
284 - * Product loop.
328 + * Category loop.
285 329 *
286 330 * @param object $query The query.
287 331 * @param array $settings Settings.
288 332 * @param string $template Template.
@@ -290,13 +334,18 @@
290 334 *
291 335 * @return string|null
292 336 */
293 337 public function category_loop( $query, $settings, $template, $is_single = false ) {
294 - $html = null;
338 + $html = null;
339 + $category_loop = false;
295 340
341 + if ( 'category-layout3' === $settings['raw_settings']['layout'] || 'category-single-layout2' === $settings['raw_settings']['layout'] ) {
342 + $category_loop = true;
343 + }
344 +
296 345 if ( $is_single ) {
297 346 // Loop arg.
298 - $arg = $this->cat_arg_dataset( $settings, $query, true );
347 + $arg = $this->cat_arg_dataset( $settings, $query, true, false, $category_loop );
299 348
300 349 // Get template.
301 350 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
302 351 } else {
@@ -301,9 +350,9 @@
301 350 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
302 351 } else {
303 352 foreach ( $query as $term ) {
304 353 // Loop arg.
305 - $arg = $this->cat_arg_dataset( $settings, $term );
354 + $arg = $this->cat_arg_dataset( $settings, $term, false, false, $category_loop );
306 355
307 356 // Get template.
308 357 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
309 358 }
@@ -330,42 +379,56 @@
330 379
331 380 // Query Args.
332 381 $args = ( new QueryArgs() )->buildArgs( $metas, 'product', $is_carousel );
333 382
334 - $temp_args = $args;
335 - $args = apply_filters( 'rtsb/elementor/args_before_loop', $args, $metas );
383 + $temp_args = $args;
384 + $default_args = [
385 + 'visibility' => 'visible',
386 + 'status' => 'publish',
387 + ];
388 + $custom_args = apply_filters( 'rtsb/elementor/args_before_loop', $args, $metas );
389 + $args = wp_parse_args( $custom_args, $default_args );
336 390
391 + /**
392 + * Before Product query hook.
393 + *
394 + * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::modify_wc_query_args 10
395 + */
396 +
397 + do_action( 'rtsb/elements/render/before_query', $settings, $args );
337 398 // Query.
338 - $product_query = new WP_Query( $args );
399 + $product_query = new WC_Product_Query( $args );
339 400
340 - if ( $product_query->have_posts() ) {
341 - // Elementor Render hook before row.
342 - ob_start();
343 - do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $product_query, $temp_args );
344 - $hook_html .= ob_get_clean();
401 + /**
402 + * Product query hook.
403 + */
404 + do_action( 'rtsb/elements/render/product_query', $product_query, $this );
345 405
406 + $products = $pagination ? $product_query->get_products()->products : $product_query->get_products();
407 +
408 + if ( ! empty( $products ) ) {
409 + // Start rendering.
410 + $hook_html .= $this->render_start( $metas, $settings, $temp_args );
411 +
346 412 // Row.
347 - $this->row( $this->product_loop( $product_query, $metas, $template ), $metas, $hook_html, $settings );
413 + $this->row( $this->product_loop( $products, $metas, $template ), $metas, $hook_html, $settings );
348 414
349 415 // Pagination.
350 416 if ( $pagination ) {
351 - $this->html .= RenderHelpers::render_pagination(
352 - $product_query,
353 - $metas,
354 - $settings['posts_limit'],
355 - $settings['pagination_per_page']
356 - );
417 + $pagination_args = [
418 + 'query' => $product_query,
419 + 'meta' => $metas,
420 + 'limit' => $settings['posts_limit'],
421 + 'per_page' => $settings['pagination_per_page'],
422 + ];
423 +
424 + $this->html .= $this->render_products_pagination( $pagination_args );
357 425 }
358 426
359 - // Elementor Render hook before row.
360 - ob_start();
361 - do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $product_query );
362 - $this->html .= ob_get_clean();
363 -
427 + // End rendering.
428 + $this->html .= $this->render_end( $metas, $settings, $products );
364 429 } else {
365 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder-pro' ) . '</div></div>';
366 -
367 - $this->row( $content, $metas );
430 + $this->no_products_msg( $metas );
368 431 }
369 432
370 433 wp_reset_postdata();
371 434
@@ -370,8 +433,15 @@
370 433 wp_reset_postdata();
371 434
372 435 unset( $args['tax_query'] );
373 436
437 + /**
438 + * After Product query hook.
439 + *
440 + * @hooked RadiusTheme\SB\Controllers\Hooks\ActionHooks::reset_wc_query_args 10
441 + */
442 + do_action( 'rtsb/elements/render/after_query', $settings, $args );
443 +
374 444 // Container.
375 445 $this->container( $this->html, $settings, 'rtsb-products-container', $template, $is_carousel );
376 446
377 447 return $this->html;
@@ -376,9 +446,8 @@
376 446
377 447 return $this->html;
378 448 }
379 449
380 -
381 450 /**
382 451 * Render Product View.
383 452 *
384 453 * @param string $template Template name.
@@ -390,24 +459,29 @@
390 459 public function wc_loop_for_product_view( $template, $settings, $widget ) {
391 460 global $wp_query, $post;
392 461
393 462 $this->get_settings = $settings;
463 + $rand = wp_rand();
394 464 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
395 465
396 - $settings['rtsb_order'] = ! empty( $wp_query->query_vars['order'] ) ? $wp_query->query_vars['order'] : 'ASC';
397 - $settings['rtsb_orderby'] = ! empty( $wp_query->query_vars['orderby'] ) ? $wp_query->query_vars['orderby'] : 'menu_order';
466 + $order = strtoupper( $wp_query->query_vars['order'] ?? 'ASC' );
467 + $settings['rtsb_orderby'] = RenderHelpers::escaping_product_orderby( $wp_query->query_vars['orderby'] ?? 'menu_order' );
468 + $settings['rtsb_order'] = in_array( $order, [ 'ASC','DESC' ], true ) ? $order : 'ASC';
398 469
399 470 $pagination = $metas['pagination'];
400 471
401 - $page_type = BuilderFns::builder_type( get_the_ID() );
402 - $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
403 - $posts_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
404 -
472 + $page_type = BuilderFns::builder_type( get_the_ID() );
473 + $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
474 + $raw_settings = $widget->get_settings_for_display();
475 + $widget_per_page = ! empty( $raw_settings['posts_per_page'] ) ? absint( $raw_settings['posts_per_page'] ) : 0;
476 + $wc_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
477 + $posts_per_page = $widget_per_page ? $widget_per_page : apply_filters( 'loop_shop_per_page', $wc_per_page ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- woocommerce default hooks used.
478 + $restore_query = false;
405 479 if ( $is_preview ) {
406 480 $main_query = clone $wp_query;
407 481 $main_post = clone $post;
408 482
409 - $ordering = ( new \WC_Query() )->get_catalog_ordering_args();
483 + $ordering = ( new WC_Query() )->get_catalog_ordering_args();
410 484 $wp_query_args = [
411 485 'post_type' => 'product',
412 486 'posts_per_page' => $posts_per_page,
413 487 ];
@@ -419,62 +493,112 @@
419 493 if ( ! empty( $ordering['order'] ) ) {
420 494 $wp_query_args['order'] = $ordering['order'];
421 495 }
422 496
423 - $wp_query = new \WP_Query( $wp_query_args ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
497 + $wp_query = new WP_Query( $wp_query_args ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
498 +
424 499 wc_set_loop_prop( 'total', $wp_query->found_posts );
425 500 wc_set_loop_prop( 'total_pages', $wp_query->max_num_pages );
501 + } elseif ( ! is_admin() && ( is_shop() || is_product_taxonomy() ) && absint( $wp_query->get( 'posts_per_page' ) ) !== $posts_per_page ) {
502 + // Apply widget's posts_per_page to the main query on the live frontend.
503 + $main_query = clone $wp_query;
504 + $main_post = clone $post;
505 +
506 + $query_vars = $wp_query->query_vars;
507 + $query_vars['posts_per_page'] = $posts_per_page;
508 +
509 + $wp_query = new WP_Query( $query_vars ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
510 +
511 + wc_set_loop_prop( 'total', $wp_query->found_posts );
512 + wc_set_loop_prop( 'total_pages', $wp_query->max_num_pages );
513 +
514 + $restore_query = true;
426 515 }
427 516
517 + if ( rtsb()->has_pro() && ( Fns::product_has_applied_filters( 'shop' ) || Fns::product_has_applied_filters( 'archive' ) || 'rtsb_builder' === get_post_type( get_the_ID() ) ) ) {
518 + $rtsb_active_filters_html = Fns::get_active_filters_html();
519 + $rtsb_wrapper_style = '' !== $rtsb_active_filters_html ? ' style="' . esc_attr( 'display:block;' ) . '"' : '';
520 + echo '<div class="rtsb-active-filters-wrapper"' . $rtsb_wrapper_style . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Style value is escaped above.
521 + Fns::print_html( $rtsb_active_filters_html, true );
522 + echo '</div>';
523 + }
524 +
428 525 // column_per_row.
429 526 if ( woocommerce_product_loop() ) {
430 527 if ( wc_get_loop_prop( 'total' ) ) {
431 - if ( Fns::product_has_applied_filters( 'shop' ) || Fns::product_has_applied_filters( 'archive' ) || 'rtsb_builder' === get_post_type( get_the_ID() ) ) {
432 - echo '<div class="rtsb-active-filters-wrapper"></div>';
433 - }
434 528
435 529 // Row.
436 - $this->row( $this->product_loop( $wp_query, $metas, $template ), $metas );
530 + $this->row( $this->wc_product_loop( $wp_query, $metas, $template ), $metas, '', $settings );
437 531 }
438 532
439 533 // Pagination.
440 - $this->html .= '<div class="rtsb-archive-pagination-wrap">';
534 + $this->html .= '<div class="rtsb-archive-pagination-wrap" data-per-page="' . absint( $posts_per_page ) . '">';
441 535
442 536 if ( $pagination ) {
443 - if ( Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' ) ) {
537 + if ( Fns::product_filters_has_ajax( apply_filters( 'rtsb/builder/set/current/page/type', '' ) ) ) {
444 538 if ( $wp_query->max_num_pages > 1 ) {
445 - $this->html .= "
446 - <div class='rtsb-load-more rtsb-pos-r'>
447 - <button data-paged='2' data-max-page='{$wp_query->max_num_pages}'><span>" . esc_html__( 'Load More', 'shopbuilder-pro' ) . "</span></button>
448 - <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
449 - </div>
450 - ";
539 + $posts_loading_type = ! empty( $metas['posts_loading_type'] ) ? $metas['posts_loading_type'] : 'pagination';
540 +
541 + if ( 'pagination_ajax' === $posts_loading_type ) {
542 + $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, true );
543 + } elseif ( 'pagination' === $posts_loading_type ) {
544 + $this->html .= Fns::custom_pagination( $wp_query->max_num_pages, $posts_per_page, false );
545 + } elseif ( 'load_on_scroll' === $posts_loading_type ) {
546 + $this->html .= "<div class='rtsb-infinite-scroll' data-trigger='1' data-paged='2' data-max-page='{$wp_query->max_num_pages}'>
547 + <div class='rtsb-infinite-action'>
548 + <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
549 + </div>
550 + </div>";
551 + } else {
552 + $load_more_text = ! empty( $metas['load_more_text'] ) ? $metas['load_more_text'] : esc_html__( 'Load More', 'shopbuilder' );
553 +
554 + $this->add_attribute(
555 + 'rtsb_load_more_btn_attr_' . $rand,
556 + [
557 + 'data-paged' => '2',
558 + 'data-max-page' => $wp_query->max_num_pages,
559 + ]
560 + );
561 +
562 + $this->html .=
563 + "<div class='rtsb-load-more rtsb-pos-r'>
564 + <button {$this->get_attribute_string( 'rtsb_load_more_btn_attr_' . $rand )}>
565 + <span>" . esc_html( $load_more_text ) . "</span>
566 + </button>
567 + <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
568 + </div>";
569 + }
451 570 }
452 571 } else {
453 - $this->html .= RenderHelpers::render_pagination(
454 - $wp_query,
455 - $metas,
456 - wc_get_loop_prop( 'total' ),
457 - $posts_per_page
458 - );
572 + $pagination_args = [
573 + 'query' => $wp_query,
574 + 'meta' => $metas,
575 + 'limit' => wc_get_loop_prop( 'total' ),
576 + 'per_page' => $posts_per_page,
577 + ];
578 +
579 + $this->html .= $this->render_products_pagination( $pagination_args, 'wp' );
459 580 }
460 581 }
461 582
462 583 $this->html .= '</div>';
463 584 } else {
464 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder-pro' ) . '</div></div>';
465 -
466 - $this->row( $content, $metas );
585 + $this->no_products_msg( $metas );
467 586 }
468 587
469 - if ( $is_preview ) {
588 + if ( $is_preview || ! empty( $restore_query ) ) {
589 + // phpcs:disable
470 590 $wp_query = $main_query;
471 591 $post = $main_post;
472 592
473 593 wp_reset_query();
474 594 wp_reset_postdata();
595 + // phpcs:enable
475 596 }
476 597
598 + // Ensure the container's data-rtsb-ajax uses the same posts_per_page as the initial render.
599 + $settings['pagination_per_page'] = $posts_per_page;
600 +
477 601 // Container.
478 602 $this->container( $this->html, $settings, 'rtsb-products-container', $template );
479 603
480 604 return $this->html;
@@ -480,12 +604,13 @@
480 604 return $this->html;
481 605 }
482 606
483 607 /**
484 - * Render Product View.
608 + * Loop Setup for Product View.
485 609 *
486 610 * @param string $template Template name.
487 611 * @param array $settings Control settings.
612 + * @param object $widget Widget object.
488 613 *
489 614 * @return string
490 615 */
491 616 public function setup_loop_for_product_view( $template, $settings, $widget ) {
@@ -502,17 +627,17 @@
502 627
503 628 setup_postdata( $GLOBALS['post'] =& $post_object ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, Squiz.PHP.DisallowMultipleAssignments.Found
504 629
505 630 // Loop arg.
506 - $arg = $this->arg_dataset( $metas, $_product->get_id() );
631 + $arg = $this->arg_dataset( $metas, $_product );
507 632 // Get template.
508 633 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
509 634
510 - $this->row( $html, $metas );
635 + $this->row( $html, $metas, '', $settings );
511 636 }
512 637
513 638 // Container.
514 - $this->container( $this->html, $settings, 'rtsb-products-container' );
639 + $this->container( $this->html, $settings, 'rtsb-products-container', '', false, false );
515 640
516 641 wp_reset_postdata();
517 642
518 643 return $this->html;
@@ -527,8 +652,13 @@
527 652 *
528 653 * @return string
529 654 */
530 655 public function category_view( $template, $settings, $is_single = false ) {
656 + // Reset the shared singleton's html buffer so leftover markup from a
657 + // previously rendered widget (e.g. a products grid that ran earlier
658 + // in the same request) doesn't get folded into the categories
659 + // container by container() below.
660 + $this->html = '';
531 661 $this->get_settings = $settings;
532 662
533 663 $metas = RenderHelpers::meta_dataset( $settings, $template, $this->get_settings_for_display() );
534 664 $taxonomy = $metas['category_source'];
@@ -536,8 +666,11 @@
536 666
537 667 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
538 668 $term = $metas['tag_term'];
539 669 }
670 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
671 + $term = $metas['brand_term'];
672 + }
540 673
541 674 // Query.
542 675 if ( $is_single ) {
543 676 if ( empty( $term ) ) {
@@ -551,13 +684,13 @@
551 684
552 685 $taxonomy_query = get_terms( $args );
553 686 }
554 687
555 - if ( ! empty( $taxonomy_query ) ) {
688 + if ( ! is_wp_error( $taxonomy_query ) && ! empty( $taxonomy_query ) ) {
556 689 // Row.
557 690 $this->row( $this->category_loop( $taxonomy_query, $metas, $template, $is_single ), $metas );
558 691 } else {
559 - $this->html .= '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
692 + $this->html = '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
560 693 }
561 694
562 695 // Container.
563 696 $this->container( $this->html, $settings, 'rtsb-categories-container', $is_single );
@@ -604,8 +737,34 @@
604 737
605 738 /**
606 739 * Builds an array with meta values.
607 740 *
741 + * @param array $meta Meta values.
742 + * @param object $product Product object.
743 + * @param bool $lazy_load Lazy Load.
744 + * @param int $i Integer.
745 + *
746 + * @return array
747 + */
748 + public function arg_dataset( array $meta, object $product, bool $lazy_load = false, $i = 0 ) {
749 + $post_id = $product->get_id();
750 + $arg = $this->build_product_arg(
751 + [
752 + 'product' => $product,
753 + 'id' => $product->get_id(),
754 + 'type' => $product->get_type(),
755 + 'meta' => $meta,
756 + 'lazy_load' => $lazy_load,
757 + 'i' => $i,
758 + ]
759 + );
760 +
761 + return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
762 + }
763 +
764 + /**
765 + * Builds an array with meta values for WP_Query.
766 + *
608 767 * @param array $meta Meta values.
609 768 * @param int $post_id Post ID.
610 769 * @param bool $lazy_load Lazy Load.
611 770 * @param int $i Integer.
@@ -611,19 +770,184 @@
611 770 * @param int $i Integer.
612 771 *
613 772 * @return array
614 773 */
615 - public function arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
774 + public function wp_arg_dataset( array $meta, int $post_id, bool $lazy_load = false, $i = 0 ) {
775 + // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
616 776 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
617 777 return [];
618 778 }
619 779
620 - global $post;
780 + global $product;
621 781
622 - $arg = [];
623 - $_product = wc_get_product( $post_id );
624 - $p_type = $_product->get_type();
782 + if ( ! $product instanceof WC_Product && $post_id ) {
783 + $product = wc_get_product( $post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Global variable is intentionally used to share the current product context with hooks and templates during rendering.
784 + }
625 785
786 + $post_id = $product->get_id();
787 + $arg = $this->build_product_arg(
788 + [
789 + 'product' => $product,
790 + 'id' => $post_id,
791 + 'type' => $product->get_type(),
792 + 'meta' => $meta,
793 + 'lazy_load' => $lazy_load,
794 + 'i' => $i,
795 + ]
796 + );
797 +
798 + return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
799 + }
800 +
801 + /**
802 + * Builds an array with meta values.
803 + *
804 + * @param array $meta Meta values.
805 + * @param Object $query Category object.
806 + * @param bool $fullwidth Fullwidth check.
807 + * @param bool $lazy_load Lazy Load.
808 + * @param bool $category_loop Category loop.
809 + *
810 + * @return array
811 + */
812 + public function cat_arg_dataset( $meta, $query = null, $fullwidth = false, $lazy_load = false, $category_loop = false ) {
813 + if ( empty( $meta ) ) {
814 + return [];
815 + }
816 +
817 + $arg = [];
818 +
819 + $products = null;
820 + $arg['class'] = null;
821 + $arg['grid'] = null;
822 + $arg['anchor_class'] = null;
823 + $arg['p_sale'] = null;
824 + $arg['count'] = max( $query->count, 0 );
825 + $arg['count_position'] = $meta['count_position'];
826 + $arg['excerpt_position'] = $meta['cat_excerpt_position'];
827 + $arg['cat_link'] = get_term_link( $query );
828 + $arg['target'] = '_self';
829 + $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
830 +
831 + if ( ! empty( $meta['tag_term'] ) ) {
832 + $arg['tag_term'] = $meta['tag_term'];
833 + }
834 +
835 + if ( ! empty( $meta['custom_link']['url'] ) ) {
836 + $arg['cat_link'] = $meta['custom_link']['url'];
837 + $arg['target'] = $meta['custom_link']['is_external'] ? '_blank' : '_self';
838 + }
839 +
840 + $title = $query->name;
841 + $excerpt = $query->description;
842 +
843 + if ( in_array( 'badges', $meta['visibility'], true ) ) {
844 + $arg['p_sale'] = $meta['custom_badge_text'];
845 + }
846 +
847 + $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
848 +
849 + if ( $arg['count'] >= 0 ) {
850 + $prefix = ! empty( $meta['before_count'] ) ? $meta['before_count'] : '';
851 + $suffix = ! empty( $meta['after_count'] ) ? $meta['after_count'] : '';
852 +
853 + $arg['count'] = $prefix . $arg['count'] . $suffix;
854 + }
855 +
856 + if ( $meta['show_custom_title'] && ! empty( $meta['cat_custom_title'] ) ) {
857 + $title = $meta['cat_custom_title'];
858 + }
859 +
860 + if ( $meta['show_custom_excerpt'] && ! empty( $meta['cat_custom_excerpt'] ) ) {
861 + $excerpt = $meta['cat_custom_excerpt'];
862 + }
863 +
864 + $arg['c_img'] = $meta['c_img'];
865 + $arg['items'] = $meta['visibility'];
866 + $arg['title_tag'] = $meta['title_tag'];
867 + $arg['title_class'] = 'category-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
868 + $arg['title'] = Fns::text_truncation( $title, $meta['title_limit_custom'] );
869 + $arg['excerpt_limit'] = $meta['excerpt_limit'];
870 + $arg['excerpt'] = wpautop( Fns::text_truncation( $excerpt, $meta['excerpt_limit_custom'] ) );
871 + $arg['title_link'] = $meta['title_link'];
872 + $arg['image_link'] = $meta['image_link'];
873 + $arg['grid'] .= 'rtsb-col-grid ';
874 +
875 + if ( ! $fullwidth ) {
876 + if ( 'masonry' === $meta['grid_type'] ) {
877 + $arg['class'] .= 'masonry-grid-item ';
878 + } else {
879 + $arg['class'] .= 'even-grid-item ';
880 + }
881 + }
882 +
883 + if ( ! empty( $meta['active_cat_slider'] ) ) {
884 + $arg['class'] .= ' rtsb-slide-item swiper-slide animated rtFadeIn ';
885 + }
886 +
887 + $arg['class'] .= 'rtsb-category-grid';
888 +
889 + ob_start();
890 + do_action( 'rtsb/categories/category_image_override', $meta, $query->term_id );
891 + $override_category_image = ob_get_clean();
892 +
893 + $arg['img_html'] = $meta['c_img'] ? Fns::get_product_image_html(
894 + 'category',
895 + $query->term_id,
896 + $meta['f_img_size'],
897 + $meta['default_img_id'],
898 + $meta['custom_img_size'],
899 + $lazy_load,
900 + false,
901 + false,
902 + $override_category_image
903 + ) : null;
904 +
905 + if ( $meta['show_custom_image'] && ! empty( $meta['custom_image'] ) ) {
906 + $arg['img_html'] = Fns::get_product_image_html(
907 + 'custom',
908 + null,
909 + $meta['f_img_size'],
910 + $meta['custom_image'],
911 + $meta['custom_img_size'],
912 + $lazy_load
913 + );
914 + }
915 +
916 + $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' : '' );
917 +
918 + if ( $category_loop ) {
919 + $query_args = [
920 + 'product_category_id' => $query->term_id,
921 + 'limit' => -1,
922 + 'order' => 'DESC',
923 + 'orderby' => 'date',
924 + ];
925 +
926 + $products_query = new WC_Product_Query( $query_args );
927 + $products = $products_query->get_products();
928 + }
929 +
930 + return apply_filters( 'rtsb/elementor/render/cat_arg_dataset', $arg, $meta, $query, $fullwidth, $lazy_load, $products );
931 + }
932 +
933 + /**
934 + * Building product args.
935 + *
936 + * @param array $args Args.
937 + * @return array
938 + */
939 + public function build_product_arg( $args ) {
940 + list(
941 + 'product' => $product,
942 + 'id' => $post_id,
943 + 'type' => $p_type,
944 + 'meta' => $meta,
945 + 'lazy_load' => $lazy_load,
946 + 'i' => $i,
947 + ) = $args;
948 +
949 + $arg = [];
626 950 $arg['class'] = null;
627 951 $arg['grid'] = null;
628 952 $arg['anchor_class'] = null;
629 953 $arg['hover_img_html'] = null;
@@ -628,25 +952,28 @@
628 952 $arg['anchor_class'] = null;
629 953 $arg['hover_img_html'] = null;
630 954 $arg['s_link'] = [];
631 955 $arg['add_to_cart'] = '';
956 + $arg['p_counter'] = absint( $i );
632 957 $arg['p_id'] = $post_id;
633 - $arg['p_link'] = get_permalink();
958 + $arg['product'] = $product;
959 + $arg['p_link'] = $product->get_permalink();
634 960 $arg['p_sale'] = '';
635 - $arg['has_attributes'] = $_product->is_type( 'variable' ) && $_product->get_attributes();
961 + $arg['rating_args'] = [];
962 + $arg['has_attributes'] = $product->is_type( 'variable' ) && $product->get_attributes();
636 963 $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
637 964 $cart_icon_html = '';
965 + $layout = $meta['layout'];
638 966
639 - $layout = $meta['layout'];
640 -
967 + // Building the arg.
641 968 $arg['target'] = '_self';
642 969 $arg['f_img'] = $meta['f_img'];
643 970 $arg['items'] = $meta['visibility'];
644 971 $arg['title_tag'] = $meta['title_tag'];
645 972 $arg['title_class'] = 'product-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
646 - $arg['title'] = Fns::text_truncation( get_the_title(), $meta['title_limit_custom'] );
973 + $arg['title'] = Fns::text_truncation( $product->get_title(), $meta['title_limit_custom'] );
647 974 $arg['excerpt_limit'] = $meta['excerpt_limit'];
648 - $arg['excerpt'] = wpautop( Fns::text_truncation( do_shortcode( $post->post_excerpt ), $meta['excerpt_limit_custom'] ) );
975 + $arg['excerpt'] = wpautop( Fns::text_truncation( do_shortcode( get_post_field( 'post_excerpt', $post_id ) ), $meta['excerpt_limit_custom'] ) );
649 976 $arg['title_link'] = $meta['title_link'];
650 977 $arg['image_link'] = $meta['image_link'];
651 978 $arg['out_of_stock'] = $meta['out_of_stock_badge_text'];
652 979 $arg['action_btn_preset'] = $meta['btn_preset'];
@@ -658,10 +985,15 @@
658 985 $is_list = preg_match( '/list/', $layout );
659 986 $is_carousel = preg_match( '/slider/', $layout );
660 987
661 988 if ( ! $is_carousel ) {
662 - $arg['grid'] .= 'rtsb-col-grid ';
663 - $arg['class'] .= ' even-grid-item ';
989 + $arg['grid'] .= 'rtsb-col-grid ';
990 +
991 + if ( 'masonry' === $meta['grid_type'] ) {
992 + $arg['class'] .= 'masonry-grid-item ';
993 + } else {
994 + $arg['class'] .= 'even-grid-item ';
995 + }
664 996 } else {
665 997 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
666 998 }
667 999
@@ -681,17 +1013,21 @@
681 1013
682 1014 $arg['class'] .= ' animated rtFadeIn';
683 1015
684 1016 if ( in_array( 'badges', $meta['visibility'], true ) ) {
685 - $arg['p_sale'] = Fns::get_sale_badge(
1017 + $badge_module = ! empty( $meta['enable_badges_module'] );
1018 + $arg['p_sale'] = Fns::is_module_active( 'product_badges' ) && $badge_module ? 'rtsb_yes' : Fns::get_promo_badge(
1019 + $product,
686 1020 $meta['sale_badge_type'],
687 1021 $meta['sale_badge_text'],
688 - $meta['out_of_stock_badge_text']
1022 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
689 1023 );
690 1024 }
691 1025
692 1026 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
693 1027
1028 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
1029 +
694 1030 if ( ! $meta['show_cart_text'] ) {
695 1031 $meta['add_to_cart_text'] = '';
696 1032 $meta['add_to_cart_success_text'] = '';
697 1033 }
@@ -704,23 +1040,41 @@
704 1040 if ( ! empty( $meta['add_to_cart_success_icon'] ) ) {
705 1041 $cart_icon_html .= Fns::icons_manager( $meta['add_to_cart_success_icon'], 'cart-success-icon' );
706 1042 }
707 1043
708 - $cart_icon = '<span class="icon">' . $cart_icon_html . '</span>';
1044 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
709 1045
710 - if ( $_product->is_purchasable() || 'grouped' === $p_type || 'external' === $p_type ) {
711 - if ( $_product->is_in_stock() ) {
1046 + if ( $product->is_purchasable() || 'grouped' === $p_type || 'external' === $p_type ) {
1047 + if ( $product->is_in_stock() ) {
712 1048 $arg['add_to_cart'] = $this->render_add_to_cart_button(
713 - get_permalink(),
714 - $post_id,
715 - $p_type,
716 - $meta['add_to_cart_text'],
717 - $meta['add_to_cart_success_text'],
718 - $cart_icon,
719 - $meta['add_to_cart_alignment']
1049 + [
1050 + 'link' => $product->get_permalink(),
1051 + 'id' => $post_id,
1052 + 'type' => $p_type,
1053 + 'text' => $meta['add_to_cart_text'],
1054 + 'success' => $meta['add_to_cart_success_text'],
1055 + 'icon_html' => $cart_icon,
1056 + 'alignment' => $meta['add_to_cart_alignment'],
1057 + 'product' => $product,
1058 + ]
720 1059 );
721 1060 }
722 1061 }
1062 + if ( Fns::is_catalog_mode() && empty( $arg['add_to_cart'] ) ) {
1063 + if ( ! empty( $meta['add_to_cart_icon'] ) ) {
1064 + $cart_icon_html = Fns::icons_manager( $meta['add_to_cart_icon'], 'cart-icon' );
1065 + }
1066 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
1067 + $arg['add_to_cart'] = CatalogModeFns::render_elementor_widget_catalog_mode_button(
1068 + [
1069 + 'id' => $post_id,
1070 + 'type' => $p_type,
1071 + 'text' => $meta['add_to_cart_text'],
1072 + 'icon_html' => $cart_icon,
1073 + 'alignment' => $meta['add_to_cart_alignment'],
1074 + ]
1075 + );
1076 + }
723 1077 }
724 1078
725 1079 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
726 1080 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -745,10 +1099,10 @@
745 1099 $meta['custom_img_size'],
746 1100 $lazy_load
747 1101 ) : null;
748 1102
749 - if ( ! ( function_exists( 'rtwpvsp' ) && $_product->is_type( 'variable' ) && $_product->get_attributes() ) ) {
750 - $gallery_ids = $_product->get_gallery_image_ids();
1103 + if ( ! ( function_exists( 'rtwpvsp' ) && $product->is_type( 'variable' ) && $product->get_attributes() ) ) {
1104 + $gallery_ids = $product->get_gallery_image_ids();
751 1105
752 1106 if ( $meta['hover_img'] && ! empty( $gallery_ids ) ) {
753 1107 $arg['class'] .= ! $meta['gallery_img'] ? ' rtsb-double-img' : '';
754 1108
@@ -783,230 +1137,466 @@
783 1137 'hover_img_html' => $arg['hover_img_html'],
784 1138 'meta' => $meta,
785 1139 ];
786 1140
787 - return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
1141 + return $arg;
788 1142 }
789 -
790 1143 /**
791 - * Builds an array with meta values.
1144 + * Renders add to cart button.
792 1145 *
793 - * @param array $meta Meta values.
794 - * @param Object $query Category object.
795 - * @param bool $fullwidth Fullwidth check.
796 - * @param bool $lazy_load Lazy Load.
1146 + * @param array $args Cart args.
797 1147 *
798 - * @return array
1148 + * @return string
799 1149 */
800 - public function cat_arg_dataset( $meta, $query = null, $fullwidth = false, $lazy_load = false ) {
801 - if ( empty( $meta ) ) {
802 - return [];
803 - }
1150 + public function render_add_to_cart_button( $args ) {
1151 + list(
1152 + 'link' => $link,
1153 + 'id' => $id,
1154 + 'type' => $type,
1155 + 'text' => $text,
1156 + 'success' => $success,
1157 + 'icon_html' => $icon_html,
1158 + 'alignment' => $alignment,
1159 + 'product' => $product
1160 + ) = $args;
804 1161
805 - $arg = [];
1162 + $content = null;
1163 + $rand = wp_rand();
1164 + $ext_link = get_post_meta( $id, '_product_url', true );
1165 + $btn_txt = get_post_meta( $id, '_button_text', true );
806 1166
807 - $arg['class'] = null;
808 - $arg['grid'] = null;
809 - $arg['anchor_class'] = null;
810 - $arg['p_sale'] = null;
811 - $arg['count'] = max( $query->count, 0 );
812 - $arg['count_position'] = $meta['count_position'];
813 - $arg['excerpt_position'] = $meta['cat_excerpt_position'];
814 - $arg['cat_link'] = get_term_link( $query );
815 - $arg['target'] = '_self';
816 - $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
1167 + ob_start();
1168 + do_action( 'rtsb/before/cart/button' );
1169 + $content .= ob_get_clean();
817 1170
818 - if ( ! empty( $meta['tag_term'] ) ) {
819 - $arg['tag_term'] = $meta['tag_term'];
1171 + $cart_attr = [
1172 + 'class' => 'rtsb-action-btn ' . $type . '-product tipsy icon-' . $alignment . ' ' . ( empty( $text ) ? 'no-text' : 'has-text' ),
1173 + 'href' => esc_url( $link ),
1174 + 'data-quantity' => '1',
1175 + 'data-product_id' => absint( $id ),
1176 + 'data-id' => absint( $id ),
1177 + ];
1178 +
1179 + if ( empty( $text ) ) {
1180 + $tooltip_mapping = [
1181 + 'simple' => esc_attr__( 'Add to Cart', 'shopbuilder' ),
1182 + 'variable' => esc_attr__( 'Select Options', 'shopbuilder' ),
1183 + 'grouped' => esc_attr__( 'View Products', 'shopbuilder' ),
1184 + 'external' => ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1185 + 'rtsb-gift-card' => esc_attr__( 'Select Gift Card', 'shopbuilder' ),
1186 + ];
1187 +
1188 + $cart_attr['title'] = $tooltip_mapping[ $type ];
820 1189 }
821 1190
822 - if ( ! empty( $meta['custom_link']['url'] ) ) {
823 - $arg['cat_link'] = $meta['custom_link']['url'];
824 - $arg['target'] = $meta['custom_link']['is_external'] ? '_blank' : '_self';
1191 + $content .= '<div class="rtsb-wc-add-to-cart-wrap">';
1192 + if ( 'variable' === $type ) {
1193 + $cart_attr['class'] = $cart_attr['class'] . ' rtsb_vs_add_to_cart ';
825 1194 }
1195 + if ( 'variable' === $type || 'grouped' === $type ) {
1196 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
826 1197
827 - $title = $query->name;
828 - $excerpt = $query->description;
1198 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
829 1199
830 - if ( in_array( 'badges', $meta['visibility'], true ) ) {
831 - $arg['p_sale'] = $meta['custom_badge_text'];
1200 + $text_btn = apply_filters(
1201 + 'rtsb/elementor/render/add_to_cart_text',
1202 + 'grouped' === $type ? __( 'View Products', 'shopbuilder' ) : __( 'Select Options', 'shopbuilder' ),
1203 + $product,
1204 + $type
1205 + );
1206 +
1207 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1208 + $content .= $icon_html;
1209 + $content .= '<span class="button-text text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '" data-cart-text="' . ( ! empty( $text ) ? esc_attr( $text ) : $tooltip_mapping['simple'] ) . '" data-variable-text="' . esc_attr( $text_btn ) . '">' . esc_html( $text_btn ) . '</span>';
1210 + } elseif ( 'external' === $type ) {
1211 + if ( ! empty( $ext_link ) ) {
1212 + $cart_attr['target'] = '_blank';
1213 + }
1214 +
1215 + $cart_attr['href'] = ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link );
1216 +
1217 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1218 +
1219 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1220 + $content .= $icon_html;
1221 + $content .= '<span class="button-text text">' . ( ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ) ) . '</span>';
1222 + } else {
1223 + $cart_attr['href'] = $cart_attr['href'] . '?add-to-cart=' . absint( $id );
1224 + $cart_attr['class'] .= ' rtsb-add-to-cart-btn';
1225 + $cart_attr['data-type'] = $type;
1226 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
1227 +
1228 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
1229 +
1230 + $content .= '<a ' . $this->get_attribute_string( 'rtsb_add_to_cart_button_' . $id . $rand ) . '>';
1231 + $content .= $icon_html;
1232 + $content .= '<span class="button-text text ' . ( ! empty( $success ) ? 'has-success-text' : 'no-success-text' ) . '" data-success="' . esc_html( $success ) . '">' . esc_html( $text ) . '</span>';
832 1233 }
833 1234
834 - $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
1235 + $content .= '</a>';
1236 + $content .= '</div>';
835 1237
836 - if ( $arg['count'] >= 0 ) {
837 - $prefix = ! empty( $meta['before_count'] ) ? $meta['before_count'] : '';
838 - $suffix = ! empty( $meta['after_count'] ) ? $meta['after_count'] : '';
1238 + ob_start();
1239 + do_action( 'rtsb/after/cart/button' );
1240 + $content .= ob_get_clean();
839 1241
840 - $arg['count'] = $prefix . $arg['count'] . $suffix;
1242 + return apply_filters( 'rtsb/render/cart/button', $content, $args );
1243 + }
1244 +
1245 + /**
1246 + * Button classes.
1247 + *
1248 + * @param array $params Button params.
1249 + *
1250 + * @return array
1251 + */
1252 + public function button_classes( $params ) {
1253 + $params['classes'] = array_merge( $params['classes'], [ 'rtsb-action-btn', 'tipsy' ] );
1254 +
1255 + return $params;
1256 + }
1257 +
1258 + /**
1259 + * Render the section title.
1260 + *
1261 + * @param array $settings The Elementor settings.
1262 + *
1263 + * @return string
1264 + */
1265 + public function render_section_title( $settings ) {
1266 + $html = '';
1267 + $rand = wp_rand();
1268 +
1269 + if ( empty( $settings['show_section_title'] ) || ( empty( $settings['query_products'] ) && 0 === count( $settings['query_products'] ) ) ) {
1270 + return $html;
841 1271 }
842 1272
843 - if ( $meta['show_custom_title'] && ! empty( $meta['cat_custom_title'] ) ) {
844 - $title = $meta['cat_custom_title'];
1273 + // Set attributes for the section title.
1274 + $this->add_attribute( 'rtsb_section_title_wrapper_' . $rand, 'class', 'rtsb-section-title-wrapper' );
1275 + $this->add_attribute( 'rtsb_section_title_' . $rand, 'class', 'rtsb-section-title' );
1276 +
1277 + // Start rendering.
1278 + $html .= '<div ' . $this->get_attribute_string( 'rtsb_section_title_wrapper_' . $rand ) . '>';
1279 + $html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' ' . $this->get_attribute_string( 'rtsb_section_title_' . $rand ) . '>';
1280 + $html .= esc_html( $settings['section_title_text'] );
1281 + $html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
1282 + $html .= '</div>';
1283 +
1284 + return $html;
1285 + }
1286 +
1287 + /**
1288 + * Renders products pagination
1289 + *
1290 + * @param array $args Pagination args.
1291 + * @param string $query_type Query type.
1292 + *
1293 + * @return string
1294 + */
1295 + public function render_products_pagination( $args, $query_type = 'wc' ) {
1296 + list(
1297 + 'query' => $query,
1298 + 'meta' => $meta,
1299 + 'limit' => $limit,
1300 + 'per_page' => $per_page
1301 + ) = $args;
1302 +
1303 + $html_utility = null;
1304 + $html = null;
1305 + $ajax = false;
1306 + $is_grid = preg_match( '/grid-layout/', $meta['layout'] ) || preg_match( '/list-layout/', $meta['layout'] );
1307 + $posts_per_page = 'wp' === $query_type ? $query->query_vars['posts_per_page'] : null;
1308 + $total_page = 'wp' === $query_type ? $query->max_num_pages : $query->get_products()->max_num_pages;
1309 +
1310 + if ( $limit ) {
1311 + if ( 'wc' === $query_type ) {
1312 + $found_post = $query->get_products()->total;
1313 + } elseif ( 'wp' === $query_type && ( empty( $wp_query->query['tax_query'] ) ) ) {
1314 + $found_post = $query->found_posts;
1315 + }
1316 +
1317 + if ( $per_page && $found_post > $per_page ) {
1318 + $found_post = $limit;
1319 + $total_page = ceil( $found_post / $per_page );
1320 + }
845 1321 }
846 1322
847 - if ( $meta['show_custom_excerpt'] && ! empty( $meta['cat_custom_excerpt'] ) ) {
848 - $excerpt = $meta['cat_custom_excerpt'];
1323 + $total_page = absint( $total_page );
1324 +
1325 + $args = [
1326 + 'total_page' => $total_page,
1327 + 'posts_per_page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1328 + 'ajax' => $ajax,
1329 + ];
1330 +
1331 + if ( 'pagination' === $meta['posts_loading_type'] ) {
1332 + if ( $is_grid ) {
1333 + $html_utility .= Fns::custom_pagination(
1334 + $total_page,
1335 + 'wc' === $query_type ? $per_page : $posts_per_page
1336 + );
1337 + }
1338 + } elseif ( 'pagination_ajax' === $meta['posts_loading_type'] && ! rtsb()->has_pro() ) {
1339 + // Free version fallback: render non-AJAX number pagination so filter
1340 + // params in the URL are preserved via get_pagenum_link().
1341 + if ( $is_grid ) {
1342 + $html_utility .= Fns::custom_pagination(
1343 + $total_page,
1344 + 'wc' === $query_type ? $per_page : $posts_per_page
1345 + );
1346 + }
1347 + } else {
1348 + ob_start();
1349 + do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1350 + $html_utility .= ob_get_clean();
849 1351 }
850 1352
851 - $layout = $meta['layout'];
1353 + if ( $html_utility ) {
1354 + $rand = wp_rand();
852 1355
853 - $arg['c_img'] = $meta['c_img'];
854 - $arg['items'] = $meta['visibility'];
855 - $arg['title_tag'] = $meta['title_tag'];
856 - $arg['title_class'] = 'category-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
857 - $arg['title'] = Fns::text_truncation( $title, $meta['title_limit_custom'] );
858 - $arg['excerpt_limit'] = $meta['excerpt_limit'];
859 - $arg['excerpt'] = wpautop( Fns::text_truncation( $excerpt, $meta['excerpt_limit_custom'] ) );
860 - $arg['title_link'] = $meta['title_link'];
861 - $arg['image_link'] = $meta['image_link'];
1356 + // Adding pagination render attributes.
1357 + $this->add_attribute(
1358 + 'rtsb_pagination_attr_' . $rand,
1359 + [
1360 + 'class' => 'rtsb-pagination-wrap element-loading',
1361 + 'data-total-pages' => $total_page,
1362 + 'data-posts-per-page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1363 + 'data-type' => $meta['posts_loading_type'],
1364 + ]
1365 + );
862 1366
863 - if ( ! $fullwidth ) {
864 - $arg['grid'] .= 'rtsb-col-grid ';
865 - $arg['class'] .= ' even-grid-item ';
866 - } else {
867 - $arg['grid'] .= 'rtsb-col-grid ';
1367 + // Start rendering.
1368 + $html = '<div ' . $this->get_attribute_string( 'rtsb_pagination_attr_' . $rand ) . '>';
1369 + $html .= $html_utility;
1370 + $html .= '</div><!-- .rtsb-pagination-wrap -->';
868 1371 }
869 1372
870 - $arg['class'] .= 'rtsb-category-grid';
1373 + return $html;
1374 + }
871 1375
872 - $arg['img_html'] = $meta['c_img'] ? Fns::get_product_image_html(
873 - 'category',
874 - $query->term_id,
875 - $meta['f_img_size'],
876 - $meta['default_img_id'],
877 - $meta['custom_img_size'],
878 - $lazy_load
879 - ) : null;
1376 + /**
1377 + * Renders slider buttons
1378 + *
1379 + * @param array $settings Settings array.
1380 + *
1381 + * @return string
1382 + */
1383 + public function render_slider_buttons( $settings ) {
1384 + $html = '';
1385 + $left_icon = $settings['left_arrow_icon'];
1386 + $right_icon = $settings['right_arrow_icon'];
880 1387
881 - if ( $meta['show_custom_image'] && ! empty( $meta['custom_image'] ) ) {
882 - $arg['img_html'] = Fns::get_product_image_html(
883 - 'custom',
884 - null,
885 - $meta['f_img_size'],
886 - $meta['custom_image'],
887 - $meta['custom_img_size'],
888 - $lazy_load
889 - );
1388 + if ( ! empty( $settings['slider_nav'] ) ) {
1389 + $html .=
1390 + '<div class="swiper-nav">
1391 + <div class="swiper-arrow swiper-button-next">
1392 + ' . Fns::icons_manager( $right_icon ) . '
1393 + </div>
1394 + <div class="swiper-arrow swiper-button-prev">
1395 + ' . Fns::icons_manager( $left_icon ) . '
1396 + </div>
1397 + </div>';
890 1398 }
891 1399
892 - $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' : '' );
1400 + $html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
893 1401
894 - return $arg;
1402 + return $html;
895 1403 }
896 1404
897 1405 /**
898 - * Renders add to cart button.
1406 + * No products message.
899 1407 *
900 - * @param string $link Product link.
901 - * @param int $id Product ID.
902 - * @param string $type Product type.
903 - * @param string $text Custom text.
904 - * @param string $success Success text.
905 - * @param string $icon_html Custom icon.
906 - * @param string $alignment Icon alignment.
1408 + * @param array $metas Meta data.
907 1409 *
1410 + * @return void
1411 + */
1412 + public function no_products_msg( $metas ) {
1413 + $content =
1414 + '<div class="rtsb-notice woocommerce-no-products-found">
1415 + <div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div>
1416 + </div>';
1417 +
1418 + $this->row( $content, $metas );
1419 + }
1420 +
1421 + /**
1422 + * Start the rendering.
1423 + *
1424 + * @param array $metas Meta Data.
1425 + * @param array $settings Widget settings.
1426 + * @param array $temp_args Additional temporary arguments for rendering.
1427 + *
908 1428 * @return string
909 1429 */
910 - public function render_add_to_cart_button( $link, $id, $type, $text, $success, $icon_html, $alignment ) {
911 - $content = null;
912 - $tooltip_attr = '';
913 - $ext_link = get_post_meta( $id, '_product_url', true );
914 - $btn_txt = get_post_meta( $id, '_button_text', true );
1430 + public function render_start( $metas, $settings, $temp_args ) {
1431 + ob_start();
915 1432
916 - if ( empty( $text ) ) {
917 - $tooltip_attr = ' title="' . esc_attr__( 'Add to Cart', 'shopbuilder' ) . '"';
1433 + /**
1434 + * Before render row hook.
1435 + */
1436 + do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $temp_args, $this );
918 1437
919 - if ( 'variable' === $type ) {
920 - $tooltip_attr = ' title="' . esc_attr__( 'Select Options', 'shopbuilder' ) . '"';
1438 + return ob_get_clean();
1439 + }
1440 +
1441 + /**
1442 + * End the rendering.
1443 + *
1444 + * @param array $metas Meta Data.
1445 + * @param array $settings Widget settings.
1446 + * @param array $products Array of products being rendered.
1447 + *
1448 + * @return string
1449 + */
1450 + public function render_end( $metas, $settings, $products ) {
1451 + ob_start();
1452 +
1453 + /**
1454 + * After render row hook.
1455 + */
1456 + do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $products );
1457 +
1458 + return ob_get_clean();
1459 + }
1460 +
1461 + /**
1462 + * Add render attribute.
1463 + *
1464 + * @param array|string $element The HTML element.
1465 + * @param array|string $key Optional. Attribute key. Default is null.
1466 + * @param array|string $value Optional. Attribute value. Default is null.
1467 + * @param bool $overwrite Optional. Whether to overwrite existing.
1468 + *
1469 + * @return self
1470 + */
1471 + public function add_attribute( $element, $key = null, $value = null, $overwrite = false ) {
1472 + if ( is_array( $element ) ) {
1473 + foreach ( $element as $element_key => $attributes ) {
1474 + $this->add_attribute( $element_key, $attributes, null, $overwrite );
921 1475 }
922 1476
923 - if ( 'grouped' === $type ) {
924 - $tooltip_attr = ' title="' . esc_attr__( 'View Products', 'shopbuilder' ) . '"';
1477 + return $this;
1478 + }
1479 +
1480 + if ( is_array( $key ) ) {
1481 + foreach ( $key as $attribute_key => $attributes ) {
1482 + $this->add_attribute( $element, $attribute_key, $attributes, $overwrite );
925 1483 }
926 1484
927 - if ( 'external' === $type ) {
928 - $tooltip_txt = ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' );
929 - $tooltip_attr = ' title="' . esc_attr( $tooltip_txt ) . '"';
930 - }
1485 + return $this;
931 1486 }
932 1487
933 - ob_start();
934 - do_action( 'rtsb/before/cart/button' );
935 - $content .= ob_get_clean();
1488 + if ( empty( $this->attributes[ $element ][ $key ] ) ) {
1489 + $this->attributes[ $element ][ $key ] = [];
1490 + }
936 1491
937 - if ( 'variable' === $type ) {
938 - $content .= sprintf(
939 - '<div class="rtsb-wc-add-to-cart-wrap">
940 - <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>
941 - </div>',
942 - esc_url( $link ),
943 - esc_html__( 'Select Options', 'shopbuilder' ),
944 - absint( $id ),
945 - esc_attr( $alignment ),
946 - empty( $text ) ? 'no-text' : 'has-text',
947 - $tooltip_attr,
948 - $icon_html,
949 - );
950 - } elseif ( 'grouped' === $type ) {
951 - $content .= sprintf(
952 - '<div class="rtsb-wc-add-to-cart-wrap">
953 - <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>
954 - </div>',
955 - esc_url( $link ),
956 - esc_html__( 'View Products', 'shopbuilder' ),
957 - absint( $id ),
958 - esc_attr( $alignment ),
959 - empty( $text ) ? 'no-text' : 'has-text',
960 - $tooltip_attr,
961 - $icon_html,
962 - );
963 - } elseif ( 'external' === $type ) {
964 - $content .= sprintf(
965 - '<div class="rtsb-wc-add-to-cart-wrap">
966 - <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>
967 - </div>',
968 - ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link ),
969 - ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
970 - absint( $id ),
971 - esc_attr( $alignment ),
972 - empty( $text ) ? 'no-text' : 'has-text',
973 - $tooltip_attr,
974 - $icon_html,
975 - ! empty( $ext_link ) ? ' target="_blank"' : ''
976 - );
1492 + settype( $value, 'array' );
1493 +
1494 + if ( $overwrite ) {
1495 + $this->attributes[ $element ][ $key ] = $value;
977 1496 } else {
978 - $content .= sprintf(
979 - '<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>',
980 - esc_url( $link ),
981 - absint( $id ),
982 - esc_attr( $type ),
983 - $icon_html,
984 - esc_html( $text ),
985 - esc_attr( $alignment ),
986 - empty( $text ) ? 'no-text' : 'has-text',
987 - $tooltip_attr,
988 - esc_html( $success ),
989 - ! empty( $success ) ? 'has-success-text' : 'no-success-text',
990 - );
1497 + $this->attributes[ $element ][ $key ] = array_merge( $this->attributes[ $element ][ $key ], $value );
991 1498 }
992 1499
993 - ob_start();
994 - do_action( 'rtsb/after/cart/button' );
995 - $content .= ob_get_clean();
1500 + return $this;
1501 + }
996 1502
997 - return $content;
1503 + /**
1504 + * Add link render attributes.
1505 + *
1506 + * @param array|string $element The HTML element.
1507 + * @param array $url_control Array of link settings.
1508 + * @param bool $overwrite Optional. Whether to overwrite existing
1509 + * attribute. Default is false, not to overwrite.
1510 + *
1511 + * @return Render
1512 + */
1513 + public function add_link_attributes( $element, array $url_control, $overwrite = false ) {
1514 + $attributes = [];
1515 +
1516 + if ( ! empty( $url_control['url'] ) ) {
1517 + $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] );
1518 +
1519 + $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols );
1520 + }
1521 +
1522 + if ( ! empty( $url_control['is_external'] ) ) {
1523 + $attributes['target'] = '_blank';
1524 + }
1525 +
1526 + if ( ! empty( $url_control['nofollow'] ) ) {
1527 + $attributes['rel'] = 'nofollow';
1528 + }
1529 +
1530 + if ( ! empty( $url_control['custom_attributes'] ) ) {
1531 + $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) );
1532 + }
1533 +
1534 + if ( $attributes ) {
1535 + $this->add_attribute( $element, $attributes, null, $overwrite );
1536 + }
1537 +
1538 + return $this;
998 1539 }
999 1540
1000 1541 /**
1001 - * Button classes.
1542 + * Get render attribute string.
1002 1543 *
1003 - * @param array $params Button params.
1544 + * @param string $element The element.
1004 1545 *
1005 - * @return array
1546 + * @return string
1006 1547 */
1007 - public function button_classes( $params ) {
1008 - $params['classes'] = array_merge( $params['classes'], [ 'rtsb-action-btn', 'tipsy' ] );
1548 + public function get_attribute_string( $element ) {
1549 + if ( empty( $this->attributes[ $element ] ) ) {
1550 + return '';
1551 + }
1009 1552
1010 - return $params;
1553 + if ( class_exists( '\Elementor\Utils' ) ) {
1554 + return Utils::render_html_attributes( $this->attributes[ $element ] );
1555 + }
1556 +
1557 + // Fallback.
1558 + return $this->render_fallback_attributes( $this->attributes[ $element ] );
1559 + }
1560 +
1561 + /**
1562 + * Function to render link attributes.
1563 + *
1564 + * @param string $id Element ID.
1565 + * @param array $control_name Control name.
1566 + * @param string $class_name Class name.
1567 + *
1568 + * @return string
1569 + */
1570 + public function render_link_attributes( $id, $control_name, $class_name = 'logo-img-link' ) {
1571 + $this->add_attribute( $id, 'class', $class_name );
1572 +
1573 + if ( ! empty( $control_name['url'] ) ) {
1574 + $this->add_link_attributes( $id, $control_name );
1575 + } else {
1576 + $this->add_attribute( $id, 'role', 'button' );
1577 + }
1578 +
1579 + return $this->get_attribute_string( $id );
1580 + }
1581 +
1582 + /**
1583 + * Render fallback attributes.
1584 + *
1585 + * @param array $attributes The attributes.
1586 + *
1587 + * @return string
1588 + */
1589 + private function render_fallback_attributes( $attributes ) {
1590 + $rendered_attributes = [];
1591 +
1592 + foreach ( $attributes as $attribute_key => $attribute_values ) {
1593 + if ( is_array( $attribute_values ) ) {
1594 + $attribute_values = implode( ' ', $attribute_values );
1595 + }
1596 +
1597 + $rendered_attributes[] = sprintf( '%1$s="%2$s"', $attribute_key, esc_attr( $attribute_values ) );
1598 + }
1599 +
1600 + return implode( ' ', $rendered_attributes );
1011 1601 }
1012 1602 }