PluginProbe
UiCore Elements – Free widgets and templates for Elementor / 1.2.1
UiCore Elements – Free widgets and templates for Elementor v1.2.1
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.2.1, at includes/utils/product-component.php

425 lines 16.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace UiCoreElements\Utils;
3
4 use Elementor\Icons_Manager;
5
6 use UiCoreElements\Helper;
7 use UiCoreElements\Controls\Query;
8
9 use UiCoreElements\Utils\Meta_Trait;
10 use UiCoreElements\Utils\Post_Trait;
11
12 defined('ABSPATH') || exit();
13
14 /**
15 * Product Component
16 *
17 * @since 1.0.11
18 */
19
20 trait Product_Trait {
21
22 use Post_Trait,
23 Meta_Trait;
24
25 /**
26 * Returns the proper query args for the product loop.
27 *
28 * @param array $settings Elementor controls settings.
29 * @param array|WP_Query $default_query The default query variables.
30 *
31 * @return array|stdClass Return the products data, prepared for loop.
32 */
33 function TRAIT_query_products($settings, $default_query)
34 {
35 $post_type = $settings['product-filter_post_type'];
36
37 switch ($post_type){
38 case 'current' :
39 // Makes sure widget will render some products at editor screen
40 if( $this->is_edit_mode() ){
41 $query_args['post_type'] = 'product';
42
43 } else {
44 $query_args = $default_query;
45
46 // Set pagination
47 $query_args['paged'] = Query::get_queried_page($settings);
48
49 // Set tax filters for filter component, if enabled
50 $queried_filters = Query::get_queried_filters($settings, 'product', 'product-filter');
51 $query_args['tax_query'] = empty($queried_filters['tax_query']) ? [] : $queried_filters['tax_query'];
52 }
53
54 // Set products per page
55 $query_args['limit'] = Helper::get_framework_visible_posts('product');
56 break;
57
58 case 'related' :
59 return Helper::get_product_related($settings['item_limit']['size']);
60 break;
61
62 default :
63 $query_args = Query::get_query_args('product-filter', $settings, true);
64 break;
65 }
66
67 // Set total pages
68 $query_args['total_pages'] = Query::get_total_pages($query_args);
69
70 // Hide out of stock products
71 if( $settings['hide_out_of_stock'] === 'yes' ){
72 $query_args['meta_query'] = [
73 [
74 'key' => '_stock_status',
75 'value' => 'instock',
76 'compare' => '=',
77 ]
78 ];
79 }
80
81 $this->_query = $query_args;
82
83 return wc_get_products($query_args);
84 }
85
86
87 // Render functions
88 function get_product_image(object $product)
89 {
90 if ($this->get_settings_for_display('image') !== 'yes') {
91 return;
92 }
93
94 // Get product image ID
95 $pic_id = $product->get_image_id();
96 if (!$pic_id) {
97 return;
98 }
99
100 // Get image size
101 $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()); ?>">
106
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';
112
113 // Print secondary image markup if exists
114 echo $sec_img ? wp_kses_post($sec_img) : '';
115
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
131 }
132 function get_product_secondary_image($product, $size = 'woocommerce_thumbnail', $bg_output = false)
133 {
134 // Requested only for products with change-image animation
135 if( 'ui-e-img-anim-change' !== $this->get_settings_for_display('anim_image') ) {
136 return false;
137 }
138
139 // Get product gallery image IDs
140 $gallery_image_ids = $product->get_gallery_image_ids();
141
142 // Check if there is at least one gallery image
143 if (empty($gallery_image_ids)) {
144 return false;
145 }
146
147 // Get the first image from the gallery
148 $secondary_image_id = $gallery_image_ids[0];
149 $secondary_image_url = wp_get_attachment_image_url($secondary_image_id, $size);
150
151 // Output secondary image as <img> or background <div>
152 if ($bg_output) {
153 return sprintf(
154 '<div class="ui-e-post-img ui-e-post-img-secondary" style="background-image: url(%s);"></div>',
155 esc_url($secondary_image_url)
156 );
157 } else {
158 return wp_get_attachment_image($secondary_image_id, $size, false, [
159 'class' => 'ui-e-post-img ui-e-post-img-secondary'
160 ]);
161 }
162 }
163 function get_product_title($product)
164 {
165 if ($this->get_settings_for_display('title') !== 'yes') {
166 return;
167 }
168
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
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 wp_kses_post($tbn_content)
315 ),
316 wp_kses_post($product)
317 ));
318
319 }
320
321 /**
322 * Renders a product item with various settings and options.
323 * Important: any changes here should also be considered to `TRAIT_render_item()` from Post Component.
324 *
325 * @param WC_Product $product The product object.
326 * @param int $index The loop index.
327 * @param bool $carousel Indicates if the item needs carousel classnames.
328 * @param bool $legacy Indicates if the item is using legacy classnames.
329 * @param bool $is_ajax Indicates if the item is being rendered through REST API.
330 *
331 * @return void
332 */
333 function TRAIT_render_product($product, $index, $carousel = false, $legacy = false, $is_ajax = false)
334 {
335 $settings = $this->get_settings_for_display();
336
337 // Classnames but checking if we the widget is APG (legacy version)
338 $item_classes = $legacy ? ['ui-e-post-item', 'ui-e-item'] : ['ui-e-item'] ; // Single item lower wrap class receptor
339 $hover_item_class = $legacy ? 'anim_item' : 'item_hover_animation'; // item hover animation class
340
341 // If widget is not carousel type, we set animations classes directly on item selector
342 if(!$carousel) {
343 $item_classes[] = 'ui-e-animations-wrp';
344 $item_classes[] = $settings['animate_items'] === 'ui-e-grid-animate' ? 'elementor-invisible' : '';
345 $item_classes[] = $settings[$hover_item_class] !== '' ? $settings[$hover_item_class] : '';
346
347 } else {
348 // Get entrance and item hover animation classes
349 $entrance = (isset($settings['animate_items']) && $settings['animate_items'] == 'ui-e-grid-animate') ? 'elementor-invisible' : '';
350 $hover = isset($settings[$hover_item_class]) ? $settings[$hover_item_class] : ''; //$settings[$hover_item_class] : null;
351 $animations = sprintf('%s %s', $entrance, $hover);
352
353 // Check if entrance or hover animation are set
354 $has_animation = !empty($entrance) || !empty($hover)
355
356 // Prints extra wrappers required by the carousel script
357 ?>
358 <div class="ui-e-wrp swiper-slide">
359 <?php if($has_animation) : ?>
360 <div class="ui-e-animations-wrp <?php echo esc_attr($animations);?>">
361 <?php endif; ?>
362
363 <?php } ?>
364
365 <div class="<?php echo esc_attr( implode(' ', $item_classes));?>">
366 <article <?php post_class('product'); ?>>
367 <div class="ui-e-post-top">
368 <?php $this->get_product_image($product); ?>
369 <?php $this->get_post_meta('top'); ?>
370 <?php
371 if($settings['button_position'] === 'ui-e-button-placement-image') {
372 $this->get_purchase_button($index);
373 }
374 ?>
375 </div>
376 <div class="ui-e-post-content">
377 <?php $this->get_post_meta('before_title'); ?>
378 <?php
379 if ($this->get_settings_for_display('title') === 'yes')
380 $this->get_product_title($product);
381 ?>
382 <?php $this->get_post_meta('after_title'); ?>
383 <?php $this->get_product_summary($product, $settings['excerpt_trim']); ?>
384 <?php $this->get_post_meta('bottom'); // button
385 ?>
386 <?php
387 if( defined('UICORE_VERSION') && version_compare(UICORE_VERSION, '6.0.1', '>=') && $this->get_settings_for_display('show_swatches') === 'yes' ){
388 // ajax requests works under minimal conditions, wich means Swatches class is not present
389 if ( $is_ajax ) {
390 require_once UICORE_INCLUDES . '/woocommerce/components/class-swatches.php';
391 }
392
393 \UiCore\WooCommerce\Swatches::print_swatches($product); // from Uicore Framework
394 }
395 if ( $this->get_settings_for_display('show_button') === 'yes' ) {
396 // Add match height spacing element if carousel. May look intrusive, but is the simplest method compared
397 // to absolute positioning or catching last content element to apply margin.
398 if($carousel && $settings['match_height'] === 'yes'){
399 ?>
400 <span class="ui-e-match-height"></span>
401 <?php
402 }
403
404 if( !isset($settings['button_position']) || empty($settings['button_position']) ) {
405 $this->get_purchase_button($index);
406 }
407 }
408 ?>
409 </div>
410 </article>
411 </div>
412
413 <?php if($carousel) { ?>
414 </div>
415 <?php if($has_animation) : ?>
416 </div>
417 <?php endif; ?>
418
419 <?php }
420 }
421
422 // keep it to avoid fatal error
423 public function content_template() {}
424 }
425