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