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
uicore-elements / includes / utils / product-component.php

product-component.php in UiCore Elements – Free widgets and templates for Elementor 1.3.17, at includes/utils/product-component.php

443 lines 17.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UiCoreElements\Utils;
4
5 use Elementor\Icons_Manager;
6
7 use UiCoreElements\Helper;
8 use UiCoreElements\Controls\Query;
9
10 use UiCoreElements\Utils\Meta_Trait;
11 use UiCoreElements\Utils\Post_Trait;
12
13 defined('ABSPATH') || exit();
14
15 /**
16 * Product Component
17 *
18 * @since 1.0.11
19 */
20
21 trait Product_Trait
22 {
23
24 use Post_Trait,
25 Meta_Trait;
26
27 /**
28 * Returns the proper query args for the product loop.
29 *
30 * @param array $settings Elementor controls settings.
31 * @param array|WP_Query $default_query The default query variables.
32 *
33 * @return array|stdClass Return the products data, prepared for loop.
34 */
35 function TRAIT_query_products($settings, $default_query)
36 {
37 $post_type = $settings['product-filter_post_type'];
38
39 switch ($post_type) {
40 case 'current':
41 // Makes sure widget will render some products at editor screen
42 if ($this->is_edit_mode()) {
43 $query_args['post_type'] = 'product';
44 } else {
45 $query_args = $default_query;
46
47 // Set pagination
48 $query_args['paged'] = Query::get_queried_page($settings);
49
50 // Set tax filters for filter component, if enabled
51 $queried_filters = Query::get_queried_filters($settings, 'product', 'product-filter');
52 $query_args['tax_query'] = empty($queried_filters['tax_query']) ? [] : $queried_filters['tax_query'];
53 }
54
55 // Set products per page
56 $query_args['limit'] = Helper::get_framework_visible_posts('product');
57 break;
58
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);
64 break;
65
66 default:
67 $query_args = Query::get_query_args('product-filter', $settings, true);
68 break;
69 }
70
71 $query_args['status'] = 'publish';
72 $query_args['total_pages'] = Query::get_total_pages($query_args);
73
74 // Hide out of stock products
75 if ($this->is_option('hide_out_of_stock', 'yes')) {
76 $query_args['stock_status'] = 'instock';
77 }
78
79 $this->_query = $query_args;
80
81 return wc_get_products($query_args);
82 }
83
84
85 // Render functions
86 function get_product_image(object $product)
87 {
88 if ($this->is_option('image', 'yes', '!==')) {
89 return;
90 }
91
92 // Get product image ID
93 $pic_id = $product->get_image_id();
94 if (!$pic_id) {
95 return;
96 }
97
98 // Get image size
99 $size = $this->get_settings_for_display('image_size_select') ?? 'uicore-medium';
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()); ?>">
104
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';
110
111 // Print secondary image markup if exists
112 echo $sec_img ? wp_kses_post($sec_img) : '';
113
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
129 }
130 function get_product_secondary_image($product, $size = 'woocommerce_thumbnail', $bg_output = false)
131 {
132 // Requested only for products with change-image animation
133 if ('ui-e-img-anim-change' !== $this->get_settings_for_display('anim_image')) {
134 return false;
135 }
136
137 // Get product gallery image IDs
138 $gallery_image_ids = $product->get_gallery_image_ids();
139
140 // Check if there is at least one gallery image
141 if (empty($gallery_image_ids)) {
142 return false;
143 }
144
145 // Get the first image from the gallery
146 $secondary_image_id = $gallery_image_ids[0];
147 $secondary_image_url = wp_get_attachment_image_url($secondary_image_id, $size);
148
149 // Output secondary image as <img> or background <div>
150 if ($bg_output) {
151 return sprintf(
152 '<div class="ui-e-post-img ui-e-post-img-secondary" style="background-image: url(%s);"></div>',
153 esc_url($secondary_image_url)
154 );
155 } else {
156 return wp_get_attachment_image($secondary_image_id, $size, false, [
157 'class' => 'ui-e-post-img ui-e-post-img-secondary'
158 ]);
159 }
160 }
161 function get_product_title($product, $tag)
162 {
163 if ($this->is_option('title', 'yes', '!==')) {
164 return;
165 }
166
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 }
176
177 function get_product_summary($product, $length = 55)
178 {
179 if (! $this->get_settings_for_display('excerpt')) {
180 return;
181 }
182
183 $summary = $product->get_short_description();
184
185 // Get product description if no summary
186 if (empty($summary)) {
187 $summary = $product->get_description();
188 }
189
190 // returns if no description also
191 if (empty($summary)) {
192 return;
193 }
194
195 ?>
196 <div class="ui-e-post-text">
197 <?php echo wp_kses_post(wp_trim_words($summary, $length)); ?>
198 </div>
199 <?php
200 }
201
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 /**
323 * Renders a product item with various settings and options.
324 * Important: any changes here should also be considered to `TRAIT_render_item()` from Post Component.
325 *
326 * @param WC_Product $product The product object.
327 * @param int $index The loop index.
328 * @param bool $carousel Indicates if the item needs carousel classnames.
329 * @param bool $legacy Indicates if the item is using legacy classnames.
330 * @param bool $is_ajax Indicates if the item is being rendered through REST API.
331 *
332 * @return void
333 */
334 function TRAIT_render_product($product, $index, $carousel = false, $legacy = false, $is_ajax = false)
335 {
336 $settings = $this->get_settings_for_display();
337
338 $product_id = is_object($product)
339 ? $product->get_id()
340 : null;
341
342 // Classnames but checking if we the widget is APG (legacy version)
343 $item_classes = $legacy ? ['ui-e-post-item', 'ui-e-item'] : ['ui-e-item']; // Single item lower wrap class receptor
344 $hover_item_class = $legacy ? 'anim_item' : 'item_hover_animation'; // item hover animation class
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
356 // If widget is not carousel type, we set animations classes directly on item selector
357 if (!$carousel) {
358 $item_classes[] = 'ui-e-animations-wrp';
359 $item_classes[] = $settings['animate_items'] === 'ui-e-grid-animate' ? 'elementor-invisible' : '';
360 $item_classes[] = $settings[$hover_item_class] !== '' ? $settings[$hover_item_class] : '';
361 } else {
362 // Get entrance and item hover animation classes
363 $entrance = $this->is_option('animate_items', 'ui-e-grid-animate') ? 'elementor-invisible' : '';
364 $hover = isset($settings[$hover_item_class]) ? $settings[$hover_item_class] : '';
365 $animations = sprintf('%s %s', $entrance, $hover);
366
367 // Check if entrance or hover animation are set
368 $has_animation = !empty($entrance) || !empty($hover);
369
370 // Prints extra wrappers required by the carousel script
371 ?>
372 <div class="ui-e-wrp swiper-slide">
373 <?php if ($has_animation) : ?>
374 <div class="ui-e-animations-wrp <?php echo esc_attr($animations); ?>">
375 <?php endif; ?>
376
377 <?php } ?>
378
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);
387 }
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 }
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 }
420
421 if (!isset($settings['button_position']) || empty($settings['button_position'])) {
422 $this->get_purchase_button($index);
423 }
424 }
425 ?>
426 </<?php echo Helper::esc_tag($content_tag, 'div'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
427 ?>>
428 </article>
429 </div>
430
431 <?php if ($carousel) { ?>
432 </div>
433 <?php if ($has_animation) : ?>
434 </div>
435 <?php endif; ?>
436
437 <?php }
438 }
439
440 // keep it to avoid fatal error
441 public function content_template() {}
442 }
443