PluginProbe
UiCore Elements – Free widgets and templates for Elementor / 1.3.17
UiCore Elements – Free widgets and templates for Elementor v1.3.17
1.3.17 1.3.16 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 All 41 releases
← All changes | includes/utils/product-component.php +150 -134 1.2.21.3.17 View file →
@@ -1,5 +1,6 @@
1 1 <?php
2 +
2 3 namespace UiCoreElements\Utils;
3 4
4 5 use Elementor\Icons_Manager;
5 6
@@ -16,9 +17,10 @@
16 17 *
17 18 * @since 1.0.11
18 19 */
19 20
20 -trait Product_Trait {
21 +trait Product_Trait
22 +{
21 23
22 24 use Post_Trait,
23 25 Meta_Trait;
24 26
@@ -28,19 +30,18 @@
28 30 * @param array $settings Elementor controls settings.
29 31 * @param array|WP_Query $default_query The default query variables.
30 32 *
31 33 * @return array|stdClass Return the products data, prepared for loop.
32 - */
34 + */
33 35 function TRAIT_query_products($settings, $default_query)
34 36 {
35 37 $post_type = $settings['product-filter_post_type'];
36 38
37 - switch ($post_type){
38 - case 'current' :
39 + switch ($post_type) {
40 + case 'current':
39 41 // Makes sure widget will render some products at editor screen
40 - if( $this->is_edit_mode() ){
42 + if ($this->is_edit_mode()) {
41 43 $query_args['post_type'] = 'product';
42 -
43 44 } else {
44 45 $query_args = $default_query;
45 46
46 47 // Set pagination
@@ -54,29 +55,26 @@
54 55 // Set products per page
55 56 $query_args['limit'] = Helper::get_framework_visible_posts('product');
56 57 break;
57 58
58 - case 'related' :
59 - return Helper::get_product_related($settings['item_limit']['size']);
59 + case 'related':
60 + $current_product_id = $settings['__current_post_id'] ?? [];
61 + $total_products = Query::get_posts_per_page('product', $settings);
62 + $query_args['limit'] = $total_products;
63 + $query_args['posts__in'] = Helper::get_product_related($total_products, $current_product_id);
60 64 break;
61 65
62 - default :
66 + default:
63 67 $query_args = Query::get_query_args('product-filter', $settings, true);
64 68 break;
65 69 }
66 70
67 - // Set total pages
71 + $query_args['status'] = 'publish';
68 72 $query_args['total_pages'] = Query::get_total_pages($query_args);
69 73
70 74 // Hide out of stock products
71 - if( $this->is_option('hide_out_of_stock', 'yes') ){
72 - $query_args['meta_query'] = [
73 - [
74 - 'key' => '_stock_status',
75 - 'value' => 'instock',
76 - 'compare' => '=',
77 - ]
78 - ];
75 + if ($this->is_option('hide_out_of_stock', 'yes')) {
76 + $query_args['stock_status'] = 'instock';
79 77 }
80 78
81 79 $this->_query = $query_args;
82 80
@@ -86,9 +84,9 @@
86 84
87 85 // Render functions
88 86 function get_product_image(object $product)
89 87 {
90 - if ( $this->is_option('image', 'yes', '!==') ) {
88 + if ($this->is_option('image', 'yes', '!==')) {
91 89 return;
92 90 }
93 91
94 92 // Get product image ID
@@ -98,42 +96,42 @@
98 96 }
99 97
100 98 // Get image size
101 99 $size = $this->get_settings_for_display('image_size_select') ?? 'uicore-medium';
102 - ?>
103 - <a class="ui-e-post-img-wrapp"
104 - href="<?php echo esc_url($product->get_permalink()); ?>"
105 - title="<?php echo esc_attr__('View Product:', 'uicore-elements') . ' ' . esc_attr($product->get_name()); ?>">
100 +?>
101 + <a class="ui-e-post-img-wrapp"
102 + href="<?php echo esc_url($product->get_permalink()); ?>"
103 + title="<?php echo esc_attr__('View Product:', 'uicore-elements') . ' ' . esc_attr($product->get_name()); ?>">
106 104
107 - <?php if ($this->get_settings_for_display('masonry') === 'ui-e-maso') { ?>
108 - <?php
109 - // Get secondary image if available
110 - $sec_img = $this->get_product_secondary_image( $product );
111 - $classes = $sec_img ? 'ui-e-post-img ui-e-hover-hide' : 'ui-e-post-img';
105 + <?php if ($this->get_settings_for_display('masonry') === 'ui-e-maso') { ?>
106 + <?php
107 + // Get secondary image if available
108 + $sec_img = $this->get_product_secondary_image($product);
109 + $classes = $sec_img ? 'ui-e-post-img ui-e-hover-hide' : 'ui-e-post-img';
112 110
113 - // Print secondary image markup if exists
114 - echo $sec_img ? wp_kses_post($sec_img) : '';
111 + // Print secondary image markup if exists
112 + echo $sec_img ? wp_kses_post($sec_img) : '';
115 113
116 - // Print main product image
117 - echo wp_get_attachment_image($pic_id, $size, false, ['class' => $classes]);
118 - ?>
119 - <?php } else { ?>
120 - <?php
121 - // Get secondary image with size
122 - $sec_img = $this->get_product_secondary_image( $product, $size, true );
123 - echo $sec_img ? wp_kses_post($sec_img) : '';
124 - ?>
125 - <div class="ui-e-post-img <?php echo $sec_img ? 'ui-e-hover-hide' : ''; ?>"
126 - style="background-image:url(<?php echo esc_url( wp_get_attachment_image_url($pic_id, $size)); ?>)">
127 - </div>
128 - <?php } ?>
129 - </a>
130 - <?php
114 + // Print main product image
115 + echo wp_get_attachment_image($pic_id, $size, false, ['class' => $classes]);
116 + ?>
117 + <?php } else { ?>
118 + <?php
119 + // Get secondary image with size
120 + $sec_img = $this->get_product_secondary_image($product, $size, true);
121 + echo $sec_img ? wp_kses_post($sec_img) : '';
122 + ?>
123 + <div class="ui-e-post-img <?php echo $sec_img ? 'ui-e-hover-hide' : ''; ?>"
124 + style="background-image:url(<?php echo esc_url(wp_get_attachment_image_url($pic_id, $size)); ?>)">
125 + </div>
126 + <?php } ?>
127 + </a>
128 + <?php
131 129 }
132 130 function get_product_secondary_image($product, $size = 'woocommerce_thumbnail', $bg_output = false)
133 131 {
134 132 // Requested only for products with change-image animation
135 - if( 'ui-e-img-anim-change' !== $this->get_settings_for_display('anim_image') ) {
133 + if ('ui-e-img-anim-change' !== $this->get_settings_for_display('anim_image')) {
136 134 return false;
137 135 }
138 136
139 137 // Get product gallery image IDs
@@ -159,25 +157,27 @@
159 157 'class' => 'ui-e-post-img ui-e-post-img-secondary'
160 158 ]);
161 159 }
162 160 }
163 - function get_product_title($product)
161 + function get_product_title($product, $tag)
164 162 {
165 - if ( $this->is_option('title', 'yes', '!==') ) {
163 + if ($this->is_option('title', 'yes', '!==')) {
166 164 return;
167 165 }
168 166
169 - ?>
170 - <a href="<?php echo esc_url($product->get_permalink()); ?>"
171 - title="<?php echo esc_attr__('View Product:', 'uicore-elements') . ' ' . esc_html($product->get_name()); ?>">
172 - <h4 class="ui-e-post-title"><span><?php echo esc_html($product->get_name()); ?></span></h4>
173 - </a>
174 - <?php
167 + ?>
168 + <a href="<?php echo esc_url($product->get_permalink()); ?>"
169 + title="<?php echo esc_attr__('View Product:', 'uicore-elements') . ' ' . esc_html($product->get_name()); ?>">
170 + <<?php echo Helper::esc_tag($tag); ?> class="ui-e-post-title">
171 + <span> <?php echo esc_html($product->get_name()); ?> </span>
172 + </<?php echo Helper::esc_tag($tag); ?>>
173 + </a>
174 + <?php
175 175 }
176 176
177 177 function get_product_summary($product, $length = 55)
178 178 {
179 - if ( ! $this->get_settings_for_display('excerpt') ) {
179 + if (! $this->get_settings_for_display('excerpt')) {
180 180 return;
181 181 }
182 182
183 183 $summary = $product->get_short_description();
@@ -182,18 +182,18 @@
182 182
183 183 $summary = $product->get_short_description();
184 184
185 185 // Get product description if no summary
186 - if( empty($summary) ) {
186 + if (empty($summary)) {
187 187 $summary = $product->get_description();
188 188 }
189 189
190 190 // returns if no description also
191 - if( empty($summary) ) {
191 + if (empty($summary)) {
192 192 return;
193 193 }
194 194
195 - ?>
195 + ?>
196 196 <div class="ui-e-post-text">
197 197 <?php echo wp_kses_post(wp_trim_words($summary, $length)); ?>
198 198 </div>
199 199 <?php
@@ -216,17 +216,17 @@
216 216 $can_purchase = $product->is_purchasable();
217 217 $button_classes = 'elementor-button ui-e-readmore ';
218 218
219 219 // Get purchase button text
220 - if($can_purchase){
220 + if ($can_purchase) {
221 221
222 222 // Direct add to cart text
223 - if($product->is_type('simple')){
223 + if ($product->is_type('simple')) {
224 224 $button_text = empty($settings['text'])
225 225 ? $product->add_to_cart_text()
226 226 : $settings['text'];
227 227
228 - // Select options text
228 + // Select options text
229 229 } else {
230 230 $button_text = empty($settings['variations_text'])
231 231 ? $product->add_to_cart_text()
232 232 : $settings['variations_text'];
@@ -231,9 +231,9 @@
231 231 ? $product->add_to_cart_text()
232 232 : $settings['variations_text'];
233 233 }
234 234
235 - // Unpurchasable product text
235 + // Unpurchasable product text
236 236 } else {
237 237 $button_text = empty($settings['unavailable_text'])
238 238 ? $product->add_to_cart_text()
239 239 : $settings['unavailable_text'];
@@ -239,16 +239,16 @@
239 239 : $settings['unavailable_text'];
240 240 }
241 241
242 242 // Render att slugs
243 - $button_slug = 'button_'.$index;
244 - $content_slug = 'content-wrapper_'.$index;
245 - $icon_slug = 'icon_'.$index;
246 - $text_slug = 'text_'.$index;
243 + $button_slug = 'button_' . $index;
244 + $content_slug = 'content-wrapper_' . $index;
245 + $icon_slug = 'icon_' . $index;
246 + $text_slug = 'text_' . $index;
247 247
248 248 // Only simple products should be ajax added to cart for UX purposes.
249 249 // Update button classes and adds all atts used by woo on product ajax add to cart.
250 - if( $can_purchase && $product->is_type('simple') ){
250 + if ($can_purchase && $product->is_type('simple')) {
251 251
252 252 $button_classes .= 'button product_type_simple add_to_cart_button ajax_add_to_cart';
253 253
254 254 $this->add_render_attribute($button_slug, [
@@ -255,10 +255,10 @@
255 255 'data-quantity' => '1',
256 256 'aria-describedby' => 'woocommerce_loop_add_to_cart_link_describedby_' . $product->get_id(),
257 257 'data-product_id' => $product->get_id(),
258 258 'data-product_sku' => $product->get_sku(),
259 - 'aria-label' => sprintf( __('Add to cart: “%s”', 'uicore-elements'), $product->get_name() ),
260 - 'data-success-message' => sprintf( __('“%s” has been added to your cart', 'uicore-elements'), $product->get_name() ),
259 + 'aria-label' => sprintf(__('Add to cart: “%s”', 'uicore-elements'), $product->get_name()),
260 + 'data-success-message' => sprintf(__('“%s” has been added to your cart', 'uicore-elements'), $product->get_name()),
261 261 ]);
262 262 }
263 263
264 264 // Button and wrapper classes
@@ -269,9 +269,9 @@
269 269 $this->add_render_attribute($button_slug, 'class', 'elementor-animation-' . $settings['hover_animation']);
270 270 }
271 271
272 272 $btn_classes = isset($settings['icon_align'])
273 - ? ['elementor-button-icon', 'elementor-align-icon-' . $settings['icon_align'] ]
273 + ? ['elementor-button-icon', 'elementor-align-icon-' . $settings['icon_align']]
274 274 : 'elementor-button-icon';
275 275
276 276 // Button text and icon classes
277 277 $this->add_render_attribute([
@@ -277,15 +277,15 @@
277 277 $this->add_render_attribute([
278 278 $icon_slug => [
279 279 'class' => $btn_classes
280 280 ],
281 - $text_slug => [
281 + $text_slug => [
282 282 'class' => 'elementor-button-text',
283 283 ],
284 284 ]);
285 285
286 286 // Icon alignment
287 - if( isset($settings['icon_align']) ){
287 + if (isset($settings['icon_align'])) {
288 288 $this->add_render_attribute($content_slug, 'class', 'elementor-button-content');
289 289 }
290 290
291 291 $tbn_content = '<span ' . $this->get_render_attribute_string($content_slug) . '>';
@@ -297,9 +297,9 @@
297 297 $tbn_content .= \ob_get_clean();
298 298 $tbn_content .= '</span>';
299 299 }
300 300
301 - $tbn_content .= '<span ' . $this->get_render_attribute_string( $text_slug) . '>';
301 + $tbn_content .= '<span ' . $this->get_render_attribute_string($text_slug) . '>';
302 302 $tbn_content .= $button_text;
303 303 $tbn_content .= '</span> </span>';
304 304
305 305 // WooCommerce add to cart button
@@ -313,11 +313,11 @@
313 313 $this->get_render_attribute_string($button_slug),
314 314 //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
315 315 Helper::esc_svg($tbn_content)
316 316 ),
317 - wp_kses_post($product)
317 + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
318 + $product
318 319 ));
319 -
320 320 }
321 321
322 322 /**
323 323 * Renders a product item with various settings and options.
@@ -334,18 +334,31 @@
334 334 function TRAIT_render_product($product, $index, $carousel = false, $legacy = false, $is_ajax = false)
335 335 {
336 336 $settings = $this->get_settings_for_display();
337 337
338 + $product_id = is_object($product)
339 + ? $product->get_id()
340 + : null;
341 +
338 342 // Classnames but checking if we the widget is APG (legacy version)
339 - $item_classes = $legacy ? ['ui-e-post-item', 'ui-e-item'] : ['ui-e-item'] ; // Single item lower wrap class receptor
343 + $item_classes = $legacy ? ['ui-e-post-item', 'ui-e-item'] : ['ui-e-item']; // Single item lower wrap class receptor
340 344 $hover_item_class = $legacy ? 'anim_item' : 'item_hover_animation'; // item hover animation class
341 345
346 + // Build content atts
347 + $content_tag = 'div';
348 + $this->add_render_attribute('content_' . $index, 'class', ['ui-e-post-content']);
349 +
350 + // Global link
351 + if ($this->is_option('global_link', 'yes')) {
352 + $content_tag = 'a';
353 + $this->add_render_attribute('content_' . $index, 'href', esc_url(get_permalink()));
354 + }
355 +
342 356 // If widget is not carousel type, we set animations classes directly on item selector
343 - if(!$carousel) {
357 + if (!$carousel) {
344 358 $item_classes[] = 'ui-e-animations-wrp';
345 359 $item_classes[] = $settings['animate_items'] === 'ui-e-grid-animate' ? 'elementor-invisible' : '';
346 360 $item_classes[] = $settings[$hover_item_class] !== '' ? $settings[$hover_item_class] : '';
347 -
348 361 } else {
349 362 // Get entrance and item hover animation classes
350 363 $entrance = $this->is_option('animate_items', 'ui-e-grid-animate') ? 'elementor-invisible' : '';
351 364 $hover = isset($settings[$hover_item_class]) ? $settings[$hover_item_class] : '';
@@ -351,76 +364,79 @@
351 364 $hover = isset($settings[$hover_item_class]) ? $settings[$hover_item_class] : '';
352 365 $animations = sprintf('%s %s', $entrance, $hover);
353 366
354 367 // Check if entrance or hover animation are set
355 - $has_animation = !empty($entrance) || !empty($hover)
368 + $has_animation = !empty($entrance) || !empty($hover);
356 369
357 370 // Prints extra wrappers required by the carousel script
358 - ?>
371 + ?>
359 372 <div class="ui-e-wrp swiper-slide">
360 - <?php if($has_animation) : ?>
361 - <div class="ui-e-animations-wrp <?php echo esc_attr($animations);?>">
362 - <?php endif; ?>
373 + <?php if ($has_animation) : ?>
374 + <div class="ui-e-animations-wrp <?php echo esc_attr($animations); ?>">
375 + <?php endif; ?>
363 376
364 - <?php } ?>
377 + <?php } ?>
365 378
366 - <div class="<?php echo esc_attr( implode(' ', $item_classes));?>">
367 - <article <?php post_class('product'); ?>>
368 - <div class="ui-e-post-top">
369 - <?php $this->get_product_image($product); ?>
370 - <?php $this->get_post_meta('top'); ?>
371 - <?php
372 - if($settings['button_position'] === 'ui-e-button-placement-image') {
379 + <div class="<?php echo esc_attr(implode(' ', $item_classes)); ?>">
380 + <article <?php post_class('product'); ?>>
381 + <div class="ui-e-post-top">
382 + <?php $this->get_product_image($product); ?>
383 + <?php $this->get_post_meta('top', $product_id); ?>
384 + <?php
385 + if ($settings['button_position'] === 'ui-e-button-placement-image') {
373 386 $this->get_purchase_button($index);
374 387 }
375 - ?>
376 - </div>
377 - <div class="ui-e-post-content">
378 - <?php $this->get_post_meta('before_title'); ?>
379 - <?php $this->get_product_title($product);?>
380 - <?php $this->get_post_meta('after_title'); ?>
381 - <?php $this->get_product_summary($product, $settings['excerpt_trim']); ?>
382 - <?php $this->get_post_meta('bottom'); // button
383 - ?>
384 - <?php
385 - if( defined('UICORE_VERSION') && version_compare(UICORE_VERSION, '6.0.1', '>=') && $this->is_option('show_swatches', 'yes') ){
386 - // ajax requests works under minimal conditions, wich means Swatches class is not present
387 - if ( $is_ajax ) {
388 - require_once UICORE_INCLUDES . '/woocommerce/components/class-swatches.php';
389 - }
388 + ?>
389 + </div>
390 + <<?php echo Helper::esc_tag($content_tag, 'div', true); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
391 + $this->print_render_attribute_string('content_' . $index); ?>>
390 392
391 - // In guterberg we might experience an `Class not found` error, so we insist in checking it even
392 - // though we just required the file. For more see https://uicore.atlassian.net/browse/ELM-432
393 - if ( class_exists('\UiCore\WooCommerce\Swatches') ) {
394 - \UiCore\WooCommerce\Swatches::print_swatches($product);
393 + <?php $this->get_post_meta('before_title', $product_id); ?>
394 + <?php $this->get_product_title($product, $settings['title_tag']); ?>
395 + <?php $this->get_post_meta('after_title', $product_id); ?>
396 + <?php $this->get_product_summary($product, $settings['excerpt_trim']); ?>
397 + <?php $this->get_post_meta('bottom', $product_id); // button
398 + ?>
399 + <?php
400 + if (defined('UICORE_VERSION') && version_compare(UICORE_VERSION, '6.0.1', '>=') && $this->is_option('show_swatches', 'yes')) {
401 + // ajax requests works under minimal conditions, wich means Swatches class is not present
402 + if ($is_ajax) {
403 + require_once UICORE_INCLUDES . '/woocommerce/components/class-swatches.php';
404 + }
405 +
406 + // In guterberg we might experience an `Class not found` error, so we insist in checking it even
407 + // though we just required the file. For more see https://uicore.atlassian.net/browse/ELM-432
408 + if (class_exists('\UiCore\WooCommerce\Swatches')) {
409 + \UiCore\WooCommerce\Swatches::print_swatches($product);
410 + }
395 411 }
396 - }
397 - if ( $this->is_option('show_button', 'yes') ) {
398 - // Add match height spacing element if carousel. May look intrusive, but is the simplest method compared
399 - // to absolute positioning or catching last content element to apply margin.
400 - if($carousel && $this->is_option('match_height', 'yes') ){
401 - ?>
412 + if ($this->is_option('show_button', 'yes')) {
413 + // Add match height spacing element if carousel. May look intrusive, but is the simplest method compared
414 + // to absolute positioning or catching last content element to apply margin.
415 + if ($carousel && $this->is_option('match_height', 'yes')) {
416 + ?>
402 417 <span class="ui-e-match-height"></span>
403 - <?php
418 + <?php
419 + }
420 +
421 + if (!isset($settings['button_position']) || empty($settings['button_position'])) {
422 + $this->get_purchase_button($index);
423 + }
404 424 }
425 + ?>
426 + </<?php echo Helper::esc_tag($content_tag, 'div'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
427 + ?>>
428 + </article>
429 + </div>
405 430
406 - if( !isset($settings['button_position']) || empty($settings['button_position']) ) {
407 - $this->get_purchase_button($index);
408 - }
409 - }
410 - ?>
431 + <?php if ($carousel) { ?>
411 432 </div>
412 - </article>
433 + <?php if ($has_animation) : ?>
413 434 </div>
435 + <?php endif; ?>
414 436
415 - <?php if($carousel) { ?>
416 - </div>
417 - <?php if($has_animation) : ?>
418 - </div>
419 - <?php endif; ?>
437 +<?php }
438 + }
420 439
421 - <?php }
422 - }
423 -
424 - // keep it to avoid fatal error
425 - public function content_template() {}
426 -}
440 + // keep it to avoid fatal error
441 + public function content_template() {}
442 + }