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 -366 2.0.23.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,101 +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 - $container_class .= ! empty( $metas['pagination'] ) ? ' rtsb-has-pagination' : ' rtsb-no-pagination';
116 - $style_attr = ' style="--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) ) . '"';
117 - $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 );
118 115
119 - $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 + ];
120 124
121 - if ( isset( $_GET['displayview'] ) ) {
122 - $view_mode = sanitize_text_field( wp_unslash( $_GET['displayview'] ) );
125 + if ( ! empty( $pagination_attributes ) ) {
126 + $container_attributes['data-rtsb-ajax'] = $pagination_attributes;
123 127 }
124 128
125 - if ( 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ) {
126 - $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';
127 133 }
128 134
129 - $container_attr .= ' data-layout="' . esc_attr( $layout ) . '"' . $style_attr . $tooltip_attr . $pagination_attr;
130 - $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 );
131 137
132 - if ( $metas['show_overlay'] ) {
133 - $container_class .= ' has-overlay';
134 - }
138 + // Start rendering.
139 + $this->html = '<div ' . $this->get_attribute_string( 'rtsb_container_attr_' . $rand ) . '>';
135 140
136 - if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
137 - $container_class .= ' show-single-cat';
138 - }
141 + // Section title.
142 + $this->html .= $this->render_section_title( $settings );
139 143
140 - $container_class = apply_filters( 'rtsb/product/container/class', $container_class, $settings );
141 - $this->html = '<div class="' . esc_attr( $container_class ) . '" id="' . esc_attr( $layout_id ) . '" ' . $container_attr . '>';
142 -
143 - if ( ! empty( $settings['query_products'] ) && count( $settings['query_products'] ) && 'yes' === $settings['show_section_title'] ) {
144 - $this->html .= '<div class="rtsb-section-title-wrapper">';
145 - $this->html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' class="rtsb-section-title" >';
146 - $this->html .= esc_html( $settings['section_title_text'] );
147 - $this->html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
148 - $this->html .= '</div>';
149 - }
150 -
144 + // Content.
151 145 $this->html .= $content;
152 146
147 + // End rendering.
153 148 $this->html .= '</div><!-- .rtsb-elementor-container -->';
154 149
155 150 // Action button icon reset.
156 151 $this->action_button_icon_set_default();
157 152
153 + // Script generator.
158 154 $script_generator = [];
159 155 $script_generator['layout'] = $layout_id;
160 - $script_generator['rand'] = absint( wp_rand() );
161 - $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;
162 159
160 + // Register scripts in the wp_footer action.
163 161 add_action(
164 162 'wp_footer',
165 163 static function () use ( $script_generator ) {
166 164 RenderHelpers::register_scripts( $script_generator );
@@ -180,39 +178,42 @@
180 178 *
181 179 * @return string
182 180 */
183 181 public function row( $content, $settings, $hook_html = '', $raw_settings = [] ) {
184 - $masonry_class = null;
185 - $loader_class = ' rtsb-pre-loader';
182 + $raw_layout = esc_html( $settings['layout'] );
183 + $layout = RenderHelpers::filter_layout( $raw_layout );
186 184
187 - $layout = $settings['layout'];
188 - $grid_type = $settings['grid_type'];
189 - $is_carousel = preg_match( '/slider/', $layout );
185 + $rand = wp_rand();
186 + $is_carousel = preg_match( '/slider/', $raw_layout );
190 187
191 - if ( 'even' === $grid_type ) {
192 - $masonry_class = ' rtsb-even';
193 - } elseif ( 'masonry' === $grid_type ) {
194 - $masonry_class = ' rtsb-masonry';
188 + if ( preg_match( '/category/', $raw_layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
189 + $is_carousel = true;
195 190 }
196 191
197 - if ( ! rtsb()->has_pro() || $is_carousel ) {
198 - $masonry_class = ' rtsb-even';
199 - }
192 + // Row classes.
193 + $row_classes = RenderHelpers::prepare_row_classes( $settings );
200 194
201 - $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 );
202 - $row_wrapper = '<div data-title="' . esc_html__( 'Loading ...', 'shopbuilder' ) . '" class="' . $row_start_class . '">';
203 - $start_wrapper = apply_filters( 'rtsb/custom/layout/row/start', $row_wrapper, $row_start_class );
204 - if ( ! empty( $settings['show_filter'] ) ) {
205 - $this->html = $hook_html;
206 - $this->html .= $start_wrapper;
207 - } else {
208 - $this->html = $start_wrapper;
209 - }
195 + // Adding render attributes.
196 + $this->add_attribute( 'rtsb_row_attr_' . $rand, 'class', $row_classes );
210 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 +
211 204 if ( $is_carousel ) {
212 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 + );
213 214
214 - $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 ) . '>';
215 216 $this->html .= '<div class="swiper-wrapper">';
216 217 }
217 218
218 219 $this->html .= $content;
@@ -219,32 +220,18 @@
219 220
220 221 if ( $is_carousel ) {
221 222 $this->html .= '</div><!-- .swiper-wrapper -->';
222 223
223 - $left_icon = $settings['left_arrow_icon'];
224 - $right_icon = $settings['right_arrow_icon'];
225 -
226 - if ( ! empty( $settings['slider_nav'] ) ) {
227 - $this->html .=
228 - '<div class="swiper-nav">
229 - <div class="swiper-arrow swiper-button-next">
230 - ' . Fns::icons_manager( $right_icon ) . '
231 - </div>
232 - <div class="swiper-arrow swiper-button-prev">
233 - ' . Fns::icons_manager( $left_icon ) . '
234 - </div>
235 - </div>';
236 - }
237 -
238 - $this->html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
239 -
224 + // Slider buttons.
225 + $this->html .= $this->render_slider_buttons( $settings );
240 226 $this->html .= '</div><!-- .rtsb-carousel-slider -->';
241 227 }
242 228
243 229 $row_end = '</div><!-- .rtsb-row -->';
244 230
245 - $this->html .= apply_filters( 'rtsb/custom/layout/row/end', $row_end );
231 + $this->html .= apply_filters( 'rtsb/elementor/row/end', $row_end );
246 232
233 + // Preloader.
247 234 $this->html .= RenderHelpers::pre_loader( $layout );
248 235
249 236 return $this->html;
250 237 }
@@ -251,8 +238,63 @@
251 238
252 239 /**
253 240 * Product loop.
254 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 + *
255 297 * @param object $query The query.
256 298 * @param array $settings Settings.
257 299 * @param string $template Template.
258 300 *
@@ -257,9 +299,9 @@
257 299 * @param string $template Template.
258 300 *
259 301 * @return string|null
260 302 */
261 - public function product_loop( $query, $settings, $template ) {
303 + public function wc_product_loop( $query, $settings, $template ) {
262 304 $html = null;
263 305
264 306 if ( empty( $query ) ) {
265 307 return null;
@@ -264,19 +306,20 @@
264 306 if ( empty( $query ) ) {
265 307 return null;
266 308 }
267 309
268 - $i = 0;
310 + $i = 0;
311 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
269 312
270 313 while ( $query->have_posts() ) {
271 314 $query->the_post();
272 315 $i++;
273 -
316 + global $product;
274 317 // Loop arg.
275 - $arg = $this->arg_dataset( $settings, get_the_ID(), $settings['lazy_load'], $i );
318 + $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
276 319
277 320 // Get template.
278 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
321 + $html .= Fns::load_template( $template . $layout, $arg, true );
279 322 }
280 323
281 324 return $html;
282 325 }
@@ -281,9 +324,9 @@
281 324 return $html;
282 325 }
283 326
284 327 /**
285 - * Product loop.
328 + * Category loop.
286 329 *
287 330 * @param object $query The query.
288 331 * @param array $settings Settings.
289 332 * @param string $template Template.
@@ -291,13 +334,18 @@
291 334 *
292 335 * @return string|null
293 336 */
294 337 public function category_loop( $query, $settings, $template, $is_single = false ) {
295 - $html = null;
338 + $html = null;
339 + $category_loop = false;
296 340
341 + if ( 'category-layout3' === $settings['raw_settings']['layout'] || 'category-single-layout2' === $settings['raw_settings']['layout'] ) {
342 + $category_loop = true;
343 + }
344 +
297 345 if ( $is_single ) {
298 346 // Loop arg.
299 - $arg = $this->cat_arg_dataset( $settings, $query, true );
347 + $arg = $this->cat_arg_dataset( $settings, $query, true, false, $category_loop );
300 348
301 349 // Get template.
302 350 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
303 351 } else {
@@ -302,9 +350,9 @@
302 350 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
303 351 } else {
304 352 foreach ( $query as $term ) {
305 353 // Loop arg.
306 - $arg = $this->cat_arg_dataset( $settings, $term );
354 + $arg = $this->cat_arg_dataset( $settings, $term, false, false, $category_loop );
307 355
308 356 // Get template.
309 357 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
310 358 }
@@ -331,42 +379,56 @@
331 379
332 380 // Query Args.
333 381 $args = ( new QueryArgs() )->buildArgs( $metas, 'product', $is_carousel );
334 382
335 - $temp_args = $args;
336 - $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 );
337 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 );
338 398 // Query.
339 - $product_query = new WP_Query( $args );
399 + $product_query = new WC_Product_Query( $args );
340 400
341 - if ( $product_query->have_posts() ) {
342 - // Elementor Render hook before row.
343 - ob_start();
344 - do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $product_query, $temp_args );
345 - $hook_html .= ob_get_clean();
401 + /**
402 + * Product query hook.
403 + */
404 + do_action( 'rtsb/elements/render/product_query', $product_query, $this );
346 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 +
347 412 // Row.
348 - $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 );
349 414
350 415 // Pagination.
351 416 if ( $pagination ) {
352 - $this->html .= RenderHelpers::render_pagination(
353 - $product_query,
354 - $metas,
355 - $settings['posts_limit'],
356 - $settings['pagination_per_page']
357 - );
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 );
358 425 }
359 426
360 - // Elementor Render hook before row.
361 - ob_start();
362 - do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $product_query );
363 - $this->html .= ob_get_clean();
364 -
427 + // End rendering.
428 + $this->html .= $this->render_end( $metas, $settings, $products );
365 429 } else {
366 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder-pro' ) . '</div></div>';
367 -
368 - $this->row( $content, $metas );
430 + $this->no_products_msg( $metas );
369 431 }
370 432
371 433 wp_reset_postdata();
372 434
@@ -371,8 +433,15 @@
371 433 wp_reset_postdata();
372 434
373 435 unset( $args['tax_query'] );
374 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 +
375 444 // Container.
376 445 $this->container( $this->html, $settings, 'rtsb-products-container', $template, $is_carousel );
377 446
378 447 return $this->html;
@@ -377,9 +446,8 @@
377 446
378 447 return $this->html;
379 448 }
380 449
381 -
382 450 /**
383 451 * Render Product View.
384 452 *
385 453 * @param string $template Template name.
@@ -391,24 +459,29 @@
391 459 public function wc_loop_for_product_view( $template, $settings, $widget ) {
392 460 global $wp_query, $post;
393 461
394 462 $this->get_settings = $settings;
463 + $rand = wp_rand();
395 464 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
396 465
397 - $settings['rtsb_order'] = ! empty( $wp_query->query_vars['order'] ) ? $wp_query->query_vars['order'] : 'ASC';
398 - $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';
399 469
400 470 $pagination = $metas['pagination'];
401 471
402 - $page_type = BuilderFns::builder_type( get_the_ID() );
403 - $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
404 - $posts_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
405 -
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;
406 479 if ( $is_preview ) {
407 480 $main_query = clone $wp_query;
408 481 $main_post = clone $post;
409 482
410 - $ordering = ( new \WC_Query() )->get_catalog_ordering_args();
483 + $ordering = ( new WC_Query() )->get_catalog_ordering_args();
411 484 $wp_query_args = [
412 485 'post_type' => 'product',
413 486 'posts_per_page' => $posts_per_page,
414 487 ];
@@ -420,62 +493,112 @@
420 493 if ( ! empty( $ordering['order'] ) ) {
421 494 $wp_query_args['order'] = $ordering['order'];
422 495 }
423 496
424 - $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 +
425 499 wc_set_loop_prop( 'total', $wp_query->found_posts );
426 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;
427 515 }
428 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 +
429 525 // column_per_row.
430 526 if ( woocommerce_product_loop() ) {
431 527 if ( wc_get_loop_prop( 'total' ) ) {
432 - if ( Fns::product_has_applied_filters( 'shop' ) || Fns::product_has_applied_filters( 'archive' ) || 'rtsb_builder' === get_post_type( get_the_ID() ) ) {
433 - echo '<div class="rtsb-active-filters-wrapper"></div>';
434 - }
435 528
436 529 // Row.
437 - $this->row( $this->product_loop( $wp_query, $metas, $template ), $metas );
530 + $this->row( $this->wc_product_loop( $wp_query, $metas, $template ), $metas, '', $settings );
438 531 }
439 532
440 533 // Pagination.
441 - $this->html .= '<div class="rtsb-archive-pagination-wrap">';
534 + $this->html .= '<div class="rtsb-archive-pagination-wrap" data-per-page="' . absint( $posts_per_page ) . '">';
442 535
443 536 if ( $pagination ) {
444 - 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', '' ) ) ) {
445 538 if ( $wp_query->max_num_pages > 1 ) {
446 - $this->html .= "
447 - <div class='rtsb-load-more rtsb-pos-r'>
448 - <button data-paged='2' data-max-page='{$wp_query->max_num_pages}'><span>" . esc_html__( 'Load More', 'shopbuilder-pro' ) . "</span></button>
449 - <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
450 - </div>
451 - ";
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 + }
452 570 }
453 571 } else {
454 - $this->html .= RenderHelpers::render_pagination(
455 - $wp_query,
456 - $metas,
457 - wc_get_loop_prop( 'total' ),
458 - $posts_per_page
459 - );
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' );
460 580 }
461 581 }
462 582
463 583 $this->html .= '</div>';
464 584 } else {
465 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder-pro' ) . '</div></div>';
466 -
467 - $this->row( $content, $metas );
585 + $this->no_products_msg( $metas );
468 586 }
469 587
470 - if ( $is_preview ) {
588 + if ( $is_preview || ! empty( $restore_query ) ) {
589 + // phpcs:disable
471 590 $wp_query = $main_query;
472 591 $post = $main_post;
473 592
474 593 wp_reset_query();
475 594 wp_reset_postdata();
595 + // phpcs:enable
476 596 }
477 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 +
478 601 // Container.
479 602 $this->container( $this->html, $settings, 'rtsb-products-container', $template );
480 603
481 604 return $this->html;
@@ -481,12 +604,13 @@
481 604 return $this->html;
482 605 }
483 606
484 607 /**
485 - * Render Product View.
608 + * Loop Setup for Product View.
486 609 *
487 610 * @param string $template Template name.
488 611 * @param array $settings Control settings.
612 + * @param object $widget Widget object.
489 613 *
490 614 * @return string
491 615 */
492 616 public function setup_loop_for_product_view( $template, $settings, $widget ) {
@@ -503,17 +627,17 @@
503 627
504 628 setup_postdata( $GLOBALS['post'] =& $post_object ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, Squiz.PHP.DisallowMultipleAssignments.Found
505 629
506 630 // Loop arg.
507 - $arg = $this->arg_dataset( $metas, $_product->get_id() );
631 + $arg = $this->arg_dataset( $metas, $_product );
508 632 // Get template.
509 633 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
510 634
511 - $this->row( $html, $metas );
635 + $this->row( $html, $metas, '', $settings );
512 636 }
513 637
514 638 // Container.
515 - $this->container( $this->html, $settings, 'rtsb-products-container' );
639 + $this->container( $this->html, $settings, 'rtsb-products-container', '', false, false );
516 640
517 641 wp_reset_postdata();
518 642
519 643 return $this->html;
@@ -528,8 +652,13 @@
528 652 *
529 653 * @return string
530 654 */
531 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 = '';
532 661 $this->get_settings = $settings;
533 662
534 663 $metas = RenderHelpers::meta_dataset( $settings, $template, $this->get_settings_for_display() );
535 664 $taxonomy = $metas['category_source'];
@@ -537,8 +666,11 @@
537 666
538 667 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
539 668 $term = $metas['tag_term'];
540 669 }
670 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
671 + $term = $metas['brand_term'];
672 + }
541 673
542 674 // Query.
543 675 if ( $is_single ) {
544 676 if ( empty( $term ) ) {
@@ -552,13 +684,13 @@
552 684
553 685 $taxonomy_query = get_terms( $args );
554 686 }
555 687
556 - if ( ! empty( $taxonomy_query ) ) {
688 + if ( ! is_wp_error( $taxonomy_query ) && ! empty( $taxonomy_query ) ) {
557 689 // Row.
558 690 $this->row( $this->category_loop( $taxonomy_query, $metas, $template, $is_single ), $metas );
559 691 } else {
560 - $this->html .= '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
692 + $this->html = '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
561 693 }
562 694
563 695 // Container.
564 696 $this->container( $this->html, $settings, 'rtsb-categories-container', $is_single );
@@ -605,8 +737,34 @@
605 737
606 738 /**
607 739 * Builds an array with meta values.
608 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 + *
609 767 * @param array $meta Meta values.
610 768 * @param int $post_id Post ID.
611 769 * @param bool $lazy_load Lazy Load.
612 770 * @param int $i Integer.
@@ -612,19 +770,184 @@
612 770 * @param int $i Integer.
613 771 *
614 772 * @return array
615 773 */
616 - 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
617 776 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
618 777 return [];
619 778 }
620 779
621 - global $post;
780 + global $product;
622 781
623 - $arg = [];
624 - $_product = wc_get_product( $post_id );
625 - $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 + }
626 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 = [];
627 950 $arg['class'] = null;
628 951 $arg['grid'] = null;
629 952 $arg['anchor_class'] = null;
630 953 $arg['hover_img_html'] = null;
@@ -629,25 +952,28 @@
629 952 $arg['anchor_class'] = null;
630 953 $arg['hover_img_html'] = null;
631 954 $arg['s_link'] = [];
632 955 $arg['add_to_cart'] = '';
956 + $arg['p_counter'] = absint( $i );
633 957 $arg['p_id'] = $post_id;
634 - $arg['p_link'] = get_permalink();
958 + $arg['product'] = $product;
959 + $arg['p_link'] = $product->get_permalink();
635 960 $arg['p_sale'] = '';
636 - $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();
637 963 $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
638 964 $cart_icon_html = '';
965 + $layout = $meta['layout'];
639 966
640 - $layout = $meta['layout'];
641 -
967 + // Building the arg.
642 968 $arg['target'] = '_self';
643 969 $arg['f_img'] = $meta['f_img'];
644 970 $arg['items'] = $meta['visibility'];
645 971 $arg['title_tag'] = $meta['title_tag'];
646 972 $arg['title_class'] = 'product-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
647 - $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'] );
648 974 $arg['excerpt_limit'] = $meta['excerpt_limit'];
649 - $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'] ) );
650 976 $arg['title_link'] = $meta['title_link'];
651 977 $arg['image_link'] = $meta['image_link'];
652 978 $arg['out_of_stock'] = $meta['out_of_stock_badge_text'];
653 979 $arg['action_btn_preset'] = $meta['btn_preset'];
@@ -659,10 +985,15 @@
659 985 $is_list = preg_match( '/list/', $layout );
660 986 $is_carousel = preg_match( '/slider/', $layout );
661 987
662 988 if ( ! $is_carousel ) {
663 - $arg['grid'] .= 'rtsb-col-grid ';
664 - $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 + }
665 996 } else {
666 997 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
667 998 }
668 999
@@ -682,17 +1013,21 @@
682 1013
683 1014 $arg['class'] .= ' animated rtFadeIn';
684 1015
685 1016 if ( in_array( 'badges', $meta['visibility'], true ) ) {
686 - $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,
687 1020 $meta['sale_badge_type'],
688 1021 $meta['sale_badge_text'],
689 - $meta['out_of_stock_badge_text']
1022 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
690 1023 );
691 1024 }
692 1025
693 1026 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
694 1027
1028 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
1029 +
695 1030 if ( ! $meta['show_cart_text'] ) {
696 1031 $meta['add_to_cart_text'] = '';
697 1032 $meta['add_to_cart_success_text'] = '';
698 1033 }
@@ -705,23 +1040,41 @@
705 1040 if ( ! empty( $meta['add_to_cart_success_icon'] ) ) {
706 1041 $cart_icon_html .= Fns::icons_manager( $meta['add_to_cart_success_icon'], 'cart-success-icon' );
707 1042 }
708 1043
709 - $cart_icon = '<span class="icon">' . $cart_icon_html . '</span>';
1044 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
710 1045
711 - if ( $_product->is_purchasable() || 'grouped' === $p_type || 'external' === $p_type ) {
712 - if ( $_product->is_in_stock() ) {
1046 + if ( $product->is_purchasable() || 'grouped' === $p_type || 'external' === $p_type ) {
1047 + if ( $product->is_in_stock() ) {
713 1048 $arg['add_to_cart'] = $this->render_add_to_cart_button(
714 - get_permalink(),
715 - $post_id,
716 - $p_type,
717 - $meta['add_to_cart_text'],
718 - $meta['add_to_cart_success_text'],
719 - $cart_icon,
720 - $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 + ]
721 1059 );
722 1060 }
723 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 + }
724 1077 }
725 1078
726 1079 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
727 1080 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -746,10 +1099,10 @@
746 1099 $meta['custom_img_size'],
747 1100 $lazy_load
748 1101 ) : null;
749 1102
750 - if ( ! ( function_exists( 'rtwpvsp' ) && $_product->is_type( 'variable' ) && $_product->get_attributes() ) ) {
751 - $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();
752 1105
753 1106 if ( $meta['hover_img'] && ! empty( $gallery_ids ) ) {
754 1107 $arg['class'] .= ! $meta['gallery_img'] ? ' rtsb-double-img' : '';
755 1108
@@ -784,230 +1137,466 @@
784 1137 'hover_img_html' => $arg['hover_img_html'],
785 1138 'meta' => $meta,
786 1139 ];
787 1140
788 - return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
1141 + return $arg;
789 1142 }
790 -
791 1143 /**
792 - * Builds an array with meta values.
1144 + * Renders add to cart button.
793 1145 *
794 - * @param array $meta Meta values.
795 - * @param Object $query Category object.
796 - * @param bool $fullwidth Fullwidth check.
797 - * @param bool $lazy_load Lazy Load.
1146 + * @param array $args Cart args.
798 1147 *
799 - * @return array
1148 + * @return string
800 1149 */
801 - public function cat_arg_dataset( $meta, $query = null, $fullwidth = false, $lazy_load = false ) {
802 - if ( empty( $meta ) ) {
803 - return [];
804 - }
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;
805 1161
806 - $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 );
807 1166
808 - $arg['class'] = null;
809 - $arg['grid'] = null;
810 - $arg['anchor_class'] = null;
811 - $arg['p_sale'] = null;
812 - $arg['count'] = max( $query->count, 0 );
813 - $arg['count_position'] = $meta['count_position'];
814 - $arg['excerpt_position'] = $meta['cat_excerpt_position'];
815 - $arg['cat_link'] = get_term_link( $query );
816 - $arg['target'] = '_self';
817 - $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();
818 1170
819 - if ( ! empty( $meta['tag_term'] ) ) {
820 - $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 ];
821 1189 }
822 1190
823 - if ( ! empty( $meta['custom_link']['url'] ) ) {
824 - $arg['cat_link'] = $meta['custom_link']['url'];
825 - $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 ';
826 1194 }
1195 + if ( 'variable' === $type || 'grouped' === $type ) {
1196 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
827 1197
828 - $title = $query->name;
829 - $excerpt = $query->description;
1198 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
830 1199
831 - if ( in_array( 'badges', $meta['visibility'], true ) ) {
832 - $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>';
833 1233 }
834 1234
835 - $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
1235 + $content .= '</a>';
1236 + $content .= '</div>';
836 1237
837 - if ( $arg['count'] >= 0 ) {
838 - $prefix = ! empty( $meta['before_count'] ) ? $meta['before_count'] : '';
839 - $suffix = ! empty( $meta['after_count'] ) ? $meta['after_count'] : '';
1238 + ob_start();
1239 + do_action( 'rtsb/after/cart/button' );
1240 + $content .= ob_get_clean();
840 1241
841 - $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;
842 1271 }
843 1272
844 - if ( $meta['show_custom_title'] && ! empty( $meta['cat_custom_title'] ) ) {
845 - $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 + }
846 1321 }
847 1322
848 - if ( $meta['show_custom_excerpt'] && ! empty( $meta['cat_custom_excerpt'] ) ) {
849 - $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();
850 1351 }
851 1352
852 - $layout = $meta['layout'];
1353 + if ( $html_utility ) {
1354 + $rand = wp_rand();
853 1355
854 - $arg['c_img'] = $meta['c_img'];
855 - $arg['items'] = $meta['visibility'];
856 - $arg['title_tag'] = $meta['title_tag'];
857 - $arg['title_class'] = 'category-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
858 - $arg['title'] = Fns::text_truncation( $title, $meta['title_limit_custom'] );
859 - $arg['excerpt_limit'] = $meta['excerpt_limit'];
860 - $arg['excerpt'] = wpautop( Fns::text_truncation( $excerpt, $meta['excerpt_limit_custom'] ) );
861 - $arg['title_link'] = $meta['title_link'];
862 - $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 + );
863 1366
864 - if ( ! $fullwidth ) {
865 - $arg['grid'] .= 'rtsb-col-grid ';
866 - $arg['class'] .= ' even-grid-item ';
867 - } else {
868 - $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 -->';
869 1371 }
870 1372
871 - $arg['class'] .= 'rtsb-category-grid';
1373 + return $html;
1374 + }
872 1375
873 - $arg['img_html'] = $meta['c_img'] ? Fns::get_product_image_html(
874 - 'category',
875 - $query->term_id,
876 - $meta['f_img_size'],
877 - $meta['default_img_id'],
878 - $meta['custom_img_size'],
879 - $lazy_load
880 - ) : 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'];
881 1387
882 - if ( $meta['show_custom_image'] && ! empty( $meta['custom_image'] ) ) {
883 - $arg['img_html'] = Fns::get_product_image_html(
884 - 'custom',
885 - null,
886 - $meta['f_img_size'],
887 - $meta['custom_image'],
888 - $meta['custom_img_size'],
889 - $lazy_load
890 - );
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>';
891 1398 }
892 1399
893 - $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>' : '';
894 1401
895 - return $arg;
1402 + return $html;
896 1403 }
897 1404
898 1405 /**
899 - * Renders add to cart button.
1406 + * No products message.
900 1407 *
901 - * @param string $link Product link.
902 - * @param int $id Product ID.
903 - * @param string $type Product type.
904 - * @param string $text Custom text.
905 - * @param string $success Success text.
906 - * @param string $icon_html Custom icon.
907 - * @param string $alignment Icon alignment.
1408 + * @param array $metas Meta data.
908 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 + *
909 1428 * @return string
910 1429 */
911 - public function render_add_to_cart_button( $link, $id, $type, $text, $success, $icon_html, $alignment ) {
912 - $content = null;
913 - $tooltip_attr = '';
914 - $ext_link = get_post_meta( $id, '_product_url', true );
915 - $btn_txt = get_post_meta( $id, '_button_text', true );
1430 + public function render_start( $metas, $settings, $temp_args ) {
1431 + ob_start();
916 1432
917 - if ( empty( $text ) ) {
918 - $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 );
919 1437
920 - if ( 'variable' === $type ) {
921 - $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 );
922 1475 }
923 1476
924 - if ( 'grouped' === $type ) {
925 - $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 );
926 1483 }
927 1484
928 - if ( 'external' === $type ) {
929 - $tooltip_txt = ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' );
930 - $tooltip_attr = ' title="' . esc_attr( $tooltip_txt ) . '"';
931 - }
1485 + return $this;
932 1486 }
933 1487
934 - ob_start();
935 - do_action( 'rtsb/before/cart/button' );
936 - $content .= ob_get_clean();
1488 + if ( empty( $this->attributes[ $element ][ $key ] ) ) {
1489 + $this->attributes[ $element ][ $key ] = [];
1490 + }
937 1491
938 - if ( 'variable' === $type ) {
939 - $content .= sprintf(
940 - '<div class="rtsb-wc-add-to-cart-wrap">
941 - <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>
942 - </div>',
943 - esc_url( $link ),
944 - esc_html__( 'Select Options', 'shopbuilder' ),
945 - absint( $id ),
946 - esc_attr( $alignment ),
947 - empty( $text ) ? 'no-text' : 'has-text',
948 - $tooltip_attr,
949 - $icon_html,
950 - );
951 - } elseif ( 'grouped' === $type ) {
952 - $content .= sprintf(
953 - '<div class="rtsb-wc-add-to-cart-wrap">
954 - <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>
955 - </div>',
956 - esc_url( $link ),
957 - esc_html__( 'View Products', 'shopbuilder' ),
958 - absint( $id ),
959 - esc_attr( $alignment ),
960 - empty( $text ) ? 'no-text' : 'has-text',
961 - $tooltip_attr,
962 - $icon_html,
963 - );
964 - } elseif ( 'external' === $type ) {
965 - $content .= sprintf(
966 - '<div class="rtsb-wc-add-to-cart-wrap">
967 - <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>
968 - </div>',
969 - ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link ),
970 - ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
971 - absint( $id ),
972 - esc_attr( $alignment ),
973 - empty( $text ) ? 'no-text' : 'has-text',
974 - $tooltip_attr,
975 - $icon_html,
976 - ! empty( $ext_link ) ? ' target="_blank"' : ''
977 - );
1492 + settype( $value, 'array' );
1493 +
1494 + if ( $overwrite ) {
1495 + $this->attributes[ $element ][ $key ] = $value;
978 1496 } else {
979 - $content .= sprintf(
980 - '<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>',
981 - esc_url( $link ),
982 - absint( $id ),
983 - esc_attr( $type ),
984 - $icon_html,
985 - esc_html( $text ),
986 - esc_attr( $alignment ),
987 - empty( $text ) ? 'no-text' : 'has-text',
988 - $tooltip_attr,
989 - esc_html( $success ),
990 - ! empty( $success ) ? 'has-success-text' : 'no-success-text',
991 - );
1497 + $this->attributes[ $element ][ $key ] = array_merge( $this->attributes[ $element ][ $key ], $value );
992 1498 }
993 1499
994 - ob_start();
995 - do_action( 'rtsb/after/cart/button' );
996 - $content .= ob_get_clean();
1500 + return $this;
1501 + }
997 1502
998 - 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;
999 1539 }
1000 1540
1001 1541 /**
1002 - * Button classes.
1542 + * Get render attribute string.
1003 1543 *
1004 - * @param array $params Button params.
1544 + * @param string $element The element.
1005 1545 *
1006 - * @return array
1546 + * @return string
1007 1547 */
1008 - public function button_classes( $params ) {
1009 - $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 + }
1010 1552
1011 - 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 );
1012 1601 }
1013 1602 }