PluginProbe
Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder / 3.1.4
Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder v3.1.4
3.1.4 3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.0.7 3.0.5 3.0.4 3.0.3 3.0.2 trunk 1.5.0 1.5.1 1.5.2 1.6.1 1.6.2 1.6.3 1.6.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 All 93 releases
← All changes | includes/controls/group-query/group-control-query.php +1018 -729 1.5.0 → 3.1.4 View file →
@@ -1,729 +1,1018 @@
1 -<?php
2 -
3 -namespace UltimateStoreKit\Includes\Controls\GroupQuery;
4 -
5 -use Elementor\Controls_Manager;
6 -use UltimateStoreKit\Includes\Controls\SelectInput\Dynamic_Select;
7 -
8 -if (!defined('ABSPATH')) {
9 - exit; // Exit if accessed directly.
10 -}
11 -
12 -trait Group_Control_Query {
13 -
14 - public function register_query_builder_controls() {
15 -
16 - $this->add_control(
17 - 'product_source',
18 - [
19 - 'label' => __('Source', 'ultimate-store-kit'),
20 - 'type' => Controls_Manager::SELECT,
21 - 'options' => $this->getGroupControlQueryPostTypes(),
22 - 'default' => 'product',
23 - ]
24 - );
25 - $this->add_control(
26 - 'product_limit',
27 - [
28 - 'label' => esc_html__('Product Limit', 'ultimate-store-kit'),
29 - 'type' => Controls_Manager::NUMBER,
30 - 'default' => 9,
31 - ]
32 - );
33 -
34 - $this->add_control(
35 - 'product_selected_ids',
36 - [
37 - 'label' => __('Search & Select', 'ultimate-store-kit'),
38 - 'type' => Dynamic_Select::TYPE,
39 - 'multiple' => true,
40 - 'label_block' => true,
41 - 'query_args' => [
42 - 'query' => 'posts',
43 - ],
44 - 'condition' => [
45 - 'product_source' => 'manual_selection',
46 - ]
47 - ]
48 - );
49 -
50 - $this->start_controls_tabs(
51 - 'tabs_product_include_exclude',
52 - [
53 - 'condition' => [
54 - 'product_source!' => ['manual_selection', 'current_query'],
55 - ]
56 - ]
57 - );
58 -
59 - $this->start_controls_tab(
60 - 'tab_product_include',
61 - [
62 - 'label' => __('Include', 'ultimate-store-kit'),
63 - 'condition' => [
64 - 'product_source!' => ['manual_selection', 'current_query'],
65 - ]
66 - ]
67 - );
68 -
69 - $this->add_control(
70 - 'product_include_by',
71 - [
72 - 'label' => __('Include By', 'ultimate-store-kit'),
73 - 'type' => Controls_Manager::SELECT2,
74 - 'multiple' => true,
75 - 'label_block' => true,
76 - 'options' => [
77 - 'authors' => __('Authors', 'ultimate-store-kit'),
78 - 'terms' => __('Terms', 'ultimate-store-kit'),
79 - ],
80 - 'condition' => [
81 - 'product_source!' => ['manual_selection', 'current_query'],
82 - ]
83 - ]
84 - );
85 -
86 - $this->add_control(
87 - 'product_include_author_ids',
88 - [
89 - 'label' => __('Authors', 'ultimate-store-kit'),
90 - 'type' => Dynamic_Select::TYPE,
91 - 'multiple' => true,
92 - 'label_block' => true,
93 - 'query_args' => [
94 - 'query' => 'authors',
95 - ],
96 - 'condition' => [
97 - 'product_include_by' => 'authors',
98 - 'product_source!' => ['manual_selection', 'current_query'],
99 - ]
100 - ]
101 - );
102 -
103 - $this->add_control(
104 - 'product_include_term_ids',
105 - [
106 - 'label' => __('Terms', 'ultimate-store-kit'),
107 - 'description' => __('Terms are items in a taxonomy. The available taxonomies are: Categories, Tags, Formats and custom taxonomies.', 'ultimate-store-kit'),
108 - 'type' => Dynamic_Select::TYPE,
109 - 'multiple' => true,
110 - 'label_block' => true,
111 - 'placeholder' => __('Type and select terms', 'ultimate-store-kit'),
112 - 'query_args' => [
113 - 'query' => 'terms',
114 - 'widget_props' => [
115 - 'post_type' => 'product_source'
116 - ]
117 - ],
118 - 'condition' => [
119 - 'product_include_by' => 'terms',
120 - 'product_source!' => ['manual_selection', 'current_query', '_related_post_type'],
121 - ]
122 - ]
123 - );
124 -
125 - $this->end_controls_tab();
126 -
127 - $this->start_controls_tab(
128 - 'tab_product_exclude',
129 - [
130 - 'label' => __('Exclude', 'ultimate-store-kit'),
131 - 'condition' => [
132 - 'product_source!' => ['manual_selection', 'current_query'],
133 - ]
134 - ]
135 - );
136 -
137 - $this->add_control(
138 - 'product_exclude_by',
139 - [
140 - 'label' => __('Exclude By', 'ultimate-store-kit'),
141 - 'type' => Controls_Manager::SELECT2,
142 - 'multiple' => true,
143 - 'label_block' => true,
144 - 'options' => [
145 - 'authors' => __('Authors', 'ultimate-store-kit'),
146 - 'current_post' => __('Current Post', 'ultimate-store-kit'),
147 - 'manual_selection' => __('Manual Selection', 'ultimate-store-kit'),
148 - 'terms' => __('Terms', 'ultimate-store-kit'),
149 - ],
150 - 'condition' => [
151 - 'product_source!' => ['manual_selection', 'current_query'],
152 - ]
153 - ]
154 - );
155 -
156 - $this->add_control(
157 - 'posts_exclude_ids',
158 - [
159 - 'label' => __('Search & Select', 'ultimate-store-kit'),
160 - 'type' => Dynamic_Select::TYPE,
161 - 'multiple' => true,
162 - 'label_block' => true,
163 - 'query_args' => [
164 - 'query' => 'posts',
165 - 'widget_props' => [
166 - 'post_type' => 'product_source'
167 - ]
168 - ],
169 - 'condition' => [
170 - 'product_source!' => ['manual_selection', 'current_query'],
171 - 'product_exclude_by' => 'manual_selection',
172 - ]
173 - ]
174 - );
175 -
176 - $this->add_control(
177 - 'product_exclude_author_ids',
178 - [
179 - 'label' => __('Authors', 'ultimate-store-kit'),
180 - 'type' => Dynamic_Select::TYPE,
181 - 'multiple' => true,
182 - 'label_block' => true,
183 - 'query_args' => [
184 - 'query' => 'authors',
185 - ],
186 - 'condition' => [
187 - 'product_exclude_by' => 'authors',
188 - 'product_source!' => ['manual_selection', 'current_query'],
189 - ]
190 - ]
191 - );
192 -
193 - $this->add_control(
194 - 'product_exclude_term_ids',
195 - [
196 - 'label' => __('Terms', 'ultimate-store-kit'),
197 - 'description' => __('Terms are items in a taxonomy. The available taxonomies are: Categories, Tags, Formats and custom taxonomies.', 'ultimate-store-kit'),
198 - 'type' => Dynamic_Select::TYPE,
199 - 'multiple' => true,
200 - 'label_block' => true,
201 - 'placeholder' => __('Type and select terms', 'ultimate-store-kit'),
202 - 'query_args' => [
203 - 'query' => 'terms',
204 - 'widget_props' => [
205 - 'post_type' => 'product_source'
206 - ]
207 - ],
208 - 'condition' => [
209 - 'product_exclude_by' => 'terms',
210 - 'product_source!' => ['manual_selection', 'current_query', '_related_post_type'],
211 - ]
212 - ]
213 - );
214 -
215 - $this->end_controls_tab();
216 - $this->end_controls_tabs();
217 -
218 - $this->add_control(
219 - 'product_divider',
220 - [
221 - 'type' => Controls_Manager::DIVIDER,
222 - 'condition' => [
223 - 'product_source!' => 'current_query',
224 - ]
225 - ]
226 - );
227 - $this->add_control(
228 - 'product_select_date',
229 - [
230 - 'label' => __('Date', 'ultimate-store-kit'),
231 - 'type' => Controls_Manager::SELECT,
232 - 'default' => 'anytime',
233 - 'options' => [
234 - 'anytime' => __('All', 'ultimate-store-kit'),
235 - 'today' => __('Past Day', 'ultimate-store-kit'),
236 - 'week' => __('Past Week', 'ultimate-store-kit'),
237 - 'month' => __('Past Month', 'ultimate-store-kit'),
238 - 'quarter' => __('Past Quarter', 'ultimate-store-kit'),
239 - 'year' => __('Past Year', 'ultimate-store-kit'),
240 - 'exact' => __('Custom', 'ultimate-store-kit'),
241 - ],
242 - 'condition' => [
243 - 'product_source!' => 'current_query',
244 - ]
245 - ]
246 - );
247 -
248 - $this->add_control(
249 - 'product_date_before',
250 - [
251 - 'label' => __('Before', 'ultimate-store-kit'),
252 - 'type' => Controls_Manager::DATE_TIME,
253 - 'description' => __('Setting a ‘Before’ date will show all the posts published until the chosen date (inclusive).', 'ultimate-store-kit'),
254 - 'condition' => [
255 - 'product_select_date' => 'exact',
256 - 'product_source!' => 'current_query',
257 - ]
258 - ]
259 - );
260 -
261 - $this->add_control(
262 - 'product_date_after',
263 - [
264 - 'label' => __('After', 'ultimate-store-kit'),
265 - 'type' => Controls_Manager::DATE_TIME,
266 - 'description' => __('Setting an ‘After’ date will show all the posts published since the chosen date (inclusive).', 'ultimate-store-kit'),
267 - 'condition' => [
268 - 'product_select_date' => 'exact',
269 - 'product_source!' => 'current_query',
270 - ]
271 - ]
272 - );
273 -
274 - $this->add_control(
275 - 'product_orderby',
276 - [
277 - 'label' => __('Order By', 'bdthemes-prime-slider'),
278 - 'type' => Controls_Manager::SELECT,
279 - 'default' => 'date',
280 - 'options' => [
281 - 'title' => __('Title', 'bdthemes-prime-slider'),
282 - 'ID' => __('ID', 'bdthemes-prime-slider'),
283 - 'date' => __('Date', 'bdthemes-prime-slider'),
284 - 'author' => __('Author', 'bdthemes-prime-slider'),
285 - 'comment_count' => __('Comment Count', 'bdthemes-prime-slider'),
286 - 'menu_order' => __('Menu Order', 'bdthemes-prime-slider'),
287 - 'rand' => __('Random', 'bdthemes-prime-slider'),
288 - 'price' => __('Price', 'bdthemes-prime-slider'),
289 - 'sales' => __('Sales', 'bdthemes-prime-slider'),
290 - ]
291 - ]
292 - );
293 - $this->add_control(
294 - 'product_order',
295 - [
296 - 'label' => __('Order', 'ultimate-store-kit'),
297 - 'type' => Controls_Manager::SELECT,
298 - 'default' => 'desc',
299 - 'options' => [
300 - 'asc' => __('ASC', 'ultimate-store-kit'),
301 - 'desc' => __('DESC', 'ultimate-store-kit'),
302 - ],
303 - 'condition' => [
304 - 'product_source!' => 'current_query',
305 - ]
306 - ]
307 - );
308 - // Others Features
309 - }
310 -
311 - public function register_controls_wc_additional() {
312 - $this->add_control(
313 - 'product_show_only',
314 - [
315 - 'label' => esc_html__('Show Product', 'bdthemes-element-pack'),
316 - 'type' => Controls_Manager::SELECT,
317 - 'default' => 'all',
318 - 'options' => [
319 - 'all' => esc_html__('All Products', 'bdthemes-element-pack'),
320 - 'onsale' => esc_html__('On Sale', 'bdthemes-element-pack'),
321 - 'featured' => esc_html__('Featured', 'bdthemes-element-pack'),
322 - ]
323 - ]
324 - );
325 - $this->add_control(
326 - 'product_hide_free',
327 - [
328 - 'label' => esc_html__('Hide Free Product', 'ultimate-store-kit'),
329 - 'type' => Controls_Manager::SWITCHER,
330 -
331 - ]
332 - );
333 - $this->add_control(
334 - 'product_hide_out_stock',
335 - [
336 - 'label' => esc_html__('Hide Out of Stock', 'ultimate-store-kit'),
337 - 'type' => Controls_Manager::SWITCHER,
338 - ]
339 - );
340 - $this->add_control(
341 - 'product_only_with_featured_image',
342 - [
343 - 'label' => __('Only Featured Image Post', 'ultimate-store-kit'),
344 - 'description' => __('Enable to display posts only when featured image is present.', 'ultimate-store-kit'),
345 - 'type' => Controls_Manager::SWITCHER,
346 - 'return_value' => 'yes',
347 - 'condition' => [
348 - 'product_source!' => 'current_query',
349 - ]
350 - ]
351 - );
352 - $this->add_control(
353 - 'query_id',
354 - [
355 - 'label' => __('Query ID', 'ultimate-store-kit'),
356 - 'description' => __('Give your Query a custom unique id to allow server side filtering', 'ultimate-store-kit'),
357 - 'type' => Controls_Manager::TEXT,
358 - 'separator' => 'before',
359 - ]
360 - );
361 - }
362 -
363 - private function setMetaQueryArgs() {
364 -
365 - $args = [];
366 -
367 - if ('current_query' === $this->getGroupControlQueryPostType()) {
368 - return [];
369 - }
370 - $args['paged'] = max(1, get_query_var('paged'), get_query_var('page'));
371 - $args['posts_per_page'] = $this->get_settings('product_limit');
372 - $args['order'] = $this->get_settings('product_order');
373 - // $args['orderby'] = $this->get_settings('product_orderby');
374 -
375 - if ('product' === $this->getGroupControlQueryPostType()) {
376 - $product_visibility_term_ids = wc_get_product_visibility_term_ids();
377 - if ('yes' == $this->get_settings('product_hide_free')) {
378 - $args['meta_query'][] = [
379 - 'key' => '_price',
380 - 'value' => 0,
381 - 'compare' => '>',
382 - 'type' => 'DECIMAL',
383 - ];
384 - }
385 -
386 - if ('yes' == $this->get_settings('product_hide_out_stock')) {
387 - $args['tax_query'][] = [
388 - [
389 - 'taxonomy' => 'product_visibility',
390 - 'field' => 'term_taxonomy_id',
391 - 'terms' => $product_visibility_term_ids['outofstock'],
392 - 'operator' => 'NOT IN',
393 - ],
394 - ]; // WPCS: slow query ok.
395 - }
396 - switch ($this->get_settings('product_show_only')) {
397 - case 'featured':
398 - $args['tax_query'][] = [
399 - 'taxonomy' => 'product_visibility',
400 - 'field' => 'term_taxonomy_id',
401 - 'terms' => $product_visibility_term_ids['featured'],
402 - ];
403 - break;
404 - case 'onsale':
405 - $product_ids_on_sale = wc_get_product_ids_on_sale();
406 - $product_ids_on_sale[] = 0;
407 - $args['post__in'] = $product_ids_on_sale;
408 - break;
409 - }
410 - switch ($this->get_settings('product_orderby')) {
411 - case 'price':
412 - $args['meta_key'] = '_price'; // WPCS: slow query ok.
413 - $args['orderby'] = 'meta_value_num';
414 - break;
415 - case 'sales':
416 - $args['meta_key'] = 'total_sales'; // WPCS: slow query ok.
417 - $args['orderby'] = 'meta_value_num';
418 - break;
419 - default:
420 - $args['orderby'] = $this->get_settings('product_orderby');
421 - }
422 - }
423 -
424 -
425 -
426 - /**
427 - * Set Feature Images
428 - */
429 - if ($this->get_settings('product_only_with_featured_image') === 'yes') {
430 - $args['meta_key'] = '_thumbnail_id';
431 - }
432 -
433 - /**
434 - * Set Date
435 - */
436 -
437 - $selected_date = $this->get_settings('product_select_date');
438 -
439 - if (!empty($selected_date)) {
440 - $date_query = [];
441 -
442 - switch ($selected_date) {
443 - case 'today':
444 - $date_query['after'] = '-1 day';
445 - break;
446 -
447 - case 'week':
448 - $date_query['after'] = '-1 week';
449 - break;
450 -
451 - case 'month':
452 - $date_query['after'] = '-1 month';
453 - break;
454 -
455 - case 'quarter':
456 - $date_query['after'] = '-3 month';
457 - break;
458 -
459 - case 'year':
460 - $date_query['after'] = '-1 year';
461 - break;
462 -
463 - case 'exact':
464 - $after_date = $this->get_settings('product_date_after');
465 - if (!empty($after_date)) {
466 - $date_query['after'] = $after_date;
467 - }
468 -
469 - $before_date = $this->get_settings('product_date_before');
470 - if (!empty($before_date)) {
471 - $date_query['before'] = $before_date;
472 - }
473 -
474 - $date_query['inclusive'] = true;
475 - break;
476 - }
477 -
478 - if (!empty($date_query)) {
479 - $args['date_query'] = $date_query;
480 - }
481 - }
482 -
483 - return $args;
484 - }
485 -
486 - protected function getGroupControlQueryArgs() {
487 -
488 - $settings = $this->get_settings();
489 - $args = $this->setMetaQueryArgs();
490 - $args['post_status'] = 'publish';
491 - $args['suppress_filters'] = false;
492 - $exclude_by = $this->getGroupControlQueryParamBy('exclude');
493 -
494 - // /**
495 - // * Set Ignore Sticky
496 - // */
497 - // if (
498 - // $this->getGroupControlQueryPostType() === 'post'
499 - // && $this->get_settings('posts_ignore_sticky_posts') === 'yes'
500 - // ) {
501 - // $args['ignore_sticky_posts'] = true;
502 -
503 - // if (in_array('current_post', $exclude_by)) {
504 - // $args['post__not_in'] = [get_the_ID()];
505 - // }
506 - // }
507 -
508 -
509 - if ($this->getGroupControlQueryPostType() === 'manual_selection') {
510 - /**
511 - * Set Including Manually
512 - */
513 - $selected_ids = $this->get_settings('product_selected_ids');
514 - $selected_ids = wp_parse_id_list($selected_ids);
515 - $args['post_type'] = 'product';
516 - if (!empty($selected_ids)) {
517 - $args['post__in'] = $selected_ids;
518 - }
519 - $args['ignore_sticky_posts'] = 1;
520 - } elseif ('current_query' === $this->getGroupControlQueryPostType()) {
521 - /**
522 - * Make Current Query
523 - */
524 - $args = $GLOBALS['wp_query']->query_vars;
525 - $args['paged'] = 1;
526 - $args = apply_filters('ultimate_store_kit/query/get_query_args/current_query', $args);
527 - } elseif ('_related_post_type' === $this->getGroupControlQueryPostType()) {
528 - /**
529 - * Set Related Query
530 - */
531 - $post_id = get_queried_object_id();
532 - $related_post_id = is_singular() && (0 !== $post_id) ? $post_id : null;
533 - $args['post_type'] = get_post_type($related_post_id);
534 -
535 - $include_by = $this->getGroupControlQueryParamBy('include');
536 - if (in_array('authors', $include_by)) {
537 - $args['author__in'] = wp_parse_id_list($settings['product_include_author_ids']);
538 - } else {
539 - $args['author__in'] = get_post_field('post_author', $related_post_id);
540 - }
541 -
542 - $exclude_by = $this->getGroupControlQueryParamBy('exclude');
543 - if (in_array('authors', $exclude_by)) {
544 - $args['author__not_in'] = wp_parse_id_list($settings['product_exclude_author_ids']);
545 - }
546 -
547 - if (in_array('current_post', $exclude_by)) {
548 - $args['post__not_in'] = [get_the_ID()];
549 - }
550 -
551 - $args['ignore_sticky_posts'] = 1;
552 - $args = apply_filters('ultimate_store_kit/query/get_query_args/related_query', $args);
553 - } else {
554 -
555 - /**
556 - * Set Post Type
557 - */
558 - $args['post_type'] = $this->getGroupControlQueryPostType();
559 -
560 - /**
561 - * Set Exclude Post
562 - */
563 - $exclude_by = $this->getGroupControlQueryParamBy('exclude');
564 - $current_post = [];
565 -
566 - if (in_array('current_post', $exclude_by) && is_singular()) {
567 - $current_post = [get_the_ID()];
568 - }
569 -
570 - if (in_array('manual_selection', $exclude_by)) {
571 - $exclude_ids = $settings['posts_exclude_ids'];
572 - $args['post__not_in'] = array_merge($current_post, wp_parse_id_list($exclude_ids));
573 - }
574 - /**
575 - * Set Authors
576 - */
577 - $include_by = $this->getGroupControlQueryParamBy('include');
578 - $exclude_by = $this->getGroupControlQueryParamBy('exclude');
579 - $include_users = [];
580 - $exclude_users = [];
581 -
582 - if (in_array('authors', $include_by)) {
583 - $include_users = wp_parse_id_list($settings['product_include_author_ids']);
584 - }
585 -
586 - if (in_array('authors', $exclude_by)) {
587 - $exclude_users = wp_parse_id_list($settings['product_exclude_author_ids']);
588 - $include_users = array_diff($include_users, $exclude_users);
589 - }
590 -
591 - if (!empty($include_users)) {
592 - $args['author__in'] = $include_users;
593 - }
594 -
595 - if (!empty($exclude_users)) {
596 - $args['author__not_in'] = $exclude_users;;
597 - }
598 -
599 - /**
600 - * Set Taxonomy
601 - */
602 - $include_by = $this->getGroupControlQueryParamBy('include');
603 - $exclude_by = $this->getGroupControlQueryParamBy('exclude');
604 - $include_terms = [];
605 - $exclude_terms = [];
606 - $terms_query = [];
607 -
608 - if (in_array('terms', $include_by)) {
609 - $include_terms = wp_parse_id_list($settings['product_include_term_ids']);
610 - }
611 -
612 - if (in_array('terms', $exclude_by)) {
613 - $exclude_terms = wp_parse_id_list($settings['product_exclude_term_ids']);
614 - $include_terms = array_diff($include_terms, $exclude_terms);
615 - }
616 -
617 - if (!empty($include_terms)) {
618 - $tax_terms_map = $this->mapGroupControlQuery($include_terms);
619 -
620 - foreach ($tax_terms_map as $tax => $terms) {
621 - $terms_query[] = [
622 - 'taxonomy' => $tax,
623 - 'field' => 'term_id',
624 - 'terms' => $terms,
625 - 'operator' => 'IN',
626 - ];
627 - }
628 - }
629 -
630 - if (!empty($exclude_terms)) {
631 - $tax_terms_map = $this->mapGroupControlQuery($exclude_terms);
632 -
633 - foreach ($tax_terms_map as $tax => $terms) {
634 - $terms_query[] = [
635 - 'taxonomy' => $tax,
636 - 'field' => 'term_id',
637 - 'terms' => $terms,
638 - 'operator' => 'NOT IN',
639 - ];
640 - }
641 - }
642 -
643 - if (!empty($terms_query)) {
644 - $args['tax_query'] = $terms_query;
645 - $args['tax_query']['relation'] = 'AND';
646 - }
647 - }
648 -
649 - $query_id = $this->get_settings('query_id');
650 - if (!empty($query_id)) {
651 - add_action('pre_get_posts', [$this, 'pre_get_posts_query_filter']);
652 - }
653 -
654 - return $args;
655 - }
656 -
657 -
658 - /**
659 - * @return mixed
660 - */
661 - private function getGroupControlQueryPostType() {
662 - return $this->get_settings('product_source');
663 - }
664 -
665 - /**
666 - * Get Query Params by args
667 - *
668 - * @param string $by
669 - *
670 - * @return array|mixed
671 - */
672 - private function getGroupControlQueryParamBy($by = 'exclude') {
673 - $mapBy = [
674 - 'exclude' => 'product_exclude_by',
675 - 'include' => 'product_include_by',
676 - ];
677 -
678 - $setting = $this->get_settings($mapBy[$by]);
679 -
680 - return (!empty($setting) ? $setting : []);
681 - }
682 -
683 - /**
684 - * @param array $term_ids
685 - *
686 - * @return array
687 - */
688 - private function mapGroupControlQuery($term_ids = []) {
689 - $terms = get_terms(
690 - [
691 - 'term_taxonomy_id' => $term_ids,
692 - 'hide_empty' => false,
693 - ]
694 - );
695 -
696 - $tax_terms_map = [];
697 -
698 - foreach ($terms as $term) {
699 - $taxonomy = $term->taxonomy;
700 - $tax_terms_map[$taxonomy][] = $term->term_id;
701 - }
702 -
703 - return $tax_terms_map;
704 - }
705 -
706 - /**
707 - * @return array|string[]|\WP_Post_Type[]
708 - */
709 - private function getGroupControlQueryPostTypes() {
710 -
711 - $post_types = [
712 - 'product' => __('Product', 'ultimate-store-kit'),
713 - 'manual_selection' => __('Manual Selection', 'ultimate-store-kit'),
714 - 'current_query' => __('Current Query', 'ultimate-store-kit'),
715 - '_related_post_type' => __('Related', 'ultimate-store-kit'),
716 - ];
717 -
718 - // $post_types = array_merge($post_types, $extra_types);
719 -
720 - return $post_types;
721 - }
722 -
723 - public function pre_get_posts_query_filter($wp_query) {
724 - if ($this) {
725 - $query_id = $this->get_settings('query_id');
726 - do_action("ultimate_store_kit/query/{$query_id}", $wp_query, $this);
727 - }
728 - }
729 -}
1 +<?php
2 +
3 +namespace UltimateStoreKit\Includes\Controls\GroupQuery;
4 +
5 +use Elementor\Controls_Manager;
6 +use UltimateStoreKit\Includes\Controls\SelectInput\Dynamic_Select;
7 +
8 +if (! defined('ABSPATH')) {
9 + exit; // Exit if accessed directly.
10 +}
11 +
12 +// phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams -- WordPressVIPMinimum targets the VIP platform, not the plugin directory. These exclusionary parameters come from a widget's own "exclude" control: the list is whatever the site owner picked in Elementor, applied to a bounded result set, not an unbounded catalogue scan.
13 +// phpcs:disable WordPress.DB.SlowDBQuery -- meta_query / tax_query / meta_key are the documented way to express these widget filters. Removing them means fetching every post and filtering in PHP, which is strictly slower.
14 +
15 +trait Group_Control_Query
16 +{
17 +
18 + public function register_query_builder_controls()
19 + {
20 +
21 + $this->add_control(
22 + 'product_source',
23 + [
24 + 'label' => __('Source', 'ultimate-store-kit'),
25 + 'type' => Controls_Manager::SELECT,
26 + 'options' => $this->getGroupControlQueryPostTypes(),
27 + 'default' => 'product',
28 + ]
29 + );
30 + $this->add_control(
31 + 'product_limit',
32 + [
33 + 'label' => esc_html__('Product Limit', 'ultimate-store-kit'),
34 + 'type' => Controls_Manager::NUMBER,
35 + 'default' => 9,
36 + ]
37 + );
38 +
39 + $this->add_control(
40 + 'product_selected_ids',
41 + [
42 + 'label' => __('Search & Select', 'ultimate-store-kit'),
43 + 'type' => Dynamic_Select::TYPE,
44 + 'multiple' => true,
45 + 'label_block' => true,
46 + 'query_args' => [
47 + 'query' => 'posts',
48 + 'post_type' => 'product',
49 + ],
50 + 'condition' => [
51 + 'product_source' => 'manual_selection',
52 + ]
53 + ]
54 + );
55 +
56 + $this->start_controls_tabs(
57 + 'tabs_product_include_exclude',
58 + [
59 + 'condition' => [
60 + 'product_source!' => ['manual_selection', 'current_query'],
61 + ]
62 + ]
63 + );
64 +
65 + $this->start_controls_tab(
66 + 'tab_product_include',
67 + [
68 + 'label' => __('Include', 'ultimate-store-kit'),
69 + 'condition' => [
70 + 'product_source!' => ['manual_selection'],
71 + ]
72 + ]
73 + );
74 +
75 + $this->add_control(
76 + 'product_include_by',
77 + [
78 + 'label' => __('Include By', 'ultimate-store-kit'),
79 + 'type' => Controls_Manager::SELECT2,
80 + 'multiple' => true,
81 + 'label_block' => true,
82 + 'options' => [
83 + 'categories' => __('Categories', 'ultimate-store-kit'),
84 + 'tags' => __('Tags', 'ultimate-store-kit'),
85 + 'brands' => __('Brands', 'ultimate-store-kit'),
86 + 'attributes' => __('Attributes', 'ultimate-store-kit'),
87 + 'date' => __('Date', 'ultimate-store-kit'),
88 + 'image' => __('Product Image', 'ultimate-store-kit'),
89 + 'price' => __('Price', 'ultimate-store-kit'),
90 + 'stock' => __('Stock', 'ultimate-store-kit'),
91 + ],
92 + 'condition' => [
93 + 'product_source!' => ['manual_selection'],
94 + ]
95 + ]
96 + );
97 +
98 + $this->add_control(
99 + 'product_include_category_ids',
100 + [
101 + 'label' => __('Categories', 'ultimate-store-kit'),
102 + 'type' => Dynamic_Select::TYPE,
103 + 'multiple' => true,
104 + 'label_block' => true,
105 + 'query_args' => [
106 + 'query' => 'product_cat',
107 + ],
108 + 'condition' => [
109 + 'product_include_by' => 'categories',
110 + 'product_source!' => ['manual_selection'],
111 + ]
112 + ]
113 + );
114 +
115 + $this->add_control(
116 + 'product_include_tag_ids',
117 + [
118 + 'label' => __('Tags', 'ultimate-store-kit'),
119 + 'type' => Dynamic_Select::TYPE,
120 + 'multiple' => true,
121 + 'label_block' => true,
122 + 'query_args' => [
123 + 'query' => 'product_tag',
124 + ],
125 + 'condition' => [
126 + 'product_include_by' => 'tags',
127 + 'product_source!' => ['manual_selection'],
128 + ]
129 + ]
130 + );
131 +
132 + $this->add_control(
133 + 'product_include_brand_ids',
134 + [
135 + 'label' => __('Brands', 'ultimate-store-kit'),
136 + 'type' => Dynamic_Select::TYPE,
137 + 'multiple' => true,
138 + 'label_block' => true,
139 + 'query_args' => [
140 + 'query' => 'product_brand',
141 + ],
142 + 'condition' => [
143 + 'product_include_by' => 'brands',
144 + 'product_source!' => ['manual_selection'],
145 + ]
146 + ]
147 + );
148 +
149 + $this->add_control(
150 + 'product_include_attribute_ids',
151 + [
152 + 'label' => __('Attributes', 'ultimate-store-kit'),
153 + 'type' => Dynamic_Select::TYPE,
154 + 'multiple' => true,
155 + 'label_block' => true,
156 + 'query_args' => [
157 + 'query' => 'product_attributes',
158 + ],
159 + 'condition' => [
160 + 'product_include_by' => 'attributes',
161 + 'product_source!' => ['manual_selection'],
162 + ]
163 + ]
164 + );
165 +
166 + $this->add_control(
167 + 'product_include_date',
168 + [
169 + 'label' => __('Date', 'ultimate-store-kit'),
170 + 'type' => Controls_Manager::SELECT,
171 + 'default' => 'anytime',
172 + 'options' => [
173 + 'anytime' => __('All', 'ultimate-store-kit'),
174 + 'today' => __('Past Day', 'ultimate-store-kit'),
175 + 'week' => __('Past Week', 'ultimate-store-kit'),
176 + 'month' => __('Past Month', 'ultimate-store-kit'),
177 + 'quarter' => __('Past Quarter', 'ultimate-store-kit'),
178 + 'year' => __('Past Year', 'ultimate-store-kit'),
179 + 'exact' => __('Custom', 'ultimate-store-kit'),
180 + ],
181 + 'condition' => [
182 + 'product_source!' => 'manual_selection',
183 + 'product_include_by' => 'date',
184 + ]
185 + ]
186 + );
187 +
188 + $this->add_control(
189 + 'product_date_before',
190 + [
191 + 'label' => __('Before', 'ultimate-store-kit'),
192 + 'type' => Controls_Manager::DATE_TIME,
193 + 'description' => __('Setting a "Before" date will show all the posts published until the chosen date (inclusive).', 'ultimate-store-kit'),
194 + 'condition' => [
195 + 'product_include_date' => 'exact',
196 + 'product_source!' => 'manual_selection',
197 + ]
198 + ]
199 + );
200 +
201 + $this->add_control(
202 + 'product_date_after',
203 + [
204 + 'label' => __('After', 'ultimate-store-kit'),
205 + 'type' => Controls_Manager::DATE_TIME,
206 + 'description' => __('Setting an "After" date will show all the posts published since the chosen date (inclusive).', 'ultimate-store-kit'),
207 + 'condition' => [
208 + 'product_include_date' => 'exact',
209 + 'product_source!' => 'manual_selection',
210 + ]
211 + ]
212 + );
213 +
214 + $this->add_control(
215 + 'product_image_include',
216 + [
217 + 'label' => esc_html__('Product Image', 'ultimate-store-kit'),
218 + 'type' => \Elementor\Controls_Manager::SELECT,
219 + 'default' => '',
220 + 'options' => [
221 + '' => esc_html__('Default', 'ultimate-store-kit'),
222 + 'set' => esc_html__('Set', 'ultimate-store-kit'),
223 + 'not_set' => esc_html__('Not Set', 'ultimate-store-kit'),
224 + ],
225 + 'condition' => [
226 + 'product_include_by' => 'image',
227 + 'product_source!' => ['manual_selection'],
228 + ]
229 + ]
230 + );
231 +
232 + $this->add_control(
233 + 'product_price_include',
234 + [
235 + 'label' => esc_html__('Price', 'ultimate-store-kit'),
236 + 'type' => \Elementor\Controls_Manager::SELECT,
237 + 'default' => '',
238 + 'options' => [
239 + '' => esc_html__('Default', 'ultimate-store-kit'),
240 + 'set' => esc_html__('Set', 'ultimate-store-kit'),
241 + 'not_set' => esc_html__('Not Set', 'ultimate-store-kit'),
242 + ],
243 + 'condition' => [
244 + 'product_include_by' => 'price',
245 + 'product_source!' => ['manual_selection'],
246 + ]
247 + ]
248 + );
249 +
250 + $this->add_control(
251 + 'product_stock_include',
252 + [
253 + 'label' => esc_html__('Stock', 'ultimate-store-kit'),
254 + 'type' => \Elementor\Controls_Manager::SELECT,
255 + 'default' => '',
256 + 'options' => [
257 + '' => esc_html__('Default', 'ultimate-store-kit'),
258 + 'in_stock' => esc_html__('In Stock', 'ultimate-store-kit'),
259 + 'out_of_stock' => esc_html__('Out of Stock', 'ultimate-store-kit'),
260 + ],
261 + 'condition' => [
262 + 'product_include_by' => 'stock',
263 + 'product_source!' => ['manual_selection'],
264 + ]
265 + ]
266 + );
267 +
268 +
269 + $this->end_controls_tab();
270 +
271 + $this->start_controls_tab(
272 + 'tab_product_exclude',
273 + [
274 + 'label' => __('Exclude', 'ultimate-store-kit'),
275 + 'condition' => [
276 + 'product_source!' => ['manual_selection', 'current_query'],
277 + ]
278 + ]
279 + );
280 +
281 + $this->add_control(
282 + 'product_exclude_by',
283 + [
284 + 'label' => __('Exclude By', 'ultimate-store-kit'),
285 + 'type' => Controls_Manager::SELECT2,
286 + 'multiple' => true,
287 + 'label_block' => true,
288 + 'options' => [
289 + 'categories' => __('Categories', 'ultimate-store-kit'),
290 + 'tags' => __('Tags', 'ultimate-store-kit'),
291 + 'brands' => __('Brands', 'ultimate-store-kit'),
292 + 'attributes' => __('Attributes', 'ultimate-store-kit'),
293 + 'manual_selection' => __('Manual Selection', 'ultimate-store-kit'),
294 + 'date' => __('Date', 'ultimate-store-kit'),
295 + 'image' => __('Product Image', 'ultimate-store-kit'),
296 + 'price' => __('Price', 'ultimate-store-kit'),
297 + 'stock' => __('Stock', 'ultimate-store-kit'),
298 + ],
299 + 'condition' => [
300 + 'product_source!' => ['manual_selection'],
301 + ]
302 + ]
303 + );
304 +
305 + $this->add_control(
306 + 'posts_exclude_ids',
307 + [
308 + 'label' => __('Search & Select', 'ultimate-store-kit'),
309 + 'type' => Dynamic_Select::TYPE,
310 + 'multiple' => true,
311 + 'label_block' => true,
312 + 'query_args' => [
313 + 'query' => 'posts',
314 + 'post_type' => 'product',
315 + ],
316 + 'condition' => [
317 + 'product_source!' => ['manual_selection'],
318 + 'product_exclude_by' => 'manual_selection',
319 + ]
320 + ]
321 + );
322 +
323 + $this->add_control(
324 + 'product_exclude_category_ids',
325 + [
326 + 'label' => __('Categories', 'ultimate-store-kit'),
327 + 'type' => Dynamic_Select::TYPE,
328 + 'multiple' => true,
329 + 'label_block' => true,
330 + 'query_args' => [
331 + 'query' => 'product_cat',
332 + ],
333 + 'condition' => [
334 + 'product_exclude_by' => 'categories',
335 + 'product_source!' => ['manual_selection'],
336 + ]
337 + ]
338 + );
339 + $this->add_control(
340 + 'product_exclude_tag_ids',
341 + [
342 + 'label' => __('Tags', 'ultimate-store-kit'),
343 + 'type' => Dynamic_Select::TYPE,
344 + 'multiple' => true,
345 + 'label_block' => true,
346 + 'query_args' => [
347 + 'query' => 'product_tag',
348 + ],
349 + 'condition' => [
350 + 'product_exclude_by' => 'tags',
351 + 'product_source!' => ['manual_selection'],
352 + ]
353 + ]
354 + );
355 + $this->add_control(
356 + 'product_exclude_brand_ids',
357 + [
358 + 'label' => __('Brands', 'ultimate-store-kit'),
359 + 'type' => Dynamic_Select::TYPE,
360 + 'multiple' => true,
361 + 'label_block' => true,
362 + 'query_args' => [
363 + 'query' => 'product_brand',
364 + ],
365 + 'condition' => [
366 + 'product_exclude_by' => 'brands',
367 + 'product_source!' => ['manual_selection'],
368 + ]
369 + ]
370 + );
371 + $this->add_control(
372 + 'product_exclude_attribute_ids',
373 + [
374 + 'label' => __('Attributes', 'ultimate-store-kit'),
375 + 'type' => Dynamic_Select::TYPE,
376 + 'multiple' => true,
377 + 'label_block' => true,
378 + 'query_args' => [
379 + 'query' => 'product_attributes',
380 + ],
381 + 'condition' => [
382 + 'product_exclude_by' => 'attributes',
383 + 'product_source!' => ['manual_selection'],
384 + ]
385 + ]
386 + );
387 +
388 + $this->add_control(
389 + 'product_exclude_date',
390 + [
391 + 'label' => __('Date', 'ultimate-store-kit'),
392 + 'type' => Controls_Manager::SELECT,
393 + 'default' => 'anytime',
394 + 'options' => [
395 + 'anytime' => __('All', 'ultimate-store-kit'),
396 + 'today' => __('Past Day', 'ultimate-store-kit'),
397 + 'week' => __('Past Week', 'ultimate-store-kit'),
398 + 'month' => __('Past Month', 'ultimate-store-kit'),
399 + 'quarter' => __('Past Quarter', 'ultimate-store-kit'),
400 + 'year' => __('Past Year', 'ultimate-store-kit'),
401 + 'exact' => __('Custom', 'ultimate-store-kit'),
402 + ],
403 + 'condition' => [
404 + 'product_exclude_by' => 'date',
405 + 'product_source!' => ['manual_selection'],
406 + ]
407 + ]
408 + );
409 +
410 + $this->add_control(
411 + 'product_exclude_date_before',
412 + [
413 + 'label' => __('Before', 'ultimate-store-kit'),
414 + 'type' => Controls_Manager::DATE_TIME,
415 + 'description' => __('Show all posts published until the chosen date (inclusive).', 'ultimate-store-kit'),
416 + 'condition' => [
417 + 'product_exclude_date' => 'exact',
418 + 'product_source!' => ['manual_selection'],
419 + ]
420 + ]
421 + );
422 +
423 + $this->add_control(
424 + 'product_exclude_date_after',
425 + [
426 + 'label' => __('After', 'ultimate-store-kit'),
427 + 'type' => Controls_Manager::DATE_TIME,
428 + 'description' => __('Show all posts published since the chosen date (inclusive).', 'ultimate-store-kit'),
429 + 'condition' => [
430 + 'product_exclude_date' => 'exact',
431 + 'product_source!' => ['manual_selection'],
432 + ]
433 + ]
434 + );
435 +
436 + $this->add_control(
437 + 'product_image_exclude',
438 + [
439 + 'label' => esc_html__('Product Image', 'ultimate-store-kit'),
440 + 'type' => \Elementor\Controls_Manager::SELECT,
441 + 'default' => '',
442 + 'options' => [
443 + '' => esc_html__('Default', 'ultimate-store-kit'),
444 + 'set' => esc_html__('Set', 'ultimate-store-kit'),
445 + 'not_set' => esc_html__('Not Set', 'ultimate-store-kit'),
446 + ],
447 + 'condition' => [
448 + 'product_exclude_by' => 'image',
449 + 'product_source!' => ['manual_selection'],
450 + ]
451 + ]
452 + );
453 +
454 + $this->add_control(
455 + 'product_price_exclude',
456 + [
457 + 'label' => esc_html__('Price', 'ultimate-store-kit'),
458 + 'type' => \Elementor\Controls_Manager::SELECT,
459 + 'default' => '',
460 + 'options' => [
461 + '' => esc_html__('Default', 'ultimate-store-kit'),
462 + 'set' => esc_html__('Set', 'ultimate-store-kit'),
463 + 'not_set' => esc_html__('Not Set', 'ultimate-store-kit'),
464 + ],
465 + 'condition' => [
466 + 'product_exclude_by' => 'price',
467 + 'product_source!' => ['manual_selection'],
468 + ]
469 + ]
470 + );
471 +
472 + $this->add_control(
473 + 'product_stock_exclude',
474 + [
475 + 'label' => esc_html__('Stock', 'ultimate-store-kit'),
476 + 'type' => \Elementor\Controls_Manager::SELECT,
477 + 'default' => '',
478 + 'options' => [
479 + '' => esc_html__('Default', 'ultimate-store-kit'),
480 + 'in_stock' => esc_html__('In Stock', 'ultimate-store-kit'),
481 + 'out_of_stock' => esc_html__('Out of Stock', 'ultimate-store-kit'),
482 + ],
483 + 'condition' => [
484 + 'product_exclude_by' => 'stock',
485 + 'product_source!' => ['manual_selection'],
486 + ]
487 + ]
488 + );
489 +
490 + $this->add_control(
491 + 'product_exclude_term_ids',
492 + [
493 + 'label' => __('Terms', 'ultimate-store-kit'),
494 + 'description' => __('Terms are items in a taxonomy. The available taxonomies are: Categories, Tags, Formats and custom taxonomies.', 'ultimate-store-kit'),
495 + 'type' => Dynamic_Select::TYPE,
496 + 'multiple' => true,
497 + 'label_block' => true,
498 + 'placeholder' => __('Type and select terms', 'ultimate-store-kit'),
499 + 'query_args' => [
500 + 'query' => 'terms',
501 + 'widget_props' => [
502 + 'post_type' => 'product_source'
503 + ]
504 + ],
505 + 'condition' => [
506 + 'product_exclude_by' => 'terms',
507 + 'product_source!' => ['manual_selection'],
508 + ]
509 + ]
510 + );
511 +
512 + $this->end_controls_tab();
513 + $this->end_controls_tabs();
514 +
515 + $this->add_control(
516 + 'product_divider',
517 + [
518 + 'type' => Controls_Manager::DIVIDER,
519 + 'condition' => [
520 + 'product_source!' => 'current_query',
521 + ]
522 + ]
523 + );
524 +
525 + $this->add_control(
526 + 'product_orderby',
527 + [
528 + 'label' => __('Order By', 'ultimate-store-kit'),
529 + 'type' => Controls_Manager::SELECT,
530 + 'default' => 'date',
531 + 'options' => [
532 + 'title' => __('Title', 'ultimate-store-kit'),
533 + 'ID' => __('ID', 'ultimate-store-kit'),
534 + 'date' => __('Date', 'ultimate-store-kit'),
535 + 'author' => __('Author', 'ultimate-store-kit'),
536 + 'comment_count' => __('Comment Count', 'ultimate-store-kit'),
537 + 'menu_order' => __('Menu Order', 'ultimate-store-kit'),
538 + 'modified' => __('Modified', 'ultimate-store-kit'),
539 + 'rand' => __('Random', 'ultimate-store-kit'),
540 + 'price' => __('Price', 'ultimate-store-kit'),
541 + 'sales' => __('Sales', 'ultimate-store-kit'),
542 + ],
543 + 'condition' => [
544 + 'product_source!' => ['current_query'],
545 + ]
546 + ]
547 + );
548 + $this->add_control(
549 + 'product_order',
550 + [
551 + 'label' => __('Order', 'ultimate-store-kit'),
552 + 'type' => Controls_Manager::SELECT,
553 + 'default' => 'desc',
554 + 'options' => [
555 + 'asc' => __('ASC', 'ultimate-store-kit'),
556 + 'desc' => __('DESC', 'ultimate-store-kit'),
557 + ],
558 + 'condition' => [
559 + 'product_source!' => 'current_query',
560 + ]
561 + ]
562 + );
563 + }
564 +
565 + public function register_controls_wc_additional()
566 + {
567 + $this->add_control(
568 + 'query_id',
569 + [
570 + 'label' => __('Query ID', 'ultimate-store-kit'),
571 + 'description' => __('Give your Query a custom unique id to allow server side filtering', 'ultimate-store-kit'),
572 + 'type' => Controls_Manager::TEXT,
573 + 'separator' => 'before',
574 + 'dynamic' => [ 'active' => true ],
575 + ]
576 + );
577 + }
578 +
579 + /**
580 + * @return array|string[]|\WP_Post_Type[]
581 + */
582 + private function getGroupControlQueryPostTypes()
583 + {
584 +
585 + $post_types = [
586 + 'product' => __('Product', 'ultimate-store-kit'),
587 + 'sale' => __('On Sale', 'ultimate-store-kit'),
588 + 'featured' => __('Featured', 'ultimate-store-kit'),
589 + 'manual_selection' => __('Manual Selection', 'ultimate-store-kit'),
590 + 'current_query' => __('Current Query', 'ultimate-store-kit'),
591 + ];
592 +
593 + return $post_types;
594 + }
595 +
596 + public function pre_get_posts_query_filter($wp_query)
597 + {
598 + if ($this) {
599 + $query_id = $this->get_settings('query_id');
600 + do_action("ultimate_store_kit/query/{$query_id}", $wp_query, $this);
601 + }
602 + }
603 +
604 + private function get_product_source_args($source)
605 + {
606 + $args = [];
607 +
608 + switch ($source) {
609 + case 'sale':
610 + $args['meta_query'] = [
611 + [
612 + 'key' => '_sale_price',
613 + 'value' => 0,
614 + 'compare' => '>',
615 + ],
616 + ];
617 + break;
618 + case 'featured':
619 + $args['tax_query'] = [
620 + [
621 + 'taxonomy' => 'product_visibility',
622 + 'field' => 'name',
623 + 'terms' => 'featured',
624 + 'operator' => 'IN',
625 + ],
626 + ];
627 + break;
628 + case 'manual_selection':
629 + $selected_ids = $this->get_settings('product_selected_ids');
630 + if (!empty($selected_ids)) {
631 + $args['post__in'] = $selected_ids;
632 + }
633 + break;
634 + }
635 +
636 + return $args;
637 + }
638 +
639 + /**
640 + * Apply "Include By" logic to WP_Query args.
641 + */
642 + private function apply_include_by_args($args, $settings)
643 + {
644 + $include_by = $settings['product_include_by'] ?? [];
645 +
646 + if (!is_array($include_by)) {
647 + $include_by = [$include_by];
648 + }
649 +
650 + // Categories
651 + if (in_array('categories', $include_by) && !empty($settings['product_include_category_ids'])) {
652 + $args['tax_query'][] = [
653 + 'taxonomy' => 'product_cat',
654 + 'field' => 'term_id',
655 + 'terms' => $settings['product_include_category_ids'],
656 + 'operator' => 'IN',
657 + ];
658 + }
659 +
660 + // Tags
661 + if (in_array('tags', $include_by) && !empty($settings['product_include_tag_ids'])) {
662 + $args['tax_query'][] = [
663 + 'taxonomy' => 'product_tag',
664 + 'field' => 'term_id',
665 + 'terms' => $settings['product_include_tag_ids'],
666 + 'operator' => 'IN',
667 + ];
668 + }
669 +
670 + // Brands
671 + if (in_array('brands', $include_by) && !empty($settings['product_include_brand_ids'])) {
672 + $args['tax_query'][] = [
673 + 'taxonomy' => 'product_brand',
674 + 'field' => 'term_id',
675 + 'terms' => $settings['product_include_brand_ids'],
676 + 'operator' => 'IN',
677 + ];
678 + }
679 +
680 + // Attributes
681 + if (in_array('attributes', $include_by) && !empty($settings['product_include_attribute_ids'])) {
682 + $args['tax_query'][] = [
683 + 'taxonomy' => 'product_attributes',
684 + 'field' => 'term_id',
685 + 'terms' => $settings['product_include_attribute_ids'],
686 + 'operator' => 'IN',
687 + ];
688 + }
689 +
690 + // Date
691 + if (in_array('date', $include_by) && !empty($settings['product_include_date'])) {
692 + $date_query = [];
693 + switch ($settings['product_include_date']) {
694 + case 'today':
695 + $date_query['after'] = '1 day ago';
696 + break;
697 + case 'week':
698 + $date_query['after'] = '1 week ago';
699 + break;
700 + case 'month':
701 + $date_query['after'] = '1 month ago';
702 + break;
703 + case 'quarter':
704 + $date_query['after'] = '3 months ago';
705 + break;
706 + case 'year':
707 + $date_query['after'] = '1 year ago';
708 + break;
709 + case 'exact':
710 + if (!empty($settings['product_date_after'])) {
711 + $date_query['after'] = $settings['product_date_after'];
712 + }
713 + if (!empty($settings['product_date_before'])) {
714 + $date_query['before'] = $settings['product_date_before'];
715 + }
716 + $date_query['inclusive'] = true;
717 + break;
718 + }
719 + if (!empty($date_query)) {
720 + $args['date_query'][] = $date_query;
721 + }
722 + }
723 +
724 + // Product Image
725 + if (in_array('image', $include_by) && isset($settings['product_image_include']) && $settings['product_image_include'] !== '') {
726 + if ($settings['product_image_include'] === 'set') {
727 + $args['meta_query'][] = [
728 + 'key' => '_thumbnail_id',
729 + 'compare' => 'EXISTS',
730 + ];
731 + } elseif ($settings['product_image_include'] === 'not_set') {
732 + $args['meta_query'][] = [
733 + 'key' => '_thumbnail_id',
734 + 'compare' => 'NOT EXISTS',
735 + ];
736 + }
737 + }
738 +
739 + // Price
740 + if (in_array('price', $include_by) && isset($settings['product_price_include']) && $settings['product_price_include'] !== '') {
741 + if ($settings['product_price_include'] === 'set') {
742 + $args['meta_query'][] = [
743 + 'key' => '_price',
744 + 'compare' => 'EXISTS',
745 + ];
746 + } elseif ($settings['product_price_include'] === 'not_set') {
747 + $args['meta_query'][] = [
748 + 'key' => '_price',
749 + 'compare' => 'NOT EXISTS',
750 + ];
751 + }
752 + }
753 +
754 + // Stock
755 + if (in_array('stock', $include_by) && isset($settings['product_stock_include']) && $settings['product_stock_include'] !== '') {
756 + if ($settings['product_stock_include'] === 'in_stock') {
757 + $args['meta_query'][] = [
758 + 'key' => '_stock_status',
759 + 'value' => 'instock',
760 + 'compare' => '=',
761 + ];
762 + } elseif ($settings['product_stock_include'] === 'out_of_stock') {
763 + $args['meta_query'][] = [
764 + 'key' => '_stock_status',
765 + 'value' => 'outofstock',
766 + 'compare' => '=',
767 + ];
768 + }
769 + }
770 +
771 + return $args;
772 + }
773 +
774 + /**
775 + * Apply "Exclude By" logic to WP_Query args.
776 + */
777 + private function apply_exclude_by_args($args, $settings)
778 + {
779 + $exclude_by = $settings['product_exclude_by'] ?? [];
780 +
781 + if (!is_array($exclude_by)) {
782 + $exclude_by = [$exclude_by];
783 + }
784 +
785 + // Categories
786 + if (in_array('categories', $exclude_by) && !empty($settings['product_exclude_category_ids'])) {
787 + $args['tax_query'][] = [
788 + 'taxonomy' => 'product_cat',
789 + 'field' => 'term_id',
790 + 'terms' => $settings['product_exclude_category_ids'],
791 + 'operator' => 'NOT IN',
792 + ];
793 + }
794 +
795 + // Tags
796 + if (in_array('tags', $exclude_by) && !empty($settings['product_exclude_tag_ids'])) {
797 + $args['tax_query'][] = [
798 + 'taxonomy' => 'product_tag',
799 + 'field' => 'term_id',
800 + 'terms' => $settings['product_exclude_tag_ids'],
801 + 'operator' => 'NOT IN',
802 + ];
803 + }
804 +
805 + // Brands
806 + if (in_array('brands', $exclude_by) && !empty($settings['product_exclude_brand_ids'])) {
807 + $args['tax_query'][] = [
808 + 'taxonomy' => 'product_brand',
809 + 'field' => 'term_id',
810 + 'terms' => $settings['product_exclude_brand_ids'],
811 + 'operator' => 'NOT IN',
812 + ];
813 + }
814 +
815 + // Attributes
816 + if (in_array('attributes', $exclude_by) && !empty($settings['product_exclude_attribute_ids'])) {
817 + $args['tax_query'][] = [
818 + 'taxonomy' => 'product_attributes',
819 + 'field' => 'term_id',
820 + 'terms' => $settings['product_exclude_attribute_ids'],
821 + 'operator' => 'NOT IN',
822 + ];
823 + }
824 +
825 + // Manual Selection
826 + if (in_array('manual_selection', $exclude_by) && !empty($settings['posts_exclude_ids'])) {
827 + $args['post__not_in'] = $settings['posts_exclude_ids'];
828 + }
829 +
830 + // Date
831 + if (in_array('date', $exclude_by) && !empty($settings['product_exclude_date'])) {
832 + $date_query = [];
833 + switch ($settings['product_exclude_date']) {
834 + case 'today':
835 + $date_query['before'] = '1 day ago';
836 + break;
837 + case 'week':
838 + $date_query['before'] = '1 week ago';
839 + break;
840 + case 'month':
841 + $date_query['before'] = '1 month ago';
842 + break;
843 + case 'quarter':
844 + $date_query['before'] = '3 months ago';
845 + break;
846 + case 'year':
847 + $date_query['before'] = '1 year ago';
848 + break;
849 + case 'exact':
850 + if (!empty($settings['product_exclude_date_after'])) {
851 + $date_query['after'] = $settings['product_exclude_date_after'];
852 + }
853 + if (!empty($settings['product_exclude_date_before'])) {
854 + $date_query['before'] = $settings['product_exclude_date_before'];
855 + }
856 + $date_query['inclusive'] = true;
857 + break;
858 + }
859 + if (!empty($date_query)) {
860 + $args['date_query'][] = $date_query;
861 + }
862 + }
863 +
864 + // Product Image
865 + if (in_array('image', $exclude_by) && isset($settings['product_image_exclude']) && $settings['product_image_exclude'] !== '') {
866 + if ($settings['product_image_exclude'] === 'set') {
867 + $args['meta_query'][] = [
868 + 'key' => '_thumbnail_id',
869 + 'compare' => 'NOT EXISTS',
870 + ];
871 + } elseif ($settings['product_image_exclude'] === 'not_set') {
872 + $args['meta_query'][] = [
873 + 'key' => '_thumbnail_id',
874 + 'compare' => 'EXISTS',
875 + ];
876 + }
877 + }
878 +
879 + // Price
880 + if (in_array('price', $exclude_by) && isset($settings['product_price_exclude']) && $settings['product_price_exclude'] !== '') {
881 + if ($settings['product_price_exclude'] === 'set') {
882 + $args['meta_query'][] = [
883 + 'key' => '_price',
884 + 'compare' => 'NOT EXISTS',
885 + ];
886 + } elseif ($settings['product_price_exclude'] === 'not_set') {
887 + $args['meta_query'][] = [
888 + 'key' => '_price',
889 + 'compare' => 'EXISTS',
890 + ];
891 + }
892 + }
893 +
894 + // Stock
895 + if (in_array('stock', $exclude_by) && isset($settings['product_stock_exclude']) && $settings['product_stock_exclude'] !== '') {
896 + if ($settings['product_stock_exclude'] === 'in_stock') {
897 + $args['meta_query'][] = [
898 + 'key' => '_stock_status',
899 + 'value' => 'instock',
900 + 'compare' => '!=',
901 + ];
902 + } elseif ($settings['product_stock_exclude'] === 'out_of_stock') {
903 + $args['meta_query'][] = [
904 + 'key' => '_stock_status',
905 + 'value' => 'outofstock',
906 + 'compare' => '!=',
907 + ];
908 + }
909 + }
910 +
911 + return $args;
912 + }
913 +
914 + protected function getGroupControlQueryArgs()
915 + {
916 + $settings = $this->get_settings();
917 + $source = $settings['product_source'] ?? 'product';
918 +
919 + // If current_query is selected, return special flag
920 + if ($source === 'current_query') {
921 + return ['__use_global_query' => true];
922 + }
923 +
924 + $page = max(1, get_query_var('paged'), get_query_var('page'));
925 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Pagination number off a public archive URL; cast to int and used only to page a query.
926 + $page = absint(empty($_GET['product-page']) ? $page : $_GET['product-page']);
927 + $paged = absint($page);
928 +
929 + $args = [
930 + 'post_type' => 'product',
931 + 'paged' => $paged
932 + ];
933 +
934 + if (!empty($settings['product_limit'])) {
935 + $args['posts_per_page'] = $settings['product_limit'];
936 + }
937 +
938 + $args = $args + $this->get_product_source_args($source);
939 +
940 + // Apply "Include By" logic
941 + $args = $this->apply_include_by_args($args, $settings);
942 +
943 + // Apply "Exclude By" logic
944 + $args = $this->apply_exclude_by_args($args, $settings);
945 +
946 + // Apply Order and OrderBy
947 + if (!empty($settings['product_orderby'])) {
948 + switch ($settings['product_orderby']) {
949 + case 'price':
950 + $args['meta_key'] = '_price';
951 + $args['orderby'] = 'meta_value_num';
952 + break;
953 + case 'sales':
954 + $args['meta_key'] = 'total_sales';
955 + $args['orderby'] = 'meta_value_num';
956 + break;
957 + default:
958 + $args['orderby'] = $settings['product_orderby'];
959 + }
960 + }
961 +
962 + if (!empty($settings['product_order'])) {
963 + $args['order'] = $settings['product_order'];
964 + }
965 +
966 + $query_id = $this->get_settings('query_id');
967 + if (! empty($query_id)) {
968 + add_action('pre_get_posts', [$this, 'pre_get_posts_query_filter']);
969 + }
970 +
971 + return $args;
972 + }
973 +
974 + /**
975 + * Get query - returns global query if current_query is selected, otherwise returns custom query
976 + */
977 + public function get_query()
978 + {
979 + return $this->_query;
980 + }
981 +
982 + /**
983 + * Query products - handles both custom and global queries
984 + * This is a helper method that can be called from widget's query_product() method
985 + */
986 + protected function build_query_from_args($query_args)
987 + {
988 + // Check if we should use global query
989 + if (isset($query_args['__use_global_query']) && $query_args['__use_global_query'] === true) {
990 + global $wp_query;
991 +
992 + // Get the product limit setting
993 + $settings = $this->get_settings();
994 + $product_limit = $settings['product_limit'] ?? null;
995 +
996 + // If product limit is set, create a new query based on global query args
997 + if (!empty($product_limit)) {
998 + $args = $wp_query->query_vars;
999 + $args['posts_per_page'] = $product_limit;
1000 + return new \WP_Query($args);
1001 + }
1002 +
1003 + return $wp_query;
1004 + } else {
1005 + return new \WP_Query($query_args);
1006 + }
1007 + }
1008 +
1009 + /**
1010 + * Query products - handles both custom and global queries
1011 + * Default implementation - widgets can override this
1012 + */
1013 + public function query_product()
1014 + {
1015 + $query_args = $this->getGroupControlQueryArgs();
1016 + $this->_query = $this->build_query_from_args($query_args);
1017 + }
1018 +}