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