PluginProbe
UiCore Elements – Free widgets and templates for Elementor / 1.2.4
UiCore Elements – Free widgets and templates for Elementor v1.2.4
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 / widgets / advanced-product-grid.php

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

498 lines 17.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 <lucas@uicore.co>
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 // In some contexts, such as theme-builder, our custom controls are not available
34 // and requiring in register_controls method breaks the widget render in editor
35 if( ! class_exists('UiCoreElements\Controls\Product_Filter') ){
36 require_once UICORE_ELEMENTS_INCLUDES . '/controls/class-product-filter-control.php';
37 }
38 if( ! class_exists('UiCoreElements\Controls\Post_Filter') ){
39 require_once UICORE_ELEMENTS_INCLUDES . '/controls/class-post-filter-control.php';
40 }
41
42 return __('Advanced Product Grid', 'uicore-elements');
43 }
44 public function get_icon()
45 {
46 return 'eicon-gallery-grid ui-e-widget';
47 }
48 public function get_keywords()
49 {
50 return [ 'woocommerce', 'product', 'shop', 'store', 'post', 'grid' ];
51 }
52 public function get_styles()
53 {
54 // get parent get_styles
55 $styles = parent::get_styles();
56
57 // remove parent AdvancedPostGrid widget main style. OBS: the IDE complains because get_styles doc
58 // says it returns obj, but we're extending a widget that returns the array, so the parent
59 // is not widget base, but the AdvancedPostGrid widget
60 if ( ($key = array_search('advanced-post-grid', $styles)) !== false) {
61 unset($styles[$key]);
62 }
63
64 // Add the product grid style
65 $styles[] = 'advanced-product-grid';
66
67 return $styles;
68 }
69 function get_query()
70 {
71 return $this->_query;
72 }
73
74 protected function register_controls()
75 {
76
77 // Fallback
78 if( !class_exists('WooCommerce') ) {
79 $this->start_controls_section(
80 'section_fallback',
81 [
82 'label' => esc_html__('Content', 'uicore-elements'),
83 ]
84 );
85 $this->add_control(
86 'woocommerce_warning',
87 [
88 'type' => \Elementor\Controls_Manager::ALERT,
89 'alert_type' => 'warning',
90 'content' => esc_html__('Please enable WooCommerce to use this widget.', 'uicore-elements'),
91 ]
92 );
93 $this->end_controls_section();
94 return;
95 }
96
97 parent::register_controls();
98
99 // Remove query source and meta controls from parent
100 $this->remove_control('posts-filter_post_type');
101 $this->remove_control('top_meta');
102 $this->remove_control('before_title_meta');
103 $this->remove_control('after_title_meta');
104 $this->remove_control('bottom_meta');
105
106 // Register query control with updated options
107 $this->start_injection( [
108 'of' => 'section_post_grid_def',
109 'at' => 'after',
110 ] );
111 $this->add_group_control(
112 Product_Filter::get_type(),
113 [
114 'name' => 'product-filter',
115 'label' => esc_html__('Products', 'uicore-elements'),
116 'description' => esc_html__('Current Query Settings > Reading', 'uicore-elements'),
117 ]
118 );
119 $this->end_injection();
120
121 // Register meta controls with updated options
122 $this->start_injection( [
123 'of' => 'section_extra_item_content',
124 'at' => 'after',
125 ] );
126 $this->TRAIT_register_post_meta_controls(false, true);
127 $this->end_injection();
128
129 // Register new button placement control
130 $this->start_injection( [
131 'of' => 'section_button_style',
132 'at' => 'after',
133 ] );
134 $this->add_control(
135 'button_position',
136 [
137 'label' => esc_html__('Placement', 'uicore-elements'),
138 'type' => \Elementor\Controls_Manager::SELECT,
139 'options' => [
140 '' => 'Default',
141 'ui-e-button-placement-image' => 'On Image',
142 ],
143 'default' => 'ui-e-button-placement-image',
144 'render_type' => 'template',
145 'prefix_class' => ''
146 ]
147 );
148 $this->end_injection();
149
150 // Register new Swatches and Sale Badge controls
151 $this->start_injection( [
152 'of' => 'show_button',
153 'at' => 'after',
154 ] );
155 $this->add_control(
156 'show_swatches',
157 [
158 'label' => esc_html__('Swatches', 'uicore-elements'),
159 'type' => \Elementor\Controls_Manager::SWITCHER,
160 'label_on' => esc_html__( 'Show', 'uicore-elements' ),
161 'label_off' => esc_html__( 'Hide', 'uicore-elements' ),
162 'description' => esc_html__('Will only work if you have Uicore Framework plugin active at least on version 6.0.1', 'uicore-elements'),
163 'default' => 'yes',
164 ]
165 );
166 $this->add_control(
167 'show_sale_badge',
168 [
169 'label' => esc_html__('Sale Badge', 'uicore-elements'),
170 'type' => \Elementor\Controls_Manager::SWITCHER,
171 'label_on' => esc_html__( 'Show', 'uicore-elements' ),
172 'label_off' => esc_html__( 'Hide', 'uicore-elements' ),
173 'default' => 'yes',
174 'prefix_class' => 'ui-e-show-sale-badge-',
175 ]
176 );
177 $this->add_control(
178 'badge_warning',
179 [
180 'type' => \Elementor\Controls_Manager::RAW_HTML,
181 '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'),
182 'content_classes' => 'elementor-control-field-description',
183 'condition' => [
184 'show_sale_badge' => 'yes',
185 ],
186 ]
187 );
188 $this->end_injection();
189
190 // Register new Hide out of stock products control
191 $this->start_injection( [
192 'of' => 'sticky',
193 'at' => 'before',
194 ] );
195 $this->add_control(
196 'hide_out_of_stock',
197 [
198 'label' => esc_html__('Hide out of stock', 'uicore-elements'),
199 'type' => \Elementor\Controls_Manager::SWITCHER,
200 'condition' => [
201 'product-filter_post_type!' => 'related',
202 ],
203 ]
204 );
205 $this->end_injection();
206
207 //
208 $this->start_injection( [
209 'of' => 'text',
210 'at' => 'after',
211 ] );
212 $this->add_control(
213 'variations_text',
214 [
215 'label' => esc_html__('Select Options Text', 'uicore-elements'),
216 'label_block' => true,
217 'default' => '',
218 'description' => esc_html__('Variable product purchase text.', 'uicore-elements'),
219 'placeholder' => esc_html__('Select Options', 'uicore-elements'),
220 ]
221 );
222 $this->add_control(
223 'unavailable_text',
224 [
225 'label' => esc_html__('Unavailable Text', 'uicore-elements'),
226 'label_block' => true,
227 'default' => '',
228 'description' => esc_html__('Out of stock or not available purchase text.', 'uicore-elements'),
229 'placeholder' => esc_html__('Read more', 'uicore-elements'),
230 ]
231 );
232 $this->end_injection();
233
234 // Update controls that uses 'posts_filter' to 'product_filter'
235 $this->update_control(
236 'item_limit',
237 [
238 'condition' => [
239 'product-filter_post_type!' => 'current',
240 ],
241 ]
242 );
243
244 // Update filters options
245 $this->update_control(
246 'filters_taxonomies',
247 [
248 'options' => Helper::get_taxonomies(true, true),
249 'default' => ['product_cat'],
250 ]
251 );
252
253 // Update Image animation options and default value
254 $this->update_control(
255 'anim_image',
256 [
257 'options' => [
258 '' => __('None', 'uicore-elements'),
259 'ui-e-img-anim-zoom' => __('Zoom', 'uicore-elements'),
260 'ui-e-img-anim-change' => __('Change image', 'uicore-elements'),
261 ],
262 'default' => 'ui-e-img-anim-change',
263 'render_type' => 'template', // `change image` adds a new html element
264 ]
265 );
266
267 // Update top meta style options so sale badge (default top meta) looks better by default
268 $this->update_control(
269 'top_meta_color',
270 [
271 'default' => '#FFF',
272 ]
273 );
274 $this->update_control(
275 'top_meta_background',
276 [
277 'default' => '#000',
278 ]
279 );
280 $this->update_control(
281 'top_meta_padding',
282 [
283 'default' => [
284 'top' => 5,
285 'right' => 5,
286 'bottom' => 5,
287 'left' => 5,
288 'unit' => 'px'
289 ],
290 ]
291 );
292
293 // Update some UI controls labels and default values
294 $this->update_control(
295 'excerpt',
296 [
297 'label' => esc_html__('Product Summary', 'uicore-elements'),
298 'default' => ''
299 ]
300 );
301 $this->update_control(
302 'excerpt_trim',
303 [ 'label' => esc_html__('Summary Length (words)', 'uicore-elements') ]
304 );
305 $this->update_control(
306 'show_button',
307 [
308 'label' => esc_html__('Add to Cart Button', 'uicore-elements'),
309 'default' => 'yes'
310 ]
311 );
312 $this->update_control(
313 'text',
314 [
315 'label' => esc_html__('Add to Cart Text', 'uicore-elements'),
316 'label_block' => true,
317 'default' => '',
318 'placeholder' => esc_html__('Add to cart', 'uicore-elements'),
319 ]
320 );
321 $this->update_control(
322 'text_color',
323 [ 'label' => esc_html__('Summary Color', 'uicore-elements') ]
324 );
325 $this->update_control(
326 'text_typography',
327 [ 'label' => esc_html__('Summary Typography', 'uicore-elements') ]
328 );
329 $this->update_control(
330 'text_gap',
331 [ 'label' => esc_html__('Summary Top Space', 'uicore-elements') ]
332 );
333
334 // Update button controls
335 $this->update_control(
336 'border_radius', // button border-radius
337 [
338 'default' => [ 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ]
339 ]
340 );
341 $this->update_control(
342 'text_padding', // button padding
343 [
344 'default' => [ 'top' => 10, 'right' => 10, 'bottom' => 10, 'left' => 10, 'unit' => 'px' ]
345 ]
346 );
347 $this->update_control(
348 'button_align',
349 [
350 'condition' => [
351 'button_position!' => 'ui-e-button-placement-image',
352 ],
353 // since product might have ajax add to cart, wich has flex display so the cart added icon do not break button layout,
354 // we justify the flex content to center so justified text align button keeps text centralized. See: https://uicore.atlassian.net/browse/ELM-429
355 'selectors' => [
356 '{{WRAPPER}} .ui-e-readmore' => 'align-self: {{VALUE}}; justify-content: center;',
357 ]
358 ]
359 );
360 $this->update_control(
361 'button_gap',
362 [
363 'condition' => [
364 'button_position!' => 'ui-e-button-placement-image',
365 ]
366 ]
367 );
368 }
369
370 /**
371 * Renders the widget content using AJAX.
372 *
373 * This method retrieves the widget settings; set up the query, and renders each post item.
374 * If no posts are found, it returns false. After rendering, it resets the query and returns the output.
375 *
376 * @param array|false $current_query The current query args, or false if the widget isn't set to use the current query.
377 *
378 * @return string|false The rendered widget content or false if no posts are found.
379 */
380 public function render_ajax($current_query)
381 {
382 // Get settings and post type
383 $settings = $this->get_settings();
384
385 $this->TRAIT_query_products( $settings, $current_query );
386 $wc_query = $this->get_query();
387
388 $products = wc_get_products($wc_query);
389
390 // No products found
391 if ( empty($products) ) {
392 return false;
393 }
394
395 \ob_start();
396 foreach ($products as $index => $product) {
397 $post_object = get_post($product->get_id());
398 setup_postdata($post_object);
399
400 $this->TRAIT_render_product($product, $index, false, true, true);
401
402 wp_reset_postdata();
403 }
404 $markup = \ob_get_clean();
405
406 return [
407 'markup' => $markup,
408 'total_pages' => $wc_query['total_pages']
409 ];
410 }
411
412 protected function render()
413 {
414 if( !class_exists('WooCommerce') ) {
415 if ( $this->is_edit_mode() ) {
416 echo '<p>' . esc_html__('Please enable WooCommerce to use this widget.', 'uicore-elements') . '</p>';
417 }
418 return;
419 }
420
421 global $wp_query;
422 $settings = $this->get_settings();
423
424 // Related products, on save page action at editor, triggers an fata error upon post object handle.
425 // Prevent it for now by disabling the widget on edit mode. TODO: remove it after fixing the issue
426 if ( 'related' === $settings['product-filter_post_type'] && $this->is_edit_mode() ) {
427 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>';
428 return;
429 }
430
431 // Build query
432 $products = $this->TRAIT_query_products( $settings, $wp_query->query );
433 $wc_query = $this->get_query();
434
435 // Store widget settings in a transient
436 $ID = strval($this->get_ID());
437 set_transient('ui_elements_widgetdata_' . $ID, $settings, \MONTH_IN_SECONDS);
438
439 // Get the quantity of items and creates a loop control
440 $items = $settings['item_limit']['size'];
441 $loops = 0;
442
443 $this->TRAIT_render_filters($settings, true);
444
445 // No posts found
446 if ( empty($products) ) {
447 echo '<p style="text-align:center">' . esc_html__('No products found.', 'uicore-elements') . '</p>';
448
449 } else {
450 ?>
451 <div class="ui-e-adv-grid">
452 <?php
453 foreach ($products as $index => $product) {
454
455 // sticky posts disregards posts per page, so ending the loop if $items == $loop forces the query respects the users item limit
456 if ($settings['sticky'] && $items == $loops) {
457 break;
458 }
459
460 // check is is related post type
461 if ( 'related' === $settings['product-filter_post_type'] ) {
462 $product = wc_get_product($product);
463 }
464
465 $post_object = get_post($product->get_id());
466 setup_postdata($post_object);
467
468 $this->TRAIT_render_product($product, $index, false, true);
469
470 wp_reset_postdata(); // Reset post data after rendering each product
471 $loops++;
472 }
473 ?>
474 </div>
475 <?php
476
477 $this->TRAIT_render_pagination($settings, true);
478
479 // Add some data, to help rest api, to the js
480 if( $this->is_option('pagination', 'yes') && $this->is_option('pagination_type', 'load_more') ) {
481
482 echo '<script>';
483
484 // Add total pages to js variable
485 echo 'window.ui_total_pages_' . esc_html($ID) . ' = ' . esc_html( $wc_query['total_pages'] ) . ';';
486
487 // Pass current query to js variable
488 if( $settings['product-filter_post_type'] == 'current' ) {
489 echo 'window.ui_query_' . esc_html($ID) . ' = ' . \json_encode($wc_query) . ';';
490 }
491
492 echo '</script>';
493 }
494 }
495 }
496 }
497 \Elementor\Plugin::instance()->widgets_manager->register(new AdvancedProductGrid());
498