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
uicore-elements / includes / widgets / advanced-product-grid.php

advanced-product-grid.php in UiCore Elements – Free widgets and templates for Elementor 1.3.18, at includes/widgets/advanced-product-grid.php

694 lines 24.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UiCoreElements;
4
5 use UiCoreElements\Controls\Product_Filter;
6 use uicoreElements\Utils\Product_Trait;
7
8 defined('ABSPATH') || exit();
9
10 /**
11 * Advanced Product Grid Widget. Extends APG.
12 *
13 * @author Lucas Marini Falbo <[email protected]>
14 * @since 1.0.11
15 */
16
17 class AdvancedProductGrid extends AdvancedPostGrid
18 {
19 private $_query;
20
21 use Product_Trait;
22
23 public function get_name()
24 {
25 return 'uicore-advanced-product-grid';
26 }
27 public function get_categories()
28 {
29 return ['uicore', 'uicore-woo'];
30 }
31 public function get_title()
32 {
33 return __('Advanced Product Grid', 'uicore-elements');
34 }
35 public function get_icon()
36 {
37 return 'eicon-gallery-grid ui-e-widget';
38 }
39 public function get_keywords()
40 {
41 return ['woocommerce', 'product', 'shop', 'store', 'post', 'grid'];
42 }
43 public function get_styles()
44 {
45 // get parent get_styles
46 $styles = parent::get_styles();
47
48 // remove parent AdvancedPostGrid widget main style. OBS: the IDE complains because get_styles doc
49 // says it returns obj, but we're extending a widget that returns the array, so the parent
50 // is not widget base, but the AdvancedPostGrid widget
51 if (($key = array_search('advanced-post-grid', $styles)) !== false) {
52 unset($styles[$key]);
53 }
54
55 // Add the product grid style
56 $styles[] = 'advanced-product-grid';
57
58 return $styles;
59 }
60
61 /**
62 * Makes sure some of our custom controls are present. This should be a
63 * temporary fix to a problem that do not happens consistently.
64 */
65 function custom_controls_verification()
66 {
67 // In some contexts, such as theme-builder, our custom controls are not available
68 // and requiring in register_controls method breaks the widget render in editor
69 if (! defined('DOING_AJAX') || ! DOING_AJAX) {
70 if (! class_exists('UiCoreElements\Controls\Post_Filter')) {
71 require_once UICORE_ELEMENTS_INCLUDES . '/controls/class-post-filter-control.php';
72 }
73 if (! class_exists('UiCoreElements\Controls\Product_Filter')) {
74 require_once UICORE_ELEMENTS_INCLUDES . '/controls/class-product-filter-control.php';
75 }
76 }
77 }
78 function get_query()
79 {
80 return $this->_query;
81 }
82
83 protected function register_controls()
84 {
85 $this->custom_controls_verification();
86
87 // Fallback
88 if (!class_exists('WooCommerce')) {
89 $this->start_controls_section(
90 'section_fallback',
91 [
92 'label' => esc_html__('Content', 'uicore-elements'),
93 ]
94 );
95 $this->add_control(
96 'woocommerce_warning',
97 [
98 'type' => \Elementor\Controls_Manager::ALERT,
99 'alert_type' => 'warning',
100 'content' => esc_html__('Please enable WooCommerce to use this widget.', 'uicore-elements'),
101 ]
102 );
103 $this->end_controls_section();
104 return;
105 }
106
107 parent::register_controls();
108
109 // Remove query source and meta controls from parent
110 $this->remove_control('posts-filter_post_type');
111 $this->remove_control('top_meta');
112 $this->remove_control('before_title_meta');
113 $this->remove_control('after_title_meta');
114 $this->remove_control('bottom_meta');
115
116 // Register query control with updated options
117 $this->start_injection([
118 'of' => 'section_post_grid_def',
119 'at' => 'after',
120 ]);
121 $this->add_group_control(
122 Product_Filter::get_type(),
123 [
124 'name' => 'product-filter',
125 'label' => esc_html__('Products', 'uicore-elements'),
126 'description' => esc_html__('Current Query Settings > Reading', 'uicore-elements'),
127 ]
128 );
129 $this->end_injection();
130
131 // Register meta controls with updated options
132 $this->start_injection([
133 'of' => 'section_extra_item_content',
134 'at' => 'after',
135 ]);
136 $this->TRAIT_register_post_meta_controls(false, true);
137 $this->end_injection();
138
139 // Register new button placement control
140 $this->start_injection([
141 'of' => 'section_button_style',
142 'at' => 'after',
143 ]);
144 $this->add_control(
145 'button_position',
146 [
147 'label' => esc_html__('Placement', 'uicore-elements'),
148 'type' => \Elementor\Controls_Manager::SELECT,
149 'options' => [
150 '' => 'Default',
151 'ui-e-button-placement-image' => 'On Image',
152 ],
153 'default' => 'ui-e-button-placement-image',
154 'render_type' => 'template',
155 'prefix_class' => ''
156 ]
157 );
158 $this->end_injection();
159
160 // Register new Swatches and Sale Badge controls
161 $this->start_injection([
162 'of' => 'show_button',
163 'at' => 'after',
164 ]);
165
166 if (defined('UICORE_ASSETS')) {
167 $this->add_control(
168 'show_swatches',
169 [
170 'label' => esc_html__('Swatches', 'uicore-elements'),
171 'type' => \Elementor\Controls_Manager::SWITCHER,
172 'label_on' => esc_html__('Show', 'uicore-elements'),
173 'label_off' => esc_html__('Hide', 'uicore-elements'),
174 'description' => esc_html__('Will only work if you have Uicore Framework plugin active at least on version 6.0.1', 'uicore-elements'),
175 'default' => 'yes',
176 ]
177 );
178 }
179
180 $this->add_control(
181 'show_sale_badge',
182 [
183 'label' => esc_html__('Sale Badge', 'uicore-elements'),
184 'type' => \Elementor\Controls_Manager::SWITCHER,
185 'label_on' => esc_html__('Show', 'uicore-elements'),
186 'label_off' => esc_html__('Hide', 'uicore-elements'),
187 'default' => 'yes',
188 'prefix_class' => 'ui-e-show-sale-badge-',
189 ]
190 );
191 $this->add_control(
192 'badge_warning',
193 [
194 'type' => \Elementor\Controls_Manager::RAW_HTML,
195 'raw' => __("If you can't see the Sale Badge after setting 'Show', you problably didn't set any Meta option as 'Product Sale', or you don't have products on sale.", 'uicore-elements'),
196 'content_classes' => 'elementor-control-field-description',
197 'condition' => [
198 'show_sale_badge' => 'yes',
199 ],
200 ]
201 );
202
203 $this->end_injection();
204
205 if (defined('UICORE_ASSETS')) {
206 $this->start_injection([
207 'of' => 'section_style_content',
208 'at' => 'after',
209 ]);
210 $this->start_controls_section(
211 'section_swatches',
212 [
213 'label' => esc_html__('Swatches', 'uicore-elements') . UICORE_ELEMENTS_NEW_OPTION,
214 'tab' => \Elementor\Controls_Manager::TAB_STYLE,
215 'condition' => [
216 'show_swatches' => 'yes',
217 ],
218 ]
219 );
220
221 $this->add_control(
222 'swatches_to',
223 [
224 'label' => esc_html__('Use Framework defaults', 'uicore-elements'),
225 'type' => \Elementor\Controls_Manager::SWITCHER,
226 'description' => esc_html__('If enabled, the swatches will inherit your theme options.', 'uicore-elements'),
227 'label_on' => esc_html__('Yes', 'uicore-elements'),
228 'label_off' => esc_html__('No', 'uicore-elements'),
229 'default' => 'yes',
230 ]
231 );
232
233 $this->add_control(
234 'swatches_size',
235 [
236 'label' => esc_html__('Size', 'uicore-elements'),
237 'type' => \Elementor\Controls_Manager::SLIDER,
238 'size_units' => ['px'],
239 'range' => [
240 'px' => [
241 'min' => 10,
242 'max' => 100,
243 ],
244 ],
245 'default' => [
246 'unit' => 'px',
247 'size' => 30,
248 ],
249 'condition' => [
250 'show_swatches' => 'yes',
251 'swatches_to!' => 'yes',
252 ],
253 'selectors' => [
254 '{{WRAPPER}}' => '--uicore-swatch-size: {{SIZE}}{{UNIT}};',
255 ],
256 ]
257 );
258
259 $this->add_control(
260 'swatches_outer_radius',
261 [
262 'label' => esc_html__('Outer Border Radius', 'uicore-elements'),
263 'type' => \Elementor\Controls_Manager::SLIDER,
264 'size_units' => ['%', 'px'],
265 'range' => [
266 '%' => [
267 'min' => 0,
268 'max' => 100,
269 ],
270 'px' => [
271 'min' => 0,
272 'max' => 50,
273 ],
274 ],
275 'default' => [
276 'unit' => '%',
277 'size' => 50,
278 ],
279 'separator' => 'before',
280 'condition' => [
281 'show_swatches' => 'yes',
282 'swatches_to!' => 'yes',
283 ],
284 'selectors' => [
285 '{{WRAPPER}}' => '--uicore-swatch-radius: {{SIZE}}{{UNIT}}; --uicore-swatch-big-radius: {{SIZE}}{{UNIT}};',
286 '{{WRAPPER}} .uicore-swatch.uicore-swatch--color' => 'border-radius: {{SIZE}}{{UNIT}};',
287 ],
288 ]
289 );
290
291 $this->add_control(
292 'swatches_inner_radius',
293 [
294 'label' => esc_html__('Inner Border Radius', 'uicore-elements'),
295 'type' => \Elementor\Controls_Manager::SLIDER,
296 'size_units' => ['px', '%'],
297 'range' => [
298 'px' => [
299 'min' => 0,
300
301 'max' => 100,
302 ],
303 '%' => [
304 'min' => 0,
305 'max' => 50,
306 ],
307 ],
308 'default' => [
309 'unit' => 'px',
310 'size' => 4,
311 ],
312 'separator' => 'after',
313 'condition' => [
314 'show_swatches' => 'yes',
315 'swatches_to!' => 'yes',
316 ],
317 'selectors' => [
318 '{{WRAPPER}} .uicore-swatch > div' => 'border-radius: {{SIZE}}{{UNIT}};',
319 ],
320 ]
321 );
322
323 $this->add_group_control(
324 \Elementor\Group_Control_Border::get_type(),
325 [
326 'name' => 'swatch_border',
327 'selector' => '{{WRAPPER}} .uicore-swatch',
328 'condition' => [
329 'show_swatches' => 'yes',
330 'swatches_to!' => 'yes',
331 ],
332 ]
333 );
334
335 $this->add_control(
336 'hover_border_size',
337 [
338 'label' => esc_html__('Hover Border Size', 'uicore-elements'),
339 'type' => \Elementor\Controls_Manager::SLIDER,
340 'size_units' => ['px'],
341 'range' => [
342 'px' => [
343 'min' => 0,
344
345 'max' => 5,
346 ],
347 ],
348 'default' => [
349 'unit' => 'px',
350 'size' => 1,
351 ],
352 'condition' => [
353 'show_swatches' => 'yes',
354 'swatches_to!' => 'yes',
355 ],
356 'selectors' => [
357 '{{WRAPPER}} .uicore-swatch:hover' => 'box-shadow: 0 0 0 {{SIZE}}{{UNIT}} var(--uicore-swatch-border);',
358 ],
359 ]
360 );
361
362 $this->add_control(
363 'swatch_hover_color',
364 [
365 'label' => esc_html__('Hover Border Color', 'uicore-elements'),
366 'type' => \Elementor\Controls_Manager::COLOR,
367 'condition' => [
368 'show_swatches' => 'yes',
369 'swatches_to!' => 'yes',
370 ],
371 'selectors' => [
372 '{{WRAPPER}}' => '--uicore-swatch-border: {{VALUE}};',
373 '{{WRAPPER}} .uicore-swatch:hover' => 'border-color: {{VALUE}};',
374 ],
375 ]
376 );
377
378 $this->end_controls_section();
379 $this->end_injection();
380 }
381
382 // Register new Hide out of stock products control
383 $this->start_injection([
384 'of' => 'sticky',
385 'at' => 'before',
386 ]);
387 $this->add_control(
388 'hide_out_of_stock',
389 [
390 'label' => esc_html__('Hide out of stock', 'uicore-elements'),
391 'type' => \Elementor\Controls_Manager::SWITCHER,
392 'condition' => [
393 'product-filter_post_type!' => 'related',
394 ],
395 ]
396 );
397 $this->end_injection();
398
399 $this->start_injection([
400 'of' => 'text',
401 'at' => 'after',
402 ]);
403 $this->add_control(
404 'variations_text',
405 [
406 'type' => \Elementor\Controls_Manager::TEXT,
407 'label' => esc_html__('Select Options Text', 'uicore-elements'),
408 'label_block' => true,
409 'default' => '',
410 'description' => esc_html__('Variable product purchase text.', 'uicore-elements'),
411 'placeholder' => esc_html__('Select Options', 'uicore-elements'),
412 ]
413 );
414 $this->add_control(
415 'unavailable_text',
416 [
417 'type' => \Elementor\Controls_Manager::TEXT,
418 'label' => esc_html__('Unavailable Text', 'uicore-elements'),
419 'label_block' => true,
420 'default' => '',
421 'description' => esc_html__('Out of stock or not available purchase text.', 'uicore-elements'),
422 'placeholder' => esc_html__('Read more', 'uicore-elements'),
423 ]
424 );
425 $this->end_injection();
426
427 // Update controls that uses 'posts_filter' to 'product_filter'
428 $this->update_control(
429 'item_limit',
430 [
431 'condition' => [
432 'product-filter_post_type!' => 'current',
433 ],
434 ]
435 );
436
437 // Update filters options
438 $this->update_control(
439 'filters_taxonomies',
440 [
441 'options' => Helper::get_taxonomies(true, true),
442 'default' => ['product_cat'],
443 ]
444 );
445
446 // Update Image animation options and default value
447 $this->update_control(
448 'anim_image',
449 [
450 'options' => [
451 '' => __('None', 'uicore-elements'),
452 'ui-e-img-anim-zoom' => __('Zoom', 'uicore-elements'),
453 'ui-e-img-anim-change' => __('Change image', 'uicore-elements'),
454 ],
455 'default' => 'ui-e-img-anim-change',
456 'render_type' => 'template', // `change image` adds a new html element
457 ]
458 );
459
460 // Update top meta style options so sale badge (default top meta) looks better by default
461 $this->update_control(
462 'top_meta_color',
463 [
464 'default' => '#FFF',
465 ]
466 );
467 $this->update_control(
468 'top_meta_background',
469 [
470 'default' => '#000',
471 ]
472 );
473 $this->update_control(
474 'top_meta_padding',
475 [
476 'default' => [
477 'top' => 5,
478 'right' => 5,
479 'bottom' => 5,
480 'left' => 5,
481 'unit' => 'px'
482 ],
483 ]
484 );
485
486 // Update some UI controls labels and default values
487 $this->update_control(
488 'excerpt',
489 [
490 'label' => esc_html__('Product Summary', 'uicore-elements'),
491 'default' => ''
492 ]
493 );
494 $this->update_control(
495 'excerpt_trim',
496 ['label' => esc_html__('Summary Length (words)', 'uicore-elements')]
497 );
498 $this->update_control(
499 'show_button',
500 [
501 'label' => esc_html__('Add to Cart Button', 'uicore-elements'),
502 'default' => 'yes'
503 ]
504 );
505 $this->update_control(
506 'text',
507 [
508 'label' => esc_html__('Add to Cart Text', 'uicore-elements'),
509 'label_block' => true,
510 'default' => '',
511 'placeholder' => esc_html__('Add to cart', 'uicore-elements'),
512 ]
513 );
514 $this->update_control(
515 'text_color',
516 ['label' => esc_html__('Summary Color', 'uicore-elements')]
517 );
518 $this->update_control(
519 'text_typography',
520 ['label' => esc_html__('Summary Typography', 'uicore-elements')]
521 );
522 $this->update_control(
523 'text_gap',
524 ['label' => esc_html__('Summary Top Space', 'uicore-elements')]
525 );
526
527 // Update button controls
528 $this->update_control(
529 'border_radius', // button border-radius
530 [
531 'default' => ['top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0]
532 ]
533 );
534 $this->update_control(
535 'text_padding', // button padding
536 [
537 'default' => ['top' => 10, 'right' => 10, 'bottom' => 10, 'left' => 10, 'unit' => 'px']
538 ]
539 );
540 $this->update_control(
541 'button_align',
542 [
543 'condition' => [
544 'button_position!' => 'ui-e-button-placement-image',
545 ],
546 // since product might have ajax add to cart, wich has flex display so the cart added icon do not break button layout,
547 // we justify the flex content to center so justified text align button keeps text centralized. See: https://uicore.atlassian.net/browse/ELM-429
548 'selectors' => [
549 '{{WRAPPER}} .ui-e-readmore' => 'align-self: {{VALUE}}; justify-content: center;',
550 ]
551 ]
552 );
553 $this->update_control(
554 'button_gap',
555 [
556 'condition' => [
557 'button_position!' => 'ui-e-button-placement-image',
558 ]
559 ]
560 );
561 }
562
563 /**
564 * Renders the widget content using AJAX.
565 *
566 * This method retrieves the widget settings; set up the query, and renders each post item.
567 * If no posts are found, it returns false. After rendering, it resets the query and returns the output.
568 *
569 * @param array|false $current_query The current query args, or false if the widget isn't set to use the current query.
570 * @param int $pageID The ID of the page where the widget is located.
571 *
572 * @return string|false The rendered widget content or false if no posts are found.
573 */
574 public function render_ajax($current_query)
575 {
576 // Get settings and post type
577 $settings = $this->get_settings();
578
579 $this->TRAIT_query_products($settings, $current_query);
580 $wc_query = $this->get_query();
581
582 $products = wc_get_products($wc_query);
583
584 // No products found
585 if (empty($products)) {
586 return false;
587 }
588
589 \ob_start();
590 foreach ($products as $index => $product) {
591 $post_object = get_post($product->get_id());
592 setup_postdata($post_object);
593
594 $this->TRAIT_render_product($product, $index, false, true, true);
595
596 wp_reset_postdata();
597 }
598 $markup = \ob_get_clean();
599
600 return [
601 'markup' => $markup,
602 'total_pages' => $wc_query['total_pages']
603 ];
604 }
605
606 protected function render()
607 {
608 if (!class_exists('WooCommerce')) {
609 if ($this->is_edit_mode()) {
610 echo '<p>' . esc_html__('Please enable WooCommerce to use this widget.', 'uicore-elements') . '</p>';
611 }
612 return;
613 }
614
615 global $wp_query;
616 $settings = $this->get_settings();
617
618 // Get product ID
619 $product_id = get_the_ID();
620
621 // Related products, on save page action at editor, triggers an fata error upon post object handle.
622 // Prevent it for now by disabling the widget on edit mode. TODO: remove it after fixing the issue
623 if ('related' === $settings['product-filter_post_type'] && $this->is_edit_mode()) {
624 echo '<p>' . esc_html__('This widget is set to display related products. To see the results, please preview the page, or customize your widget before setting "current" query.', 'uicore-elements') . '</p>';
625 return;
626 }
627
628 // Build query
629 $products = $this->TRAIT_query_products($settings, $wp_query->query);
630 $wc_query = $this->get_query();
631
632 // Store widget settings in a transient
633 $ID = strval($this->get_ID());
634 set_transient('ui_elements_widgetdata_' . $ID, $settings, \MONTH_IN_SECONDS);
635
636 // Get the quantity of items and creates a loop control
637 $items = $settings['item_limit']['size'];
638 $loops = 0;
639
640 $this->TRAIT_render_filters($settings, true);
641
642 // No posts found
643 if (empty($products)) {
644 echo '<p style="text-align:center">' . esc_html__('No products found.', 'uicore-elements') . '</p>';
645 } else {
646 ?>
647 <div class="ui-e-adv-grid">
648 <?php
649 foreach ($products as $index => $product) {
650
651 // sticky posts disregards posts per page, so ending the loop if $items == $loop forces the query respects the users item limit
652 if ($settings['sticky'] && $items == $loops) {
653 break;
654 }
655
656 // check is is related post type
657 if ('related' === $settings['product-filter_post_type']) {
658 $product = wc_get_product($product);
659 }
660
661 $post_object = get_post($product->get_id());
662 setup_postdata($post_object);
663
664 $this->TRAIT_render_product($product, $index, false, true);
665
666 wp_reset_postdata(); // Reset post data after rendering each product
667 $loops++;
668 }
669 ?>
670 </div>
671 <?php
672
673 $this->TRAIT_render_pagination($settings, $product_id, true);
674
675 // Add some data, to help rest api, to the js
676 if ($this->is_option('pagination', 'yes') && $this->is_option('pagination_type', 'load_more')) {
677
678 echo '<script>';
679
680 // Add total pages to js variable
681 echo 'window.ui_total_pages_' . esc_html($ID) . ' = ' . esc_html($wc_query['total_pages']) . ';';
682
683 // Pass current query to js variable
684 if ($settings['product-filter_post_type'] == 'current') {
685 echo 'window.ui_query_' . esc_html($ID) . ' = ' . \json_encode($wc_query) . ';';
686 }
687
688 echo '</script>';
689 }
690 }
691 }
692 }
693 \Elementor\Plugin::instance()->widgets_manager->register(new AdvancedProductGrid());
694