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 +933 -404 2.1.13.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,10 +8,14 @@
8 8 */
9 9
10 10 namespace RadiusTheme\SB\Elementor\Render;
11 11
12 +use RadiusTheme\SBPRO\Modules\CatalogMode\CatalogModeFns;
13 +use WC_Query;
12 14 use WP_Query;
13 15 use WC_Product;
16 +use Elementor\Utils;
17 +use WC_Product_Query;
14 18 use RadiusTheme\SB\Helpers\Fns;
15 19 use RadiusTheme\SB\Models\QueryArgs;
16 20 use RadiusTheme\SB\Helpers\BuilderFns;
17 21 use RadiusTheme\SB\Traits\SingletonTrait;
@@ -51,8 +55,15 @@
51 55 */
52 56 public $get_settings;
53 57
54 58 /**
59 + * Element attributes.
60 + *
61 + * @var array
62 + */
63 + private $attributes = [];
64 +
65 + /**
55 66 * Get elementor settings.
56 67 *
57 68 * @return array
58 69 */
@@ -67,140 +78,87 @@
67 78 * @param array $settings Settings.
68 79 * @param string $class Class.
69 80 * @param string $template Template name.
70 81 * @param bool $fullwidth Fullwidth check.
71 - * @param bool $ajax_attr Ajax attributes.
82 + * @param bool $ajax Ajax attributes.
72 83 *
73 84 * @return string
74 85 */
75 - public function container( $content, $settings, $class = '', $template = '', $fullwidth = false, $ajax_attr = true ) {
76 - $layout_id = 'rtsb-container-' . absint( wp_rand() );
77 - $container_attr = null;
78 - $cart_txt_class = '';
79 - $container_class = 'rtsb-elementor-container products rtsb-pos-r ' . esc_attr( $class );
80 - $metas = RenderHelpers::meta_dataset( $settings, $template );
86 + public function container( $content, $settings, $class = '', $template = '', $fullwidth = false, $ajax = true ) {
87 + $rand = wp_rand();
88 + $layout_id = 'rtsb-container-' . $rand;
89 + $metas = RenderHelpers::meta_dataset( $settings, $template );
90 + $raw_layout = esc_html( $metas['layout'] );
91 + $layout = RenderHelpers::filter_layout( $raw_layout, $template );
92 + $style_attr = '--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) );
93 + $view_mode = $settings['view_mode'] ?? 'grid';
94 + $view_mode = isset( $_GET['displayview'] ) ? sanitize_text_field( wp_unslash( $_GET['displayview'] ) ) : $view_mode; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
95 + $tooltip_attr = 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ? esc_attr( $metas['tooltip_position_list'] ) : $metas['tooltip_position'];
96 + $has_pro = rtsb()->has_pro();
97 + $has_filters = $has_pro && ! empty( $settings['tax_filter'] );
98 + $grid_style = $settings['grid_style'] ?? 'even';
81 99
82 - $layout = $metas['layout'];
83 - $animation = $metas['hover_animation'];
84 - $animation .= ' gallery-hover-' . ( ! empty( $settings['gallery_hover_animation'] ) ? $settings['gallery_hover_animation'] : 'fade' );
85 -
86 - $is_cat = preg_match( '/category/', $layout );
87 - $is_carousel = preg_match( '/slider/', $layout );
88 -
89 - if ( ! $is_cat ) {
90 - $cart_txt_class = $metas['show_cart_text'] ? ' has-cart-text' : ' no-cart-text';
91 - }
92 -
93 - if ( $is_carousel ) {
94 - $fullwidth = true;
95 - }
96 -
97 - $badge_class = [
98 - 'position' => $metas['badge_position'],
99 - 'alignment' => $metas['badge_alignment'],
100 - ];
101 -
102 - if ( ! rtsb()->has_pro() && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
100 + // Set default layout.
101 + if ( ! $has_pro && ! in_array( $layout, array_keys( Fns::free_layouts() ), true ) ) {
103 102 $layout = RenderHelpers::set_default_layout( $layout );
104 103 }
105 104
106 - $has_tab_filters = rtsb()->has_pro() && ! empty( $settings['tax_filter'] );
107 -
108 - if ( $is_carousel ) {
109 - $pagination_attr = $has_tab_filters ? RenderHelpers::pagination_data( $settings, $template ) : '';
110 - } elseif ( $is_cat ) {
111 - $pagination_attr = '';
112 - } else {
113 - $ajax_pagination = ! empty( $settings['show_pagination'] ) && 'pagination' !== $settings['pagination_type'];
114 - $page = Fns::product_filters_has_ajax( 'shop' ) || Fns::product_filters_has_ajax( 'archive' );
115 - $condition = rtsb()->has_pro() && $ajax_attr && ( $ajax_pagination || $has_tab_filters || $page );
116 - $pagination_attr = $condition ? RenderHelpers::pagination_data( $settings, $template ) : '';
105 + if ( $has_pro && 'masonry' === $grid_style ) {
106 + $style_attr .= '; --rtsb-masonry-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) );
107 + $style_attr .= '; --rtsb-masonry-list-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) );
117 108 }
118 109
119 - if ( ! in_array( 'clear-btn', $metas['visibility'], true ) ) {
120 - $container_class .= ' no-clear-btn';
121 - }
110 + // Container classes.
111 + $container_classes = RenderHelpers::prepare_container_classes( $metas, $settings, $class );
122 112
123 - $container_class .= ! empty( $metas['pagination'] ) ? ' rtsb-has-pagination' : ' rtsb-no-pagination';
124 - $style_attr = ' style="--rtsb-default-columns: ' . esc_attr( RenderHelpers::default_columns( $layout ) ) . '"';
125 - $tooltip_attr = ' data-tooltip-position="' . esc_attr( $metas['tooltip_position'] ) . '"';
113 + // Pagination attributes.
114 + $pagination_attributes = RenderHelpers::prepare_pagination_attr( $layout, $has_filters, $template, $settings, $ajax );
126 115
127 - $view_mode = $settings['view_mode'] ?? 'grid';
116 + // Attributes for the container.
117 + $container_attributes = [
118 + 'id' => $layout_id,
119 + 'class' => $container_classes,
120 + 'style' => apply_filters( 'rtsb/product/container/attr', $style_attr, $settings ),
121 + 'data-layout' => $layout,
122 + 'data-tooltip-position' => $tooltip_attr,
123 + ];
128 124
129 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended
130 - if ( isset( $_GET['displayview'] ) ) {
131 - $view_mode = sanitize_text_field( wp_unslash( $_GET['displayview'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
125 + if ( ! empty( $pagination_attributes ) ) {
126 + $container_attributes['data-rtsb-ajax'] = $pagination_attributes;
132 127 }
133 128
134 - if ( 'list' == $view_mode && ! empty( $metas['tooltip_position_list'] ) ) {
135 - $tooltip_attr = ' data-tooltip-position="' . esc_attr( $metas['tooltip_position_list'] ) . '"';
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';
136 133 }
137 134
138 - $container_attr .= ' data-layout="' . esc_attr( $layout ) . '"' . $style_attr . $tooltip_attr . $pagination_attr;
139 - $container_class .= ' badge-' . esc_attr( $badge_class['position'] ) . ' badge-' . esc_attr( $badge_class['alignment'] ) . esc_attr( $cart_txt_class ) . ' img-hover-' . esc_attr( $animation ) . ' excerpt-' . esc_attr( $metas['cat_excerpt_position'] );
135 + // Adding render attributes.
136 + $this->add_attribute( 'rtsb_container_attr_' . $rand, $container_attributes );
140 137
141 - if ( $metas['show_overlay'] ) {
142 - $container_class .= ' has-overlay';
143 - }
138 + // Start rendering.
139 + $this->html = '<div ' . $this->get_attribute_string( 'rtsb_container_attr_' . $rand ) . '>';
144 140
145 - if ( in_array( 'single-cat', $metas['visibility'], true ) ) {
146 - $container_class .= ' show-single-cat';
147 - }
141 + // Section title.
142 + $this->html .= $this->render_section_title( $settings );
148 143
149 - if ( ! empty( $settings['show_compare_text'] ) ) {
150 - $container_class .= ' has-compare-text';
151 - }
152 -
153 - if ( ! empty( $settings['show_quick_view_text'] ) ) {
154 - $container_class .= ' has-quick-view-text';
155 - }
156 -
157 - if ( ! empty( $settings['show_wishlist_text'] ) ) {
158 - $container_class .= ' has-wishlist-text';
159 - }
160 -
161 - if ( empty( $settings['show_compare_icon'] ) ) {
162 - $container_class .= ' no-compare-icon';
163 - }
164 -
165 - if ( empty( $settings['show_quick_view_icon'] ) ) {
166 - $container_class .= ' no-quick-view-icon';
167 - }
168 -
169 - if ( empty( $settings['show_wishlist_icon'] ) ) {
170 - $container_class .= ' no-wishlist-icon';
171 - }
172 -
173 - if ( empty( $settings['show_quick_checkout_icon'] ) ) {
174 - $container_class .= ' no-quick_checkout-icon';
175 - }
176 - if ( $is_cat && empty( $settings['show_overlay'] ) ) {
177 - $container_class .= ' no-category-bg-overlay';
178 - }
179 -
180 - $container_class = apply_filters( 'rtsb/product/container/class', $container_class, $settings );
181 - $this->html = '<div class="' . esc_attr( $container_class ) . '" id="' . esc_attr( $layout_id ) . '" ' . $container_attr . '>';
182 -
183 - if ( ! empty( $settings['query_products'] ) && count( $settings['query_products'] ) && 'yes' === $settings['show_section_title'] ) {
184 - $this->html .= '<div class="rtsb-section-title-wrapper">';
185 - $this->html .= '<' . esc_attr( $settings['section_title_tag'] ) . ' class="rtsb-section-title" >';
186 - $this->html .= esc_html( $settings['section_title_text'] );
187 - $this->html .= '</' . esc_attr( $settings['section_title_tag'] ) . '>';
188 - $this->html .= '</div>';
189 - }
190 -
144 + // Content.
191 145 $this->html .= $content;
192 146
147 + // End rendering.
193 148 $this->html .= '</div><!-- .rtsb-elementor-container -->';
194 149
195 150 // Action button icon reset.
196 151 $this->action_button_icon_set_default();
197 152
153 + // Script generator.
198 154 $script_generator = [];
199 155 $script_generator['layout'] = $layout_id;
200 - $script_generator['rand'] = absint( wp_rand() );
201 - $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;
202 159
160 + // Register scripts in the wp_footer action.
203 161 add_action(
204 162 'wp_footer',
205 163 static function () use ( $script_generator ) {
206 164 RenderHelpers::register_scripts( $script_generator );
@@ -220,39 +178,42 @@
220 178 *
221 179 * @return string
222 180 */
223 181 public function row( $content, $settings, $hook_html = '', $raw_settings = [] ) {
224 - $masonry_class = null;
225 - $loader_class = ' rtsb-pre-loader';
182 + $raw_layout = esc_html( $settings['layout'] );
183 + $layout = RenderHelpers::filter_layout( $raw_layout );
226 184
227 - $layout = $settings['layout'];
228 - $grid_type = $settings['grid_type'];
229 - $is_carousel = preg_match( '/slider/', $layout );
185 + $rand = wp_rand();
186 + $is_carousel = preg_match( '/slider/', $raw_layout );
230 187
231 - if ( 'even' === $grid_type ) {
232 - $masonry_class = ' rtsb-even';
233 - } elseif ( 'masonry' === $grid_type ) {
234 - $masonry_class = ' rtsb-masonry';
188 + if ( preg_match( '/category/', $raw_layout ) && ( ! empty( $settings['active_cat_slider'] ) ) ) {
189 + $is_carousel = true;
235 190 }
236 191
237 - if ( ! rtsb()->has_pro() || $is_carousel ) {
238 - $masonry_class = ' rtsb-even';
239 - }
192 + // Row classes.
193 + $row_classes = RenderHelpers::prepare_row_classes( $settings );
240 194
241 - $row_start_class = apply_filters( 'rtsb/custom/layout/row/classes', esc_attr( 'rtsb-row rtsb-content-loader element-loading rtsb-' . $layout . $masonry_class . $loader_class ), $settings );
242 - $row_wrapper = '<div data-title="' . esc_html__( 'Loading ...', 'shopbuilder' ) . '" class="' . $row_start_class . '">';
243 - $start_wrapper = apply_filters( 'rtsb/custom/layout/row/start', $row_wrapper, $row_start_class );
244 - if ( ! empty( $settings['show_filter'] ) ) {
245 - $this->html = $hook_html;
246 - $this->html .= $start_wrapper;
247 - } else {
248 - $this->html = $start_wrapper;
249 - }
195 + // Adding render attributes.
196 + $this->add_attribute( 'rtsb_row_attr_' . $rand, 'class', $row_classes );
250 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 +
251 204 if ( $is_carousel ) {
252 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 + );
253 214
254 - $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 ) . '>';
255 216 $this->html .= '<div class="swiper-wrapper">';
256 217 }
257 218
258 219 $this->html .= $content;
@@ -259,32 +220,18 @@
259 220
260 221 if ( $is_carousel ) {
261 222 $this->html .= '</div><!-- .swiper-wrapper -->';
262 223
263 - $left_icon = $settings['left_arrow_icon'];
264 - $right_icon = $settings['right_arrow_icon'];
265 -
266 - if ( ! empty( $settings['slider_nav'] ) ) {
267 - $this->html .=
268 - '<div class="swiper-nav">
269 - <div class="swiper-arrow swiper-button-next">
270 - ' . Fns::icons_manager( $right_icon ) . '
271 - </div>
272 - <div class="swiper-arrow swiper-button-prev">
273 - ' . Fns::icons_manager( $left_icon ) . '
274 - </div>
275 - </div>';
276 - }
277 -
278 - $this->html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
279 -
224 + // Slider buttons.
225 + $this->html .= $this->render_slider_buttons( $settings );
280 226 $this->html .= '</div><!-- .rtsb-carousel-slider -->';
281 227 }
282 228
283 229 $row_end = '</div><!-- .rtsb-row -->';
284 230
285 - $this->html .= apply_filters( 'rtsb/custom/layout/row/end', $row_end );
231 + $this->html .= apply_filters( 'rtsb/elementor/row/end', $row_end );
286 232
233 + // Preloader.
287 234 $this->html .= RenderHelpers::pre_loader( $layout );
288 235
289 236 return $this->html;
290 237 }
@@ -291,8 +238,63 @@
291 238
292 239 /**
293 240 * Product loop.
294 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 + *
295 297 * @param object $query The query.
296 298 * @param array $settings Settings.
297 299 * @param string $template Template.
298 300 *
@@ -297,9 +299,9 @@
297 299 * @param string $template Template.
298 300 *
299 301 * @return string|null
300 302 */
301 - public function product_loop( $query, $settings, $template ) {
303 + public function wc_product_loop( $query, $settings, $template ) {
302 304 $html = null;
303 305
304 306 if ( empty( $query ) ) {
305 307 return null;
@@ -304,19 +306,20 @@
304 306 if ( empty( $query ) ) {
305 307 return null;
306 308 }
307 309
308 - $i = 0;
310 + $i = 0;
311 + $layout = RenderHelpers::filter_layout( esc_html( $settings['layout'] ), $template );
309 312
310 313 while ( $query->have_posts() ) {
311 314 $query->the_post();
312 315 $i++;
313 -
316 + global $product;
314 317 // Loop arg.
315 - $arg = $this->arg_dataset( $settings, get_the_ID(), $settings['lazy_load'], $i );
318 + $arg = $this->arg_dataset( $settings, $product, $settings['lazy_load'], $i );
316 319
317 320 // Get template.
318 - $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
321 + $html .= Fns::load_template( $template . $layout, $arg, true );
319 322 }
320 323
321 324 return $html;
322 325 }
@@ -321,9 +324,9 @@
321 324 return $html;
322 325 }
323 326
324 327 /**
325 - * Product loop.
328 + * Category loop.
326 329 *
327 330 * @param object $query The query.
328 331 * @param array $settings Settings.
329 332 * @param string $template Template.
@@ -333,11 +336,13 @@
333 336 */
334 337 public function category_loop( $query, $settings, $template, $is_single = false ) {
335 338 $html = null;
336 339 $category_loop = false;
340 +
337 341 if ( 'category-layout3' === $settings['raw_settings']['layout'] || 'category-single-layout2' === $settings['raw_settings']['layout'] ) {
338 342 $category_loop = true;
339 343 }
344 +
340 345 if ( $is_single ) {
341 346 // Loop arg.
342 347 $arg = $this->cat_arg_dataset( $settings, $query, true, false, $category_loop );
343 348
@@ -374,42 +379,56 @@
374 379
375 380 // Query Args.
376 381 $args = ( new QueryArgs() )->buildArgs( $metas, 'product', $is_carousel );
377 382
378 - $temp_args = $args;
379 - $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 );
380 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 );
381 398 // Query.
382 - $product_query = new WP_Query( $args );
399 + $product_query = new WC_Product_Query( $args );
383 400
384 - if ( $product_query->have_posts() ) {
385 - // Elementor Render hook before row.
386 - ob_start();
387 - do_action( 'rtsb/elementor/render/before_row', $metas, $settings, $product_query, $temp_args );
388 - $hook_html .= ob_get_clean();
401 + /**
402 + * Product query hook.
403 + */
404 + do_action( 'rtsb/elements/render/product_query', $product_query, $this );
389 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 +
390 412 // Row.
391 - $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 );
392 414
393 415 // Pagination.
394 416 if ( $pagination ) {
395 - $this->html .= RenderHelpers::render_pagination(
396 - $product_query,
397 - $metas,
398 - $settings['posts_limit'],
399 - $settings['pagination_per_page']
400 - );
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 );
401 425 }
402 426
403 - // Elementor Render hook before row.
404 - ob_start();
405 - do_action( 'rtsb/elementor/render/after_row', $metas, $settings, $product_query );
406 - $this->html .= ob_get_clean();
407 -
427 + // End rendering.
428 + $this->html .= $this->render_end( $metas, $settings, $products );
408 429 } else {
409 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div></div>';
410 -
411 - $this->row( $content, $metas );
430 + $this->no_products_msg( $metas );
412 431 }
413 432
414 433 wp_reset_postdata();
415 434
@@ -414,8 +433,15 @@
414 433 wp_reset_postdata();
415 434
416 435 unset( $args['tax_query'] );
417 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 +
418 444 // Container.
419 445 $this->container( $this->html, $settings, 'rtsb-products-container', $template, $is_carousel );
420 446
421 447 return $this->html;
@@ -420,9 +446,8 @@
420 446
421 447 return $this->html;
422 448 }
423 449
424 -
425 450 /**
426 451 * Render Product View.
427 452 *
428 453 * @param string $template Template name.
@@ -434,24 +459,29 @@
434 459 public function wc_loop_for_product_view( $template, $settings, $widget ) {
435 460 global $wp_query, $post;
436 461
437 462 $this->get_settings = $settings;
463 + $rand = wp_rand();
438 464 $metas = RenderHelpers::meta_dataset( $settings, $template, $widget->get_settings_for_display() );
439 465
440 - $settings['rtsb_order'] = ! empty( $wp_query->query_vars['order'] ) ? $wp_query->query_vars['order'] : 'ASC';
441 - $settings['rtsb_orderby'] = ! empty( $wp_query->query_vars['orderby'] ) ? $wp_query->query_vars['orderby'] : 'menu_order';
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';
442 469
443 470 $pagination = $metas['pagination'];
444 471
445 - $page_type = BuilderFns::builder_type( get_the_ID() );
446 - $is_preview = BuilderFns::is_builder_preview() && array_key_exists( $page_type, BuilderFns::builder_page_types() );
447 - $posts_per_page = wc_get_default_products_per_row() * wc_get_default_product_rows_per_page();
448 -
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;
449 479 if ( $is_preview ) {
450 480 $main_query = clone $wp_query;
451 481 $main_post = clone $post;
452 482
453 - $ordering = ( new \WC_Query() )->get_catalog_ordering_args();
483 + $ordering = ( new WC_Query() )->get_catalog_ordering_args();
454 484 $wp_query_args = [
455 485 'post_type' => 'product',
456 486 'posts_per_page' => $posts_per_page,
457 487 ];
@@ -464,61 +494,111 @@
464 494 $wp_query_args['order'] = $ordering['order'];
465 495 }
466 496
467 497 $wp_query = new WP_Query( $wp_query_args ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
498 +
468 499 wc_set_loop_prop( 'total', $wp_query->found_posts );
469 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;
470 515 }
471 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 +
472 525 // column_per_row.
473 526 if ( woocommerce_product_loop() ) {
474 527 if ( wc_get_loop_prop( 'total' ) ) {
475 - if ( Fns::product_has_applied_filters( 'shop' ) || Fns::product_has_applied_filters( 'archive' ) || 'rtsb_builder' === get_post_type( get_the_ID() ) ) {
476 - echo '<div class="rtsb-active-filters-wrapper"></div>';
477 - }
478 528
479 529 // Row.
480 - $this->row( $this->product_loop( $wp_query, $metas, $template ), $metas );
530 + $this->row( $this->wc_product_loop( $wp_query, $metas, $template ), $metas, '', $settings );
481 531 }
482 532
483 533 // Pagination.
484 - $this->html .= '<div class="rtsb-archive-pagination-wrap">';
534 + $this->html .= '<div class="rtsb-archive-pagination-wrap" data-per-page="' . absint( $posts_per_page ) . '">';
485 535
486 536 if ( $pagination ) {
487 - 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', '' ) ) ) {
488 538 if ( $wp_query->max_num_pages > 1 ) {
489 - $this->html .= "
490 - <div class='rtsb-load-more rtsb-pos-r'>
491 - <button data-paged='2' data-max-page='{$wp_query->max_num_pages}'><span>" . esc_html__( 'Load More', 'shopbuilder' ) . "</span></button>
492 - <div class='rtsb-loadmore-loading rtsb-ball-clip-rotate'><div></div></div>
493 - </div>
494 - ";
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 + }
495 570 }
496 571 } else {
497 - $this->html .= RenderHelpers::render_pagination(
498 - $wp_query,
499 - $metas,
500 - wc_get_loop_prop( 'total' ),
501 - $posts_per_page
502 - );
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' );
503 580 }
504 581 }
505 582
506 583 $this->html .= '</div>';
507 584 } else {
508 - $content = '<div class="woocommerce-no-products-found"><div class="woocommerce-info">' . esc_html__( 'No products were found matching your selection.', 'shopbuilder' ) . '</div></div>';
509 -
510 - $this->row( $content, $metas );
585 + $this->no_products_msg( $metas );
511 586 }
512 587
513 - if ( $is_preview ) {
588 + if ( $is_preview || ! empty( $restore_query ) ) {
589 + // phpcs:disable
514 590 $wp_query = $main_query;
515 591 $post = $main_post;
516 592
517 593 wp_reset_query();
518 594 wp_reset_postdata();
595 + // phpcs:enable
519 596 }
520 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 +
521 601 // Container.
522 602 $this->container( $this->html, $settings, 'rtsb-products-container', $template );
523 603
524 604 return $this->html;
@@ -524,12 +604,13 @@
524 604 return $this->html;
525 605 }
526 606
527 607 /**
528 - * Render Product View.
608 + * Loop Setup for Product View.
529 609 *
530 610 * @param string $template Template name.
531 611 * @param array $settings Control settings.
612 + * @param object $widget Widget object.
532 613 *
533 614 * @return string
534 615 */
535 616 public function setup_loop_for_product_view( $template, $settings, $widget ) {
@@ -546,13 +627,13 @@
546 627
547 628 setup_postdata( $GLOBALS['post'] =& $post_object ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, Squiz.PHP.DisallowMultipleAssignments.Found
548 629
549 630 // Loop arg.
550 - $arg = $this->arg_dataset( $metas, $_product->get_id() );
631 + $arg = $this->arg_dataset( $metas, $_product );
551 632 // Get template.
552 633 $html .= Fns::load_template( $template . $settings['layout'], $arg, true );
553 634
554 - $this->row( $html, $metas );
635 + $this->row( $html, $metas, '', $settings );
555 636 }
556 637
557 638 // Container.
558 639 $this->container( $this->html, $settings, 'rtsb-products-container', '', false, false );
@@ -571,8 +652,13 @@
571 652 *
572 653 * @return string
573 654 */
574 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 = '';
575 661 $this->get_settings = $settings;
576 662
577 663 $metas = RenderHelpers::meta_dataset( $settings, $template, $this->get_settings_for_display() );
578 664 $taxonomy = $metas['category_source'];
@@ -580,8 +666,11 @@
580 666
581 667 if ( rtsb()->has_pro() && ( ! empty( $metas['tag_term'] ) ) ) {
582 668 $term = $metas['tag_term'];
583 669 }
670 + if ( rtsb()->has_pro() && ( ! empty( $metas['brand_term'] ) ) ) {
671 + $term = $metas['brand_term'];
672 + }
584 673
585 674 // Query.
586 675 if ( $is_single ) {
587 676 if ( empty( $term ) ) {
@@ -599,9 +688,9 @@
599 688 if ( ! is_wp_error( $taxonomy_query ) && ! empty( $taxonomy_query ) ) {
600 689 // Row.
601 690 $this->row( $this->category_loop( $taxonomy_query, $metas, $template, $is_single ), $metas );
602 691 } else {
603 - $this->html .= '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
692 + $this->html = '<p>' . esc_html__( 'No categories found', 'shopbuilder' ) . '</p>';
604 693 }
605 694
606 695 // Container.
607 696 $this->container( $this->html, $settings, 'rtsb-categories-container', $is_single );
@@ -648,8 +737,34 @@
648 737
649 738 /**
650 739 * Builds an array with meta values.
651 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 + *
652 767 * @param array $meta Meta values.
653 768 * @param int $post_id Post ID.
654 769 * @param bool $lazy_load Lazy Load.
655 770 * @param int $i Integer.
@@ -655,22 +770,184 @@
655 770 * @param int $i Integer.
656 771 *
657 772 * @return array
658 773 */
659 - 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
660 776 if ( ( empty( $meta ) && ! $post_id ) || ! in_array( get_post_type( $post_id ), [ 'product_variation', 'product' ] ) ) {
661 777 return [];
662 778 }
663 779
664 - global $post, $product;
780 + global $product;
665 781
666 782 if ( ! $product instanceof WC_Product && $post_id ) {
667 - $product = wc_get_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.
668 784 }
669 785
670 - $arg = [];
671 - $p_type = $product->get_type();
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 + );
672 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 = [];
673 950 $arg['class'] = null;
674 951 $arg['grid'] = null;
675 952 $arg['anchor_class'] = null;
676 953 $arg['hover_img_html'] = null;
@@ -675,26 +952,28 @@
675 952 $arg['anchor_class'] = null;
676 953 $arg['hover_img_html'] = null;
677 954 $arg['s_link'] = [];
678 955 $arg['add_to_cart'] = '';
956 + $arg['p_counter'] = absint( $i );
679 957 $arg['p_id'] = $post_id;
680 - $arg['p_link'] = get_permalink();
958 + $arg['product'] = $product;
959 + $arg['p_link'] = $product->get_permalink();
681 960 $arg['p_sale'] = '';
682 961 $arg['rating_args'] = [];
683 962 $arg['has_attributes'] = $product->is_type( 'variable' ) && $product->get_attributes();
684 963 $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
685 964 $cart_icon_html = '';
965 + $layout = $meta['layout'];
686 966
687 - $layout = $meta['layout'];
688 -
967 + // Building the arg.
689 968 $arg['target'] = '_self';
690 969 $arg['f_img'] = $meta['f_img'];
691 970 $arg['items'] = $meta['visibility'];
692 971 $arg['title_tag'] = $meta['title_tag'];
693 972 $arg['title_class'] = 'product-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
694 - $arg['title'] = Fns::text_truncation( get_the_title(), $meta['title_limit_custom'] );
973 + $arg['title'] = Fns::text_truncation( $product->get_title(), $meta['title_limit_custom'] );
695 974 $arg['excerpt_limit'] = $meta['excerpt_limit'];
696 - $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'] ) );
697 976 $arg['title_link'] = $meta['title_link'];
698 977 $arg['image_link'] = $meta['image_link'];
699 978 $arg['out_of_stock'] = $meta['out_of_stock_badge_text'];
700 979 $arg['action_btn_preset'] = $meta['btn_preset'];
@@ -706,10 +985,15 @@
706 985 $is_list = preg_match( '/list/', $layout );
707 986 $is_carousel = preg_match( '/slider/', $layout );
708 987
709 988 if ( ! $is_carousel ) {
710 - $arg['grid'] .= 'rtsb-col-grid ';
711 - $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 + }
712 996 } else {
713 997 $arg['grid'] .= 'rtsb-col-grid rtsb-col-full rtsb-slide-item swiper-slide ';
714 998 }
715 999
@@ -729,17 +1013,21 @@
729 1013
730 1014 $arg['class'] .= ' animated rtFadeIn';
731 1015
732 1016 if ( in_array( 'badges', $meta['visibility'], true ) ) {
733 - $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,
734 1020 $meta['sale_badge_type'],
735 1021 $meta['sale_badge_text'],
736 - $meta['out_of_stock_badge_text']
1022 + apply_filters( 'rtsb/elementor/render/out_of_stock_badge_text', $meta['out_of_stock_badge_text'] )
737 1023 );
738 1024 }
739 1025
740 1026 $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
741 1027
1028 + $meta['add_to_cart_text'] = apply_filters( 'rtsb/elementor/render/add_to_cart_text', $meta['add_to_cart_text'], $product );
1029 +
742 1030 if ( ! $meta['show_cart_text'] ) {
743 1031 $meta['add_to_cart_text'] = '';
744 1032 $meta['add_to_cart_success_text'] = '';
745 1033 }
@@ -752,23 +1040,41 @@
752 1040 if ( ! empty( $meta['add_to_cart_success_icon'] ) ) {
753 1041 $cart_icon_html .= Fns::icons_manager( $meta['add_to_cart_success_icon'], 'cart-success-icon' );
754 1042 }
755 1043
756 - $cart_icon = '<span class="icon">' . $cart_icon_html . '</span>';
1044 + $cart_icon = ! empty( $cart_icon_html ) ? '<span class="icon">' . $cart_icon_html . '</span>' : '';
757 1045
758 1046 if ( $product->is_purchasable() || 'grouped' === $p_type || 'external' === $p_type ) {
759 1047 if ( $product->is_in_stock() ) {
760 1048 $arg['add_to_cart'] = $this->render_add_to_cart_button(
761 - get_permalink(),
762 - $post_id,
763 - $p_type,
764 - $meta['add_to_cart_text'],
765 - $meta['add_to_cart_success_text'],
766 - $cart_icon,
767 - $meta['add_to_cart_alignment']
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 + ]
768 1059 );
769 1060 }
770 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 + }
771 1077 }
772 1078
773 1079 if ( in_array( 'quick_view', $meta['visibility'], true ) ) {
774 1080 add_filter( 'rtsb/module/quick_view/button_params', [ $this, 'button_classes' ] );
@@ -831,243 +1137,466 @@
831 1137 'hover_img_html' => $arg['hover_img_html'],
832 1138 'meta' => $meta,
833 1139 ];
834 1140
835 - return apply_filters( 'rtsb/elementor/render/arg_dataset', $arg, $post_id, $meta, $lazy_load );
1141 + return $arg;
836 1142 }
837 -
838 1143 /**
839 - * Builds an array with meta values.
1144 + * Renders add to cart button.
840 1145 *
841 - * @param array $meta Meta values.
842 - * @param Object $query Category object.
843 - * @param bool $fullwidth Fullwidth check.
844 - * @param bool $lazy_load Lazy Load.
1146 + * @param array $args Cart args.
845 1147 *
846 - * @return array
1148 + * @return string
847 1149 */
848 - public function cat_arg_dataset( $meta, $query = null, $fullwidth = false, $lazy_load = false, $category_loop = false ) {
849 - if ( empty( $meta ) ) {
850 - return [];
851 - }
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;
852 1161
853 - $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 );
854 1166
855 - $products = null;
856 - $arg['class'] = null;
857 - $arg['grid'] = null;
858 - $arg['anchor_class'] = null;
859 - $arg['p_sale'] = null;
860 - $arg['count'] = max( $query->count, 0 );
861 - $arg['count_position'] = $meta['count_position'];
862 - $arg['excerpt_position'] = $meta['cat_excerpt_position'];
863 - $arg['cat_link'] = get_term_link( $query );
864 - $arg['target'] = '_self';
865 - $arg['raw_settings'] = ! empty( $meta['raw_settings'] ) ? $meta['raw_settings'] : [];
1167 + ob_start();
1168 + do_action( 'rtsb/before/cart/button' );
1169 + $content .= ob_get_clean();
866 1170
867 - if ( ! empty( $meta['tag_term'] ) ) {
868 - $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 ];
869 1189 }
870 1190
871 - if ( ! empty( $meta['custom_link']['url'] ) ) {
872 - $arg['cat_link'] = $meta['custom_link']['url'];
873 - $arg['target'] = $meta['custom_link']['is_external'] ? '_blank' : '_self';
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 ';
874 1194 }
1195 + if ( 'variable' === $type || 'grouped' === $type ) {
1196 + $cart_attr = apply_filters( 'rtsb/elementor/render/cart_attributes', $cart_attr, $product );
875 1197
876 - $title = $query->name;
877 - $excerpt = $query->description;
1198 + $this->add_attribute( 'rtsb_add_to_cart_button_' . $id . $rand, $cart_attr );
878 1199
879 - if ( in_array( 'badges', $meta['visibility'], true ) ) {
880 - $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>';
881 1233 }
882 1234
883 - $arg['badge_class'] = RenderHelpers::badge_class( $meta['badge_preset'] );
1235 + $content .= '</a>';
1236 + $content .= '</div>';
884 1237
885 - if ( $arg['count'] >= 0 ) {
886 - $prefix = ! empty( $meta['before_count'] ) ? $meta['before_count'] : '';
887 - $suffix = ! empty( $meta['after_count'] ) ? $meta['after_count'] : '';
1238 + ob_start();
1239 + do_action( 'rtsb/after/cart/button' );
1240 + $content .= ob_get_clean();
888 1241
889 - $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;
890 1271 }
891 1272
892 - if ( $meta['show_custom_title'] && ! empty( $meta['cat_custom_title'] ) ) {
893 - $title = $meta['cat_custom_title'];
894 - }
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' );
895 1276
896 - if ( $meta['show_custom_excerpt'] && ! empty( $meta['cat_custom_excerpt'] ) ) {
897 - $excerpt = $meta['cat_custom_excerpt'];
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 + }
898 1321 }
899 1322
900 - $layout = $meta['layout'];
1323 + $total_page = absint( $total_page );
901 1324
902 - $arg['c_img'] = $meta['c_img'];
903 - $arg['items'] = $meta['visibility'];
904 - $arg['title_tag'] = $meta['title_tag'];
905 - $arg['title_class'] = 'category-title rtsb-text-limit limit-' . $meta['title_limit'] . ( ! $meta['title_link'] ? ' no-link' : '' );
906 - $arg['title'] = Fns::text_truncation( $title, $meta['title_limit_custom'] );
907 - $arg['excerpt_limit'] = $meta['excerpt_limit'];
908 - $arg['excerpt'] = wpautop( Fns::text_truncation( $excerpt, $meta['excerpt_limit_custom'] ) );
909 - $arg['title_link'] = $meta['title_link'];
910 - $arg['image_link'] = $meta['image_link'];
1325 + $args = [
1326 + 'total_page' => $total_page,
1327 + 'posts_per_page' => 'wc' === $query_type ? $per_page : $posts_per_page,
1328 + 'ajax' => $ajax,
1329 + ];
911 1330
912 - if ( ! $fullwidth ) {
913 - $arg['grid'] .= 'rtsb-col-grid ';
914 - $arg['class'] .= ' even-grid-item ';
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 + }
915 1347 } else {
916 - $arg['grid'] .= 'rtsb-col-grid ';
1348 + ob_start();
1349 + do_action( 'rtsb/elementor/render/pagination', $meta, $query, $args );
1350 + $html_utility .= ob_get_clean();
917 1351 }
918 1352
919 - $arg['class'] .= 'rtsb-category-grid';
1353 + if ( $html_utility ) {
1354 + $rand = wp_rand();
920 1355
921 - $arg['img_html'] = $meta['c_img'] ? Fns::get_product_image_html(
922 - 'category',
923 - $query->term_id,
924 - $meta['f_img_size'],
925 - $meta['default_img_id'],
926 - $meta['custom_img_size'],
927 - $lazy_load
928 - ) : null;
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 + );
929 1366
930 - if ( $meta['show_custom_image'] && ! empty( $meta['custom_image'] ) ) {
931 - $arg['img_html'] = Fns::get_product_image_html(
932 - 'custom',
933 - null,
934 - $meta['f_img_size'],
935 - $meta['custom_image'],
936 - $meta['custom_img_size'],
937 - $lazy_load
938 - );
1367 + // Start rendering.
1368 + $html = '<div ' . $this->get_attribute_string( 'rtsb_pagination_attr_' . $rand ) . '>';
1369 + $html .= $html_utility;
1370 + $html .= '</div><!-- .rtsb-pagination-wrap -->';
939 1371 }
940 1372
941 - $arg['excerpt_class'] = 'category-info' . ( 'block' === $meta['count_position'] ? ' block-count' : ' inline-count' ) . ( 'above' === $meta['cat_excerpt_position'] ? ' excerpt-above' : '' ) . ( empty( $arg['excerpt'] ) ? ' no-excerpt' : '' );
1373 + return $html;
1374 + }
942 1375
943 - if ( $category_loop ) {
944 - $query_args = [
945 - 'product_category_id' => $query->term_id,
946 - 'limit' => -1,
947 - 'order' => 'DESC',
948 - 'orderby' => 'date',
949 - ];
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'];
950 1387
951 - $products_query = new \WC_Product_Query( $query_args );
952 - $products = $products_query->get_products();
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>';
953 1398 }
954 1399
955 - return apply_filters( 'rtsb/elementor/render/cat_arg_dataset', $arg, $meta, $query, $fullwidth, $lazy_load, $products );
1400 + $html .= ! empty( $settings['slider_dot'] ) ? '<div class="swiper-pagination rtsb-pos-s"></div>' : '';
1401 +
1402 + return $html;
956 1403 }
957 1404
958 1405 /**
959 - * Renders add to cart button.
1406 + * No products message.
960 1407 *
961 - * @param string $link Product link.
962 - * @param int $id Product ID.
963 - * @param string $type Product type.
964 - * @param string $text Custom text.
965 - * @param string $success Success text.
966 - * @param string $icon_html Custom icon.
967 - * @param string $alignment Icon alignment.
1408 + * @param array $metas Meta data.
968 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 + *
969 1428 * @return string
970 1429 */
971 - public function render_add_to_cart_button( $link, $id, $type, $text, $success, $icon_html, $alignment ) {
972 - $content = null;
973 - $tooltip_attr = '';
974 - $ext_link = get_post_meta( $id, '_product_url', true );
975 - $btn_txt = get_post_meta( $id, '_button_text', true );
1430 + public function render_start( $metas, $settings, $temp_args ) {
1431 + ob_start();
976 1432
977 - if ( empty( $text ) ) {
978 - $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 );
979 1437
980 - if ( 'variable' === $type ) {
981 - $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 );
982 1475 }
983 1476
984 - if ( 'grouped' === $type ) {
985 - $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 );
986 1483 }
987 1484
988 - if ( 'external' === $type ) {
989 - $tooltip_txt = ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' );
990 - $tooltip_attr = ' title="' . esc_attr( $tooltip_txt ) . '"';
991 - }
1485 + return $this;
992 1486 }
993 1487
994 - ob_start();
995 - do_action( 'rtsb/before/cart/button' );
996 - $content .= ob_get_clean();
1488 + if ( empty( $this->attributes[ $element ][ $key ] ) ) {
1489 + $this->attributes[ $element ][ $key ] = [];
1490 + }
997 1491
998 - if ( 'variable' === $type ) {
999 - $content .= sprintf(
1000 - '<div class="rtsb-wc-add-to-cart-wrap">
1001 - <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>
1002 - </div>',
1003 - esc_url( $link ),
1004 - esc_html__( 'Select Options', 'shopbuilder' ),
1005 - absint( $id ),
1006 - esc_attr( $alignment ),
1007 - empty( $text ) ? 'no-text' : 'has-text',
1008 - $tooltip_attr,
1009 - $icon_html,
1010 - );
1011 - } elseif ( 'grouped' === $type ) {
1012 - $content .= sprintf(
1013 - '<div class="rtsb-wc-add-to-cart-wrap">
1014 - <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>
1015 - </div>',
1016 - esc_url( $link ),
1017 - esc_html__( 'View Products', 'shopbuilder' ),
1018 - absint( $id ),
1019 - esc_attr( $alignment ),
1020 - empty( $text ) ? 'no-text' : 'has-text',
1021 - $tooltip_attr,
1022 - $icon_html,
1023 - );
1024 - } elseif ( 'external' === $type ) {
1025 - $content .= sprintf(
1026 - '<div class="rtsb-wc-add-to-cart-wrap">
1027 - <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>
1028 - </div>',
1029 - ! empty( $ext_link ) ? esc_url( $ext_link ) : esc_url( $link ),
1030 - ! empty( $btn_txt ) ? esc_html( $btn_txt ) : esc_html__( 'Buy Product', 'shopbuilder' ),
1031 - absint( $id ),
1032 - esc_attr( $alignment ),
1033 - empty( $text ) ? 'no-text' : 'has-text',
1034 - $tooltip_attr,
1035 - $icon_html,
1036 - ! empty( $ext_link ) ? ' target="_blank"' : ''
1037 - );
1492 + settype( $value, 'array' );
1493 +
1494 + if ( $overwrite ) {
1495 + $this->attributes[ $element ][ $key ] = $value;
1038 1496 } else {
1039 - $content .= sprintf(
1040 - '<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>',
1041 - esc_url( $link ),
1042 - absint( $id ),
1043 - esc_attr( $type ),
1044 - $icon_html,
1045 - esc_html( $text ),
1046 - esc_attr( $alignment ),
1047 - empty( $text ) ? 'no-text' : 'has-text',
1048 - $tooltip_attr,
1049 - esc_html( $success ),
1050 - ! empty( $success ) ? 'has-success-text' : 'no-success-text',
1051 - );
1497 + $this->attributes[ $element ][ $key ] = array_merge( $this->attributes[ $element ][ $key ], $value );
1052 1498 }
1053 1499
1054 - ob_start();
1055 - do_action( 'rtsb/after/cart/button' );
1056 - $content .= ob_get_clean();
1500 + return $this;
1501 + }
1057 1502
1058 - 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;
1059 1539 }
1060 1540
1061 1541 /**
1062 - * Button classes.
1542 + * Get render attribute string.
1063 1543 *
1064 - * @param array $params Button params.
1544 + * @param string $element The element.
1065 1545 *
1066 - * @return array
1546 + * @return string
1067 1547 */
1068 - public function button_classes( $params ) {
1069 - $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 + }
1070 1552
1071 - 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 );
1072 1601 }
1073 1602 }