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

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

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