PluginProbe
UiCore Elements – Free widgets and templates for Elementor / 1.3.18
UiCore Elements – Free widgets and templates for Elementor v1.3.18
1.3.18 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 All 42 releases
← All changes | includes/utils/product-component.php +266 -123 1.2.0 → 1.3.18 View file →
@@ -1,7 +1,10 @@
1 1 <?php
2 +
2 3 namespace UiCoreElements\Utils;
3 4
5 +use Elementor\Icons_Manager;
6 +
4 7 use UiCoreElements\Helper;
5 8 use UiCoreElements\Controls\Query;
6 9
7 10 use UiCoreElements\Utils\Meta_Trait;
@@ -14,9 +17,10 @@
14 17 *
15 18 * @since 1.0.11
16 19 */
17 20
18 -trait Product_Trait {
21 +trait Product_Trait
22 +{
19 23
20 24 use Post_Trait,
21 25 Meta_Trait;
22 26
@@ -26,19 +30,18 @@
26 30 * @param array $settings Elementor controls settings.
27 31 * @param array|WP_Query $default_query The default query variables.
28 32 *
29 33 * @return array|stdClass Return the products data, prepared for loop.
30 - */
34 + */
31 35 function TRAIT_query_products($settings, $default_query)
32 36 {
33 - $post_type = $settings['product-filter_post_type'];
37 + $post_type = $settings['product-filter_post_type'];
34 38
35 - switch ($post_type){
36 - case 'current' :
39 + switch ($post_type) {
40 + case 'current':
37 41 // Makes sure widget will render some products at editor screen
38 - if( $this->is_edit_mode() ){
42 + if ($this->is_edit_mode()) {
39 43 $query_args['post_type'] = 'product';
40 -
41 44 } else {
42 45 $query_args = $default_query;
43 46
44 47 // Set pagination
@@ -47,31 +50,31 @@
47 50 // Set tax filters for filter component, if enabled
48 51 $queried_filters = Query::get_queried_filters($settings, 'product', 'product-filter');
49 52 $query_args['tax_query'] = empty($queried_filters['tax_query']) ? [] : $queried_filters['tax_query'];
50 53 }
54 +
55 + // Set products per page
56 + $query_args['limit'] = Helper::get_framework_visible_posts('product');
51 57 break;
52 58
53 - case 'related' :
54 - 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);
55 64 break;
56 65
57 - default :
66 + default:
58 67 $query_args = Query::get_query_args('product-filter', $settings, true);
59 68 break;
60 69 }
61 70
62 - // Set total pages
71 + $query_args['status'] = 'publish';
63 72 $query_args['total_pages'] = Query::get_total_pages($query_args);
64 73
65 74 // Hide out of stock products
66 - if( $settings['hide_out_of_stock'] === 'yes' ){
67 - $query_args['meta_query'] = [
68 - [
69 - 'key' => '_stock_status',
70 - 'value' => 'instock',
71 - 'compare' => '=',
72 - ]
73 - ];
75 + if ($this->is_option('hide_out_of_stock', 'yes')) {
76 + $query_args['stock_status'] = 'instock';
74 77 }
75 78
76 79 $this->_query = $query_args;
77 80
@@ -81,9 +84,9 @@
81 84
82 85 // Render functions
83 86 function get_product_image(object $product)
84 87 {
85 - if ($this->get_settings_for_display('image') !== 'yes') {
88 + if ($this->is_option('image', 'yes', '!==')) {
86 89 return;
87 90 }
88 91
89 92 // Get product image ID
@@ -93,42 +96,42 @@
93 96 }
94 97
95 98 // Get image size
96 99 $size = $this->get_settings_for_display('image_size_select') ?? 'uicore-medium';
97 - ?>
98 - <a class="ui-e-post-img-wrapp"
99 - href="<?php echo esc_url($product->get_permalink()); ?>"
100 - 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()); ?>">
101 104
102 - <?php if ($this->get_settings_for_display('masonry') === 'ui-e-maso') { ?>
103 - <?php
104 - // Get secondary image if available
105 - $sec_img = $this->get_product_secondary_image( $product );
106 - $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';
107 110
108 - // Print secondary image markup if exists
109 - echo $sec_img ? wp_kses_post($sec_img) : '';
111 + // Print secondary image markup if exists
112 + echo $sec_img ? wp_kses_post($sec_img) : '';
110 113
111 - // Print main product image
112 - echo wp_get_attachment_image($pic_id, $size, false, ['class' => $classes]);
113 - ?>
114 - <?php } else { ?>
115 - <?php
116 - // Get secondary image with size
117 - $sec_img = $this->get_product_secondary_image( $product, $size, true );
118 - echo $sec_img ? wp_kses_post($sec_img) : '';
119 - ?>
120 - <div class="ui-e-post-img <?php echo $sec_img ? 'ui-e-hover-hide' : ''; ?>"
121 - style="background-image:url(<?php echo esc_url( wp_get_attachment_image_url($pic_id, $size)); ?>)">
122 - </div>
123 - <?php } ?>
124 - </a>
125 - <?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
126 129 }
127 130 function get_product_secondary_image($product, $size = 'woocommerce_thumbnail', $bg_output = false)
128 131 {
129 132 // Requested only for products with change-image animation
130 - 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')) {
131 134 return false;
132 135 }
133 136
134 137 // Get product gallery image IDs
@@ -154,25 +157,27 @@
154 157 'class' => 'ui-e-post-img ui-e-post-img-secondary'
155 158 ]);
156 159 }
157 160 }
158 - function get_product_title($product)
161 + function get_product_title($product, $tag)
159 162 {
160 - if ($this->get_settings_for_display('title') !== 'yes') {
163 + if ($this->is_option('title', 'yes', '!==')) {
161 164 return;
162 165 }
163 166
164 - ?>
165 - <a href="<?php echo esc_url($product->get_permalink()); ?>"
166 - title="<?php echo esc_attr__('View Product:', 'uicore-elements') . ' ' . esc_html($product->get_name()); ?>">
167 - <h4 class="ui-e-post-title"><span><?php echo esc_html($product->get_name()); ?></span></h4>
168 - </a>
169 - <?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
170 175 }
171 176
172 177 function get_product_summary($product, $length = 55)
173 178 {
174 - if ( ! $this->get_settings_for_display('excerpt') ) {
179 + if (! $this->get_settings_for_display('excerpt')) {
175 180 return;
176 181 }
177 182
178 183 $summary = $product->get_short_description();
@@ -177,18 +182,18 @@
177 182
178 183 $summary = $product->get_short_description();
179 184
180 185 // Get product description if no summary
181 - if( empty($summary) ) {
186 + if (empty($summary)) {
182 187 $summary = $product->get_description();
183 188 }
184 189
185 190 // returns if no description also
186 - if( empty($summary) ) {
191 + if (empty($summary)) {
187 192 return;
188 193 }
189 194
190 - ?>
195 + ?>
191 196 <div class="ui-e-post-text">
192 197 <?php echo wp_kses_post(wp_trim_words($summary, $length)); ?>
193 198 </div>
194 199 <?php
@@ -194,12 +199,133 @@
194 199 <?php
195 200 }
196 201
197 202 /**
203 + * Render the product purchase button. Based on `TRAIT_get_button()` method.
204 + *
205 + * @param int $index The loop index.
206 + * @param bool $is_product. If true, will render an add to cart button.
207 + *
208 + * @return void
209 + * @since 1.2.1
210 + */
211 + function get_purchase_button($index)
212 + {
213 + global $product;
214 + $settings = $this->get_settings_for_display();
215 +
216 + $can_purchase = $product->is_purchasable();
217 + $button_classes = 'elementor-button ui-e-readmore ';
218 +
219 + // Get purchase button text
220 + if ($can_purchase) {
221 +
222 + // Direct add to cart text
223 + if ($product->is_type('simple')) {
224 + $button_text = empty($settings['text'])
225 + ? $product->add_to_cart_text()
226 + : $settings['text'];
227 +
228 + // Select options text
229 + } else {
230 + $button_text = empty($settings['variations_text'])
231 + ? $product->add_to_cart_text()
232 + : $settings['variations_text'];
233 + }
234 +
235 + // Unpurchasable product text
236 + } else {
237 + $button_text = empty($settings['unavailable_text'])
238 + ? $product->add_to_cart_text()
239 + : $settings['unavailable_text'];
240 + }
241 +
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;
247 +
248 + // Only simple products should be ajax added to cart for UX purposes.
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')) {
251 +
252 + $button_classes .= 'button product_type_simple add_to_cart_button ajax_add_to_cart';
253 +
254 + $this->add_render_attribute($button_slug, [
255 + 'data-quantity' => '1',
256 + 'aria-describedby' => 'woocommerce_loop_add_to_cart_link_describedby_' . $product->get_id(),
257 + 'data-product_id' => $product->get_id(),
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()),
261 + ]);
262 + }
263 +
264 + // Button and wrapper classes
265 + $this->add_render_attribute($button_slug, 'class', $button_classes);
266 + $this->add_render_attribute($content_slug, 'class', 'elementor-button-content-wrapper');
267 +
268 + if (!empty($settings['hover_animation'])) {
269 + $this->add_render_attribute($button_slug, 'class', 'elementor-animation-' . $settings['hover_animation']);
270 + }
271 +
272 + $btn_classes = isset($settings['icon_align'])
273 + ? ['elementor-button-icon', 'elementor-align-icon-' . $settings['icon_align']]
274 + : 'elementor-button-icon';
275 +
276 + // Button text and icon classes
277 + $this->add_render_attribute([
278 + $icon_slug => [
279 + 'class' => $btn_classes
280 + ],
281 + $text_slug => [
282 + 'class' => 'elementor-button-text',
283 + ],
284 + ]);
285 +
286 + // Icon alignment
287 + if (isset($settings['icon_align'])) {
288 + $this->add_render_attribute($content_slug, 'class', 'elementor-button-content');
289 + }
290 +
291 + $tbn_content = '<span ' . $this->get_render_attribute_string($content_slug) . '>';
292 +
293 + if (!empty($settings['icon']) || !empty($settings['selected_icon']['value'])) {
294 + $tbn_content .= '<span ' . $this->get_render_attribute_string($icon_slug) . '>';
295 + \ob_start();
296 + Icons_Manager::render_icon($settings['selected_icon'], ['aria-hidden' => 'true']);
297 + $tbn_content .= \ob_get_clean();
298 + $tbn_content .= '</span>';
299 + }
300 +
301 + $tbn_content .= '<span ' . $this->get_render_attribute_string($text_slug) . '>';
302 + $tbn_content .= $button_text;
303 + $tbn_content .= '</span> </span>';
304 +
305 + // WooCommerce add to cart button
306 + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
307 + echo Helper::esc_svg(apply_filters(
308 + 'woocommerce_loop_add_to_cart_link', // WPCS: XSS ok.
309 + sprintf(
310 + '<a href="%1$s" %2$s> %3$s </a>',
311 + esc_url($product->add_to_cart_url()),
312 + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
313 + $this->get_render_attribute_string($button_slug),
314 + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
315 + Helper::esc_svg($tbn_content)
316 + ),
317 + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
318 + $product
319 + ));
320 + }
321 +
322 + /**
198 323 * Renders a product item with various settings and options.
199 324 * Important: any changes here should also be considered to `TRAIT_render_item()` from Post Component.
200 325 *
201 326 * @param WC_Product $product The product object.
327 + * @param int $index The loop index.
202 328 * @param bool $carousel Indicates if the item needs carousel classnames.
203 329 * @param bool $legacy Indicates if the item is using legacy classnames.
204 330 * @param bool $is_ajax Indicates if the item is being rendered through REST API.
205 331 *
@@ -204,96 +330,113 @@
204 330 * @param bool $is_ajax Indicates if the item is being rendered through REST API.
205 331 *
206 332 * @return void
207 333 */
208 - function TRAIT_render_product($product, $carousel = false, $legacy = false, $is_ajax = false)
334 + function TRAIT_render_product($product, $index, $carousel = false, $legacy = false, $is_ajax = false)
209 335 {
210 - $settings = $this->get_settings_for_display();
336 + $settings = $this->get_settings_for_display();
211 337
338 + $product_id = is_object($product)
339 + ? $product->get_id()
340 + : null;
341 +
212 342 // Classnames but checking if we the widget is APG (legacy version)
213 - $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
214 344 $hover_item_class = $legacy ? 'anim_item' : 'item_hover_animation'; // item hover animation class
215 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 +
216 356 // If widget is not carousel type, we set animations classes directly on item selector
217 - if(!$carousel) {
357 + if (!$carousel) {
218 358 $item_classes[] = 'ui-e-animations-wrp';
219 359 $item_classes[] = $settings['animate_items'] === 'ui-e-grid-animate' ? 'elementor-invisible' : '';
220 360 $item_classes[] = $settings[$hover_item_class] !== '' ? $settings[$hover_item_class] : '';
221 -
222 361 } else {
223 362 // Get entrance and item hover animation classes
224 - $entrance = (isset($settings['animate_items']) && $settings['animate_items'] == 'ui-e-grid-animate') ? 'elementor-invisible' : '';
225 - $hover = isset($settings[$hover_item_class]) ? $settings[$hover_item_class] : ''; //$settings[$hover_item_class] : null;
363 + $entrance = $this->is_option('animate_items', 'ui-e-grid-animate') ? 'elementor-invisible' : '';
364 + $hover = isset($settings[$hover_item_class]) ? $settings[$hover_item_class] : '';
226 365 $animations = sprintf('%s %s', $entrance, $hover);
227 366
228 367 // Check if entrance or hover animation are set
229 - $has_animation = !empty($entrance) || !empty($hover)
368 + $has_animation = !empty($entrance) || !empty($hover);
230 369
231 370 // Prints extra wrappers required by the carousel script
232 - ?>
371 + ?>
233 372 <div class="ui-e-wrp swiper-slide">
234 - <?php if($has_animation) : ?>
235 - <div class="ui-e-animations-wrp <?php echo esc_attr($animations);?>">
236 - <?php endif; ?>
373 + <?php if ($has_animation) : ?>
374 + <div class="ui-e-animations-wrp <?php echo esc_attr($animations); ?>">
375 + <?php endif; ?>
237 376
238 - <?php } ?>
377 + <?php } ?>
239 378
240 - <div class="<?php echo esc_attr( implode(' ', $item_classes));?>">
241 - <article <?php post_class('product'); ?>>
242 - <div class="ui-e-post-top">
243 - <?php $this->get_product_image($product); ?>
244 - <?php $this->get_post_meta('top'); ?>
245 - <?php
246 - if($settings['button_position'] === 'ui-e-button-placement-image') {
247 - $this->TRAIT_get_button(true);
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') {
386 + $this->get_purchase_button($index);
248 387 }
249 - ?>
250 - </div>
251 - <div class="ui-e-post-content">
252 - <?php $this->get_post_meta('before_title'); ?>
253 - <?php
254 - if ($this->get_settings_for_display('title') === 'yes')
255 - $this->get_product_title($product);
256 - ?>
257 - <?php $this->get_post_meta('after_title'); ?>
258 - <?php $this->get_product_summary($product, $settings['excerpt_trim']); ?>
259 - <?php $this->get_post_meta('bottom'); // button
260 - ?>
261 - <?php
262 - if( defined('UICORE_VERSION') && version_compare(UICORE_VERSION, '6.0.1', '>=') && $this->get_settings_for_display('show_swatches') === 'yes' ){
263 - // ajax requests works under minimal conditions, wich means Swatches class is not present
264 - if ( $is_ajax ) {
265 - require_once UICORE_INCLUDES . '/woocommerce/components/class-swatches.php';
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); ?>>
392 +
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 + }
266 411 }
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 + ?>
417 + <span class="ui-e-match-height"></span>
418 + <?php
419 + }
267 420
268 - \UiCore\WooCommerce\Swatches::print_swatches($product); // from Uicore Framework
269 - }
270 - if ( $this->get_settings_for_display('show_button') === 'yes' ) {
271 - // Add match height spacing element if carousel. May look intrusive, but is the simplest method compared
272 - // to absolute positioning or catching last content element to apply margin.
273 - if($carousel && $settings['match_height'] === 'yes'){
274 - ?>
275 - <span class="ui-e-match-height"></span>
276 - <?php
421 + if (!isset($settings['button_position']) || empty($settings['button_position'])) {
422 + $this->get_purchase_button($index);
423 + }
277 424 }
425 + ?>
426 + </<?php echo Helper::esc_tag($content_tag, 'div'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
427 + ?>>
428 + </article>
429 + </div>
278 430
279 - if( !isset($settings['button_position']) || empty($settings['button_position']) ) {
280 - $this->TRAIT_get_button(true);
281 - }
282 - }
283 - ?>
431 + <?php if ($carousel) { ?>
284 432 </div>
285 - </article>
433 + <?php if ($has_animation) : ?>
286 434 </div>
435 + <?php endif; ?>
287 436
288 - <?php if($carousel) { ?>
289 - </div>
290 - <?php if($has_animation) : ?>
291 - </div>
292 - <?php endif; ?>
437 +<?php }
438 + }
293 439
294 - <?php }
295 - }
296 -
297 - // keep it to avoid fatal error
298 - public function content_template() {}
299 -}
440 + // keep it to avoid fatal error
441 + public function content_template() {}
442 + }