PluginProbe
Filter Everything — WordPress & WooCommerce Filters / trunk
Filter Everything — WordPress & WooCommerce Filters vtrunk
1.9.6 1.9.5 1.9.4 1.9.3 1.9.2.2 1.9.2.1 trunk 1.2.1 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.1 1.4.4 1.4.5 1.4.8 1.4.9 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 All 51 releases
filter-everything / src / Entities / EntityManager.php

EntityManager.php in Filter Everything — WordPress & WooCommerce Filters trunk, at src/Entities/EntityManager.php

1,489 lines 50.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FilterEverything\Filter;
4
5 if ( ! defined('ABSPATH') ) {
6 exit;
7 }
8
9 use FilterEverything\Filter\Pro\Entities\PostMetaExistsEntity;
10 use FilterEverything\Filter\Pro\Entities\TaxonomyNumEntity;
11
12 class EntityManager
13 {
14 const DEFAULT_PREFIX = 'filter-';
15
16 private $allConfiguredFilters;
17
18 public function __construct()
19 {
20 $this->allConfiguredFilters = $this->getAllConfiguredFilters();
21 }
22
23 public static function getTaxonomies()
24 {
25 $excludedTaxes = flrt_excluded_taxonomies();
26 $args = [];
27 $taxonomies = get_taxonomies($args, 'objects');
28
29 foreach ($taxonomies as $t => $taxonomy) {
30 if (in_array($taxonomy->name, $excludedTaxes)) {
31 unset($taxonomies[$t]);
32 }
33 }
34
35 return $taxonomies;
36 }
37
38 private function getData( $key )
39 {
40 return Container::instance()->getParam( $key );
41 }
42
43 private function storeData( $key, $data )
44 {
45 Container::instance()->storeParam( $key, $data );
46 }
47
48 /**
49 * @param $key = $entity.'-'.$name
50 */
51 public function createEntity( $key, $postType = '' )
52 {
53 if ( ! $key ) {
54 return false;
55 }
56
57 $fse = Container::instance()->getFilterService();
58 $name = '';
59 $parts = explode( $fse->sep, $key, 2 );
60
61 $entity = $parts[0];
62 $name = $parts[1];
63
64 $storeKey = $key;
65 /**
66 * We add post_type suffix to the key for filters
67 * which terms depend from post_type
68 * */
69 if ( in_array( $entity, array(
70 'post_meta_num',
71 'post_meta_exists',
72 'post_meta',
73 'tax_numeric',
74 'author',
75 'post_date',
76 'post_meta_date',
77 )
78 ) && $postType ) {
79 $storeKey = $key.'_'.$postType;
80 }
81
82 if ( $entityExists = $this->getData( $storeKey ) ) {
83 return $entityExists;
84 }
85
86 switch( $entity ) {
87 case 'taxonomy':
88 $this->storeData( $storeKey, new TaxonomyEntity( $name ) );
89 break;
90
91 case 'post_meta':
92 $this->storeData( $storeKey, new PostMetaEntity( $name, $postType ) );
93 break;
94
95 case 'post_meta_num':
96 $this->storeData( $storeKey, new PostMetaNumEntity( $name, $postType ) );
97 break;
98
99 case 'post_meta_exists':
100 if( class_exists('FilterEverything\Filter\Pro\Entities\PostMetaExistsEntity') ) {
101 $this->storeData($storeKey, new PostMetaExistsEntity( $name, $postType ) );
102 }else{
103 // For the plugin version downgrade compatibility
104 $this->storeData($storeKey, new DefaultEntity( $name ) );
105 }
106 break;
107
108 case 'author':
109 $this->storeData( $storeKey, new AuthorEntity( $name, $postType ) );
110 break;
111
112 case 'tax_numeric':
113 if ( class_exists('FilterEverything\Filter\Pro\Entities\TaxonomyNumEntity') ) {
114 $this->storeData( $storeKey, new TaxonomyNumEntity( $name, $postType ) );
115 } else {
116 // For the plugin version downgrade compatibility
117 $this->storeData($storeKey, new DefaultEntity( $name ) );
118 }
119
120 break;
121
122 case 'post_date':
123 $this->storeData( $storeKey, new PostDateEntity( $name, $postType ) );
124 break;
125
126 case 'post_meta_date':
127 $this->storeData( $storeKey, new PostMetaDateEntity( $name, $postType ) );
128 break;
129 }
130
131 unset( $fse, $parts, $entity, $name );
132
133 return $this->getData( $storeKey );
134 }
135
136 public function getEntityByFilter( $filter, $postType = '' )
137 {
138 $fse = Container::instance()->getFilterService();
139
140 if ( ! isset( $filter['entity'] ) ) {
141 return false;
142 }
143
144 if ( ! isset( $filter['e_name'] ) ) {
145 return false;
146 }
147
148 return $this->createEntity( $fse->getEntityKey( $filter['entity'], $filter['e_name'] ), $postType );
149
150 }
151
152 public function getPossibleTaxonomies()
153 {
154 $entities = [];
155 $args = apply_filters( 'wpc_get_taxonomies_args', [] );
156 $taxonomies = get_taxonomies( $args, 'objects' );
157 $excluded_taxonomies = flrt_excluded_taxonomies();
158
159 foreach ( $taxonomies as $taxonomy ){
160 if( in_array( $taxonomy->name, $excluded_taxonomies ) ){
161 continue;
162 }
163 // It is better to save value as 'taxonomy_pa_size' because
164 // user potentially can create post_meta with the same name
165 $label = ucwords( flrt_ucfirst( mb_strtolower( $taxonomy->label ) ) );
166 if( ! $label ){
167 $label = $taxonomy->name;
168 }
169 $entities[ 'taxonomy_' . $taxonomy->name ] = $label;
170 }
171
172 return $entities;
173 }
174
175 public function getPossibleEntities()
176 {
177 $entities = [];
178
179 $entities['taxonomy']['group_label'] = esc_html__( 'Taxonomy Filters', 'filter-everything' );
180 $entities['taxonomy']['entities'] = $this->getPossibleTaxonomies();
181
182 $other = array(
183 'post_meta' => array(
184 'group_label' => esc_html__('Custom Field Filters', 'filter-everything'),
185 'entities' => array(
186 'post_meta' => esc_html__('Custom Field', 'filter-everything'),
187 'post_meta_num' => esc_html__('Custom Field Numeric', 'filter-everything'),
188 'post_meta_date' => esc_html__('Custom Field Date', 'filter-everything'),
189 'post_meta_exists' => esc_html__('Custom Field Exists - Available in PRO', 'filter-everything'),
190 )
191 ),
192 'other' => array(
193 'group_label' => esc_html__( 'Other Filters', 'filter-everything' ),
194 'entities' => array(
195 'post_date' => esc_html__( 'Post Date', 'filter-everything' ),
196 'author_author' => esc_html__( 'Post Author', 'filter-everything' ),
197 'tax_numeric' => esc_html__( 'Taxonomy Numeric - Available in PRO', 'filter-everything' ),
198 )
199 )
200 );
201
202 $result = array_merge( $entities, $other );
203
204 $result = apply_filters( 'wpc_possible_entities', $result );
205
206 return $result;
207 }
208
209 public function prepareFilterCommon( $entityAndEname, $filters = array() )
210 {
211 $filter = [];
212 $e_name = '';
213 $fs = Container::instance()->getFilterService();
214
215 $allPermalinksSettings = get_option( 'wpc_filter_permalinks', [] );
216 $entityName = explode( $fs->sep, $entityAndEname, 2 );
217
218 $filter['e_name'] = $e_name = $fs->getEntityEname( $entityAndEname );
219 $filter['entity'] = $entityName[0];
220
221 if( isset( $allPermalinksSettings[$entityAndEname] ) && $allPermalinksSettings[$entityAndEname] ){
222 $filter['slug'] = $allPermalinksSettings[$entityAndEname];
223 // This is messed up because label is not good index for this item, but it already exists
224 // And I will try ty change it later. Maybe. Very very maybe :-)
225 $filter['label'] = '{'.$allPermalinksSettings[$entityAndEname].'}';
226 }
227
228 if( isset( $filters[$e_name] ) ){
229 $filter['title'] = $filters[$e_name]['label'];
230 }
231
232 return $filter;
233 }
234
235 public function getCommonFilterValues( $eName, $postType )
236 {
237 $filter = [];
238
239 if( ! $eName || ! $postType ){
240 return false;
241 }
242
243 $fs = Container::instance()->getFilterService();
244 $allIndexedFilters = get_option( 'wpc_seo_rules_settings' );
245
246 foreach ( $allIndexedFilters as $item => $value ) {
247 $itemKey = explode(":", $item, 2 );
248 $maybePostType = $itemKey[0]; // post|product etc
249
250 if( $maybePostType === $postType ){
251 $entityAndEname = $itemKey[1];
252 $entityName = explode( $fs->sep, $entityAndEname, 2 );
253
254 if( $entityName[1] === $eName ){
255 $filter = $this->prepareFilterCommon( $itemKey[1] );
256 break;
257 }
258 }
259 }
260
261 return $filter;
262 }
263
264 public function getFilterByEname( $e_name, $sets = [] )
265 {
266 $container = Container::instance();
267
268 if( ! empty( $sets ) ){
269 $actualSets = $sets;
270 }else{
271 $wpManager = $container->getWpManager();
272 $actualSets = $wpManager->getQueryVar( 'wpc_page_related_set_ids' );
273 }
274
275 if( empty( $actualSets ) || ! $actualSets ){
276 return false;
277 }
278
279 $theFilter = [];
280
281 foreach ( $actualSets as $actualSet ) {
282 $key = $e_name . '_'.$actualSet['ID'];
283 if ( isset( $this->allConfiguredFilters[$key] ) ){
284 $theFilter = $this->allConfiguredFilters[$key];
285 break;
286 }
287 }
288
289 return $theFilter;
290 }
291
292 public function getFilterById( $filter_id )
293 {
294 if( ! $filter_id ) {
295 return [];
296 }
297
298 $theFilter = [];
299 foreach( $this->allConfiguredFilters as $filter ){
300
301 if( $filter['ID'] === $filter_id ){
302 $theFilter = $filter;
303 break;
304 }
305 }
306
307 return $theFilter;
308 }
309
310 /**
311 * @param string $slug filters prefix e.g. 'cat', 'tag'
312 * @param array $onlyKeys optional. Keys from filter array that should be extracted
313 * @return array all matched filters from DB.
314 */
315 public function getAllFiltersBySlug( $slug, $onlyKeys = [] )
316 {
317 $theFilters = [];
318
319 foreach( $this->allConfiguredFilters as $filter ){
320 if( $filter['slug'] === $slug ){
321 $theFilters[] = $filter;
322 }
323 }
324
325 if( ! empty( $onlyKeys ) ){
326 $extractedFilters = [];
327 foreach( $theFilters as $filter ){
328 $extractedFilters[] = flrt_extract_vars( $filter, $onlyKeys );
329 }
330 return $extractedFilters;
331 }
332
333 return $theFilters;
334 }
335
336 public function getFilterBySlug( $slug )
337 {
338 $theFilter = [];
339
340 foreach( $this->allConfiguredFilters as $filter ) {
341 if ( $filter['slug'] === $slug ) {
342 $theFilter = $filter;
343 break;
344 }
345 }
346
347 // if( ! empty( $onlyKeys ) ){
348 // return flrt_extract_vars( $theFilter, $onlyKeys );
349 // }
350
351 return $theFilter;
352 }
353
354 public function getGlobalConfiguredSlugs()
355 {
356 $permalinksTab = new PermalinksTab();
357 return get_option( $permalinksTab->optionName, [] );
358 }
359
360 public function getFlatEntities( $entities = false )
361 {
362 if( ! $entities ){
363 $entities = $this->getPossibleEntities();
364 }
365
366 $flat_entities = [];
367
368 array_walk_recursive( $entities, function ( $value, $key ) use ( &$flat_entities ) {
369 if( $key !== 'group_label' ){
370 // $value = label
371 $flat_entities[ $key ] = $value;
372 }
373 }, $flat_entities );
374
375 return $flat_entities;
376 }
377
378 /**
379 * @return array
380 */
381 public function extractBelongsFilters( $all_filters )
382 {
383 $result = [];
384 if( ! is_array( $all_filters ) ){
385 return $result;
386 }
387
388 $ffs = Container::instance()->getFilterFieldsService();
389
390 foreach( $all_filters as $filter ) {
391 if( $ffs->filterBelongsToPostType( $filter['parent'], $filter['entity'], $filter['e_name'] ) ){
392 $result[ $filter['ID'] ] = $filter;
393 }
394 }
395
396 return $result;
397 }
398
399 public function hasPostTypeFilters( $postType )
400 {
401 global $wpdb;
402
403 $sql[] = "SELECT {$wpdb->posts}.ID FROM {$wpdb->posts}";
404 $sql[] = "WHERE {$wpdb->posts}.post_type = '%s'";
405 $sql[] = "AND {$wpdb->posts}.post_status = 'publish'";
406 $sql[] = "AND {$wpdb->posts}.post_excerpt = '%s'";
407 $sql[] = "LIMIT 0, 1";
408
409 $sql = implode(' ', $sql);
410
411 $query = $wpdb->prepare( $sql, FLRT_FILTERS_SET_POST_TYPE, $postType );
412 $results = $wpdb->get_results( $query, OBJECT );
413
414 if( ! empty( $results ) ){
415 return true;
416 }
417
418 return false;
419 }
420
421 public function getFiltersRelatedWithPostType( $postType, $entityName = '')
422 {
423 global $wpdb;
424
425 $container = Container::instance();
426
427 // There shouldn't be duplicates in entities
428 // Because of validation
429 $sql[] = "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_title, {$wpdb->posts}.post_content, {$wpdb->posts}.post_excerpt";
430 $sql[] = "FROM {$wpdb->posts}";
431 $sql[] = "WHERE {$wpdb->posts}.post_type = '%s'";
432 $sql[] = "AND {$wpdb->posts}.post_status = 'publish'";
433
434 $sql[] = "AND {$wpdb->posts}.post_parent IN (";
435 $sql[] = "SELECT {$wpdb->posts}.ID FROM {$wpdb->posts}";
436 $sql[] = "WHERE {$wpdb->posts}.post_type = '%s'";
437 $sql[] = "AND {$wpdb->posts}.post_status = 'publish'";
438 $sql[] = "AND {$wpdb->posts}.post_excerpt = '%s'"; // $postType
439 $sql[] = ")";
440 $sql[] = "ORDER BY {$wpdb->posts}.ID ASC";
441
442 $sql = implode(' ', $sql);
443
444 $query = $wpdb->prepare($sql, FLRT_FILTERS_POST_TYPE, FLRT_FILTERS_SET_POST_TYPE, $postType);
445
446 $key = $postType.'_get_related_filters';
447 $results = $container->getParam( $key );
448
449 if( ! $results ){
450 $results = $wpdb->get_results( $query, OBJECT );
451 if( ! $results ){
452 $results = '-1';
453 }
454 $container->storeParam( $key, $results );
455 }
456
457 if( $results === '-1' ){
458 return [];
459 }
460
461 $filters = [];
462 foreach ( $results as $result ) {
463 $filterData = maybe_unserialize( $result->post_content );
464
465 if( ! is_array( $filterData ) ){
466 continue;
467 }
468
469 if( $entityName && mb_strpos( $result->post_excerpt, $entityName ) === false ){
470 continue;
471 }
472
473 if( isset( $filters[ $filterData['e_name'] ] ) ){
474 continue;
475 }
476
477 $filters[$filterData['e_name']]['ID'] = $result->ID;
478 $filters[$filterData['e_name']]['label'] = $result->post_title;
479
480 $filters[$filterData['e_name']] = array_merge( $filters[$filterData['e_name']], $filterData );
481 }
482
483 return $filters;
484 }
485
486 private function makeFiltersQuery()
487 {
488 $transient_key = 'wpc_filters_query';
489 if ( false === ( $results = flrt_get_transient( $transient_key ) ) ) {
490 global $wpdb;
491
492 $sql[] = "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_title, {$wpdb->posts}.post_content,";
493 $sql[] = "{$wpdb->posts}.post_name, {$wpdb->posts}.post_parent, {$wpdb->posts}.menu_order";
494 $sql[] = "FROM {$wpdb->posts}";
495 $sql[] = "WHERE {$wpdb->posts}.post_type = '%s'";
496 $sql[] = "AND {$wpdb->posts}.post_status = 'publish'";
497 $sql[] = "AND {$wpdb->posts}.post_parent != 0";
498 $sql[] = "ORDER BY {$wpdb->posts}.menu_order ASC, {$wpdb->posts}.ID DESC";
499
500 $sql = implode(' ', $sql);
501
502 $query = $wpdb->prepare( $sql, FLRT_FILTERS_POST_TYPE );
503 $results = $wpdb->get_results( $query, OBJECT );
504
505 flrt_set_transient( $transient_key, $results, FLRT_TRANSIENT_PERIOD_HOURS * HOUR_IN_SECONDS );
506 }
507
508 if( ! $results ){
509 return [];
510 }
511
512 return $results;
513 }
514
515 public function getAllConfiguredFilters()
516 {
517 $key = 'wpc_filters';
518
519 if( ! $filters = $this->getData( $key ) ){
520 $filters = [];
521
522 foreach( $this->makeFiltersQuery() as $k => $filter_post ){
523 $filter = $this->prepareFilter( $filter_post );
524 $k = $filter['e_name'].'_'.$filter['parent'];
525 $filters[$k] = $filter;
526 }
527
528 $this->storeData( $key, $filters );
529 }
530
531 return $filters;
532 }
533
534 public function selectOnlySetFilters( $set_id, $keys = [] )
535 {
536 if( ! $set_id ){
537 return false;
538 }
539 $setFilters = [];
540
541 foreach( $this->allConfiguredFilters as $filter ){
542 if( $filter['parent'] == $set_id ){
543 if( ! empty( $keys ) ){
544 $setFilters[ $filter['ID'] ] = flrt_extract_vars( $filter, $keys );
545 }else{
546 $setFilters[ $filter['ID'] ] = $filter;
547 }
548 }
549 }
550
551 return $setFilters;
552 }
553 /**
554 * @return array
555 */
556 public function getParamFromFilters( $filters, $key )
557 {
558 $result = [];
559 if( ! is_array( $filters ) ){
560 return $result;
561 }
562
563 foreach ( $filters as $_key => $_filter ){
564 if( isset( $_filter[ $key ] ) ){
565 $result[] = $_filter[ $key ];
566 }
567 }
568
569 return $result;
570 }
571
572 public function getConfiguredPathSlugs( $filters = NULL )
573 {
574 $slugs = [];
575
576 if( ! $filters ){
577 $filters = $this->allConfiguredFilters;
578 }
579
580 foreach( $filters as $filter ){
581 if( $filter['in_path'] === 'yes' ){
582 $slugs[] = $filter['slug'];
583 }
584 }
585
586 return $slugs;
587 }
588
589 public function getConfiguredQuerySlugs( $filters = NULL )
590 {
591 $slugs = [];
592
593 if( ! $filters ){
594 $filters = $this->allConfiguredFilters;
595 }
596
597 foreach( $filters as $filter ){
598
599 if( FLRT_PERMALINKS_ENABLED ) {
600 if ($filter['in_path'] === 'no') {
601 $slugs[] = $filter['slug'];
602 }
603 }else{
604 $slugs[] = $filter['slug'];
605 }
606 }
607
608 return array_unique( $slugs );
609 }
610
611 public function prepareFilter( $filter_post )
612 {
613 if( ! isset( $filter_post->ID ) ){
614 return false;
615 }
616
617 $raw_data = (array) maybe_unserialize( $filter_post->post_content );
618 $fse = Container::instance()->getFilterService();
619 $empty_filter = $fse->getEmptyFilter();
620
621 $defaults = array(
622 'ID' => $filter_post->ID,
623 'parent' => $filter_post->post_parent,
624 'menu_order' => $filter_post->menu_order,
625 'label' => $filter_post->post_title,
626 'slug' => $filter_post->post_name
627 );
628
629 $filter = wp_parse_args( $raw_data, wp_parse_args( $defaults, $empty_filter ) );
630 $filter = apply_filters( 'wpc_after_get_filter', $filter );
631
632 unset($fse);
633
634 return $filter;
635 }
636
637 /**
638 * @param $sets
639 * @return array
640 */
641 public function getOnlyBelongsFilters( $sets )
642 {
643 $relevantFilters = [];
644
645 if ( is_array( $sets ) ) {
646 foreach ( $sets as $set ) {
647 $relevantFilters[] = $this->selectOnlySetFilters( $set['ID'] );
648 }
649 }
650
651 $relevantFilters = flrt_remove_level_array($relevantFilters);
652
653 return $this->extractBelongsFilters($relevantFilters);
654 }
655
656 public function checkForbiddenFilters( $queriedFilters, $allowedFilters )
657 {
658 $requestedSlugs = $this->getParamFromFilters( $queriedFilters, 'slug' );
659 $allowedSlugs = $this->getParamFromFilters( $allowedFilters, 'slug' );
660
661 foreach( $requestedSlugs as $slug ){
662 if( ! in_array( $slug, $allowedSlugs ) ){
663 return false;
664 }
665 }
666
667 return true;
668 }
669
670 public function getEntityAllTermsSlugs( $slug )
671 {
672 $slugs = [];
673 $terms = $this->getEntityTermsBySlug( $slug );
674
675 if( is_wp_error( $terms ) ){
676 return $slugs;
677 }
678
679 foreach( $terms as $k => $termObject ){
680 $slugs[ $termObject->term_id ] = $termObject->slug;
681 }
682
683 return $slugs;
684 }
685
686 /**
687 * @param array $sets
688 * @return array|false|mixed
689 */
690 public function getSetsRelatedFilters( $sets = [] )
691 {
692 /**
693 * @todo we have to change this. Not page related, but Set related filters.
694 */
695 $subkey = '';
696 $wpManager = Container::instance()->getWpManager();
697
698 if ( empty( $sets ) ) {
699 $sets = $wpManager->getQueryVar( 'wpc_page_related_set_ids' );
700 }
701
702 if ( ! empty( $sets ) ) {
703 foreach ( $sets as $set ) {
704 $subkey .= '_' . $set['ID'];
705 }
706 }
707
708 $key = 'wpc_related_filters'.$subkey;
709
710 if ( ! $actual = Container::instance()->getParam( $key ) ) {
711
712 $queried = $wpManager->getQueryVar( 'queried_values', [] );
713 $configured = $this->getOnlyBelongsFilters( $sets );
714 $actual = $configured;
715
716 /**
717 * @feature Create new method that populates filters with requested values
718 * This should be there in EntityMananger because for all entities it should be done in the same way
719 */
720 foreach ( $configured as $k => $filter ) {
721 // Merge with queried values
722 $values = isset( $queried[$filter['slug']]['values'] ) ? $queried[$filter['slug']]['values'] : [];
723 $actual[$k]['values'] = $values;
724 }
725
726 Container::instance()->storeParam( $key, $actual );
727 }
728
729 return $actual;
730 }
731
732 /**
733 * Should always return postIDs
734 */
735 public function getAlreadyFilteredPostIds( $setId, $exceptEntity = false )
736 {
737 /**
738 * @bug searching of all queried post IDs doesn't work properly for two or more PostMetaNum filters in one set.
739 */
740
741 $wpManager = Container::instance()->getWpManager();
742 $allWpQueriedPostIds = $this->getAllSetWpQueriedPostIds( $setId );
743
744 $postIds = $allWpQueriedPostIds ? $allWpQueriedPostIds : [];
745
746 if( $wpManager->getQueryVar('wpc_is_filter_request') ){
747 $filteredPostsIdsKeys = $this->collectFilteredPostsIds( $setId );
748
749 $allWpQueriedPostIdsKeys = array_flip( $allWpQueriedPostIds );
750 $allWpQueriedPostIdsKeys = apply_filters( 'wpc_from_products_to_variations', $allWpQueriedPostIdsKeys );
751
752 if( $exceptEntity ){
753 unset( $filteredPostsIdsKeys[$exceptEntity->getName()] );
754 }
755
756 if( ! empty( $filteredPostsIdsKeys ) ) {
757 $intersection_keys = $this->getBetweenFiltersIntersect($filteredPostsIdsKeys, $allWpQueriedPostIdsKeys);
758
759 // Replace back from Variation IDs to Product IDs
760 $postIds = apply_filters( 'wpc_from_variations_to_products', array_flip($intersection_keys) );
761 }
762 }
763
764 return $postIds;
765 }
766
767 public function getAllSetWpQueriedPostIds( $setId )
768 {
769 $ids = [];
770 if( ! $setId ){
771 return $ids;
772 }
773
774 $wpManager = Container::instance()->getWpManager();
775 $key = 'wpc_all_set_queried_post_ids_' . $setId;
776 if ( isset( $_GET['srch'] ) && $_GET['srch'] ) {
777 $keyword = filter_input( INPUT_GET, 'srch', FILTER_SANITIZE_SPECIAL_CHARS );
778 $key .= '_' . $keyword;
779 }
780 $ids = $wpManager->getQueryVar( $key );
781
782 $query_on_the_page = true;
783
784 if ( $ids === false ) {
785
786 // Let's check if the set is related with a query on the page
787 $sets = $wpManager->getQueryVar( 'wpc_page_related_set_ids' );
788 foreach ( $sets as $set ){
789 if( isset( $set['query_on_the_page'] ) && $set['query_on_the_page'] === false ){
790 $query_on_the_page = false;
791 }
792 }
793
794 $set_filter_query = $wpManager->getQueryVar( 'wpc_set_filter_query_' . $setId );
795
796 if( ! $set_filter_query ){
797 /**
798 * string
799 */
800 $theGet = Container::instance()->getTheGet();
801 $savedQueryVars = get_post_meta( $setId, 'wpc_filter_set_query_vars', true );
802
803 if( $savedQueryVars ){
804 $query_vars = maybe_unserialize( $savedQueryVars );
805
806 if( is_array( $query_vars ) ){
807 $set_filter_query = new \WP_Query();
808
809 if( $query_on_the_page ){
810 if( isset( $theGet['s'] ) ){
811 $query_vars['s'] = $theGet['s'];
812 }
813 }
814
815 $set_filter_query->query_vars = $query_vars;
816 }
817 }
818 }
819
820
821 $detected_source = '';
822 if (!empty($set_filter_query->query_vars['flrt_detected_source'])) {
823 $detected_source = $set_filter_query->query_vars['flrt_detected_source'];
824 }
825
826 if ($set_filter_query instanceof \WP_Query) {
827
828 // Configure WP_Query object to select only posts IDs
829 $set_filter_query->set( 'fields', 'ids' );
830 $set_filter_query->set( 'posts_per_page', -1 );
831 $set_filter_query->set( 'nopaging', true );
832 $set_filter_query->set( 'post_status', 'publish' );
833 $set_filter_query->set( 'flrt_query_clone', true );
834
835 // The saved query vars may lack an explicit post_type (e.g. Avada's
836 // fusion_blog element applies it at runtime, not in the stored vars). Without
837 // it, a taxonomy-based clone can fall back to post_type 'any' and pull in
838 // other post types (e.g. Pages that share the filtered taxonomy), inflating
839 // term counters above what the front-end query — restricted to the set's post
840 // type — actually shows. Constrain the count universe to the set's own post
841 // type so the counters match the displayed results.
842 if ( ! $set_filter_query->get( 'post_type' ) ) {
843 $set_post_type = get_post_field( 'post_excerpt', $setId );
844 $set_filter_query->set( 'post_type', $set_post_type ? $set_post_type : 'post' );
845 }
846
847 do_action( 'wpc_all_set_wp_queried_posts' , $set_filter_query, $setId );
848
849 $ids = $set_filter_query->get_posts();
850
851 }
852
853 if(!empty($detected_source) && isset($set_filter_query->query_vars['flrt_detected_source'])){
854 $set_filter_query->query_vars['flrt_detected_source'] = $detected_source;
855 }
856
857 $ids = apply_filters( 'wpc_check_errors_ids', $ids, $set_filter_query);
858
859 $ids = (! empty( $ids ) ) ? $ids : [];
860
861 $wpManager->setQueryVar($key, $ids);
862
863 unset($set_filter_query);
864 }
865
866 return $ids;
867 }
868
869 // This method must be executed before output
870 public function prepareEntitiesToDisplay( $sets )
871 {
872 global $flrt_json_data;
873 $container = Container::instance();
874 $wpManager = $container->getWpManager();
875 $fse = Container::instance()->getFilterService();
876 $subkey = '';
877
878 $post_type = $sets[0]['filtered_post_type'];
879 $setId = $sets[0]['ID'];
880 $current_set = $sets[0];
881
882 $all_sets = $wpManager->getQueryVar( 'wpc_page_related_set_ids' );
883 $queryRelatedSets = flrt_get_sets_with_the_same_query( $all_sets, $current_set );
884
885 $filter_by_stock_exists = false;
886
887 $relatedSets = [];
888 foreach ( $queryRelatedSets as $set_id ) {
889 $relatedSets[] = array( 'ID' => $set_id );
890 }
891
892 $subkey = implode( '_', $queryRelatedSets );
893 $jsonQueryRelatedSets = $subkey;
894 $key = 'wpc_entities_prepared_' . $subkey;
895
896 if( ! $container->getParam( $key ) ) {
897
898 $relatedFilters = $this->getSetsRelatedFilters( $relatedSets );
899
900 if ( $post_type === 'product' && ! empty( $relatedFilters ) ) {
901 foreach ($relatedFilters as $filter) {
902 if (isset($filter['e_name']) && $filter['e_name'] === '_stock_status') {
903 $filter_by_stock_exists = true;
904 break;
905 }
906 }
907 }
908
909 $relatedFilters = apply_filters( 'wpc_related_filters_before_terms_count', $relatedFilters, $sets );
910
911 $allPostsIds = $this->getAllSetWpQueriedPostIds( $setId );
912
913 if ( ! empty( $allPostsIds ) ) {
914 $allPostsIds = array_flip( $allPostsIds );
915 }
916
917 $allEntities = [];
918
919 if ( ! empty( $relatedFilters ) ) {
920
921 foreach ( $relatedFilters as $filter ) {
922 // Part 1 collect all entities
923 $entity = $this->getEntityByFilter( $filter, $post_type );
924
925 if ( $entity instanceof PostMetaExistsEntity ) {
926 $entity->setPostTypes( array( $post_type ) );
927 }
928
929 // Emulated filters (e.g. the Stock status emulation) carry
930 // parent = "-1", which resolves to an empty set universe in
931 // populateTermsWithPostIds(). Use the current page set then.
932 $populate_set_id = ( (int) $filter['parent'] > 0 ) ? $filter['parent'] : $setId;
933
934 $entity->populateTermsWithPostIds( $populate_set_id, $post_type );
935 // $entity->filter exists solely for the frontend JSON. With the
936 // range list switched off the saved rows must not ship: the term
937 // items carry no per-bucket counters then (calculateRangeCounts
938 // is gated on show_range_list), and the JS recount enters its
939 // range branch on the mere presence of the rows and crashes.
940 // '' matches getEmptyFilter(); the DB and admin UI keep the rows.
941 if ( ! isset( $filter['show_range_list'] ) || $filter['show_range_list'] !== 'yes' ) {
942 $filter['range_list_input'] = '';
943 }
944 $entity->filter = $filter;
945 $allEntities[$entity->getName()] = $entity;
946 }
947 }
948
949
950 foreach ($allEntities as $entity_key => $entity){
951 $sort_array = [];
952 foreach ($entity->items as $item){
953 $sort_array[] = $item->slug;
954 }
955
956 $entity->items_sort = array_values($fse->sortTerms($sort_array));
957
958 $allEntities[$entity_key] = $entity;
959 }
960
961 // Post IDs with variations instead of parent products
962 // This must be called here, after the $entity->populateTermsWithPostIds(); method called
963 $filteredAllPostsIds = $this->collectFilteredPostsIds( $setId );
964
965 /**
966 * Allows to modify filtered post ids
967 */
968 $filteredAllPostsIds = apply_filters( 'wpc_filtered_all_posts_before_terms_count', $filteredAllPostsIds, $allEntities );
969 $totalCount = array_sum(array_map('count', $filteredAllPostsIds));
970
971 foreach ( $queryRelatedSets as $set_id ) {
972 $flrt_json_data[$set_id]['relatedSets'] = $jsonQueryRelatedSets;
973 $flrt_json_data[$set_id]['filteredAllPostsIds'] = $filteredAllPostsIds;
974 $flrt_json_data[$set_id]['totalFilteredCount'] = $totalCount;
975 }
976
977
978
979
980 /**
981 * Allows to modify all post ids
982 */
983 $flrt_json_data[$set_id]['totalAllPostsIds'] = count($allPostsIds);
984 $allPostsIds = apply_filters( 'wpc_from_products_to_variations', $allPostsIds );
985 foreach ( $queryRelatedSets as $set_id ) {
986 $flrt_json_data[$set_id]['allPostsIds'] = $allPostsIds;
987 }
988
989
990
991 foreach ( $allEntities as $entityName => $entity ) {
992 if ( $entityName === '_stock_status' && ! $filter_by_stock_exists && $post_type === 'product' ) {
993 $filter = flrt_get_stock_status_filter_emulation();
994 } else {
995 $filter = $this->getFilterByEname( $entityName, $relatedSets );
996 }
997 foreach ( $entity->items as $index => $term ) {
998 $entity->items[$index]->count = count( $entity->items[$index]->posts );
999 }
1000
1001 $isRangeEntity = ( $entity instanceof PostMetaNumEntity || $entity instanceof TaxonomyNumEntity || $entity instanceof PostDateEntity || $entity instanceof PostMetaDateEntity );
1002
1003 if ( $isRangeEntity ) {
1004 $postsIn = apply_filters( 'wpc_min_and_max_values_numeric_filters', $this->getAlreadyFilteredPostIds( $setId, $entity ), $entity );
1005 $entity->updateMinAndMaxValues( $postsIn );
1006 }
1007
1008 if ( in_array( $filter['orderby'], ['menuasc', 'menudesc'] ) ) {
1009 if( $entity instanceof TaxonomyEntity ){
1010 foreach ($entity->items as $k => $term) {
1011 $termOrder = get_term_meta( $term->term_id, 'order', true );
1012 $term->menu_order = $termOrder ? $termOrder : 0;
1013 $entity->items[$k] = $term;
1014 }
1015 }
1016 }
1017
1018 $is_rating = (isset($filter['view']) && $filter['view'] === 'rating' && $filter['e_name'] === 'product_visibility' ) ? true : false;
1019 if($is_rating){
1020 $filter['orderby'] = 'default';
1021 }
1022
1023 if ( $isRangeEntity ) {
1024 // Range entities are keyed 'min'/'max' ('from'/'to') — usort() would
1025 // reindex the keys and break items['min'] lookups in the recount JS
1026 } elseif( $filter['orderby'] === 'default' ) {
1027 $entity->items = apply_filters( 'wpc_default_sorting_terms', $entity->items, $filter );
1028 } else {
1029 $entity->items = $this->sortTerms( $entity->items, $filter['orderby'] );
1030 }
1031
1032 /**
1033 * @feature move selected terms to top
1034 */
1035 $used_for_variations = isset( $filter['used_for_variations'] ) ? $filter['used_for_variations'] : false;
1036 $entity->items = apply_filters( 'wpc_items_before_calc_term_count', $entity->items, $entity, $used_for_variations );
1037 foreach ( $entity->items as $index => $term ) {
1038 $termPostsFlipped = array_flip( $term->posts );
1039
1040 $isShowRangeList = ! empty( $filter['show_range_list'] ) && $filter['show_range_list'] === 'yes';
1041 $isMinOrMax = $index === 'min' || $index === 'max';
1042 $hasRangeInput = ! empty( $filter['range_list_input'] );
1043
1044 $shouldProcessRanges = $isShowRangeList && $isMinOrMax && $hasRangeInput;
1045
1046 if ( $shouldProcessRanges ) {
1047 $this->calculateRangeCounts( $entity->items[ $index ], $filter, $termPostsFlipped, $filteredAllPostsIds, $allPostsIds );
1048 }
1049
1050 $entity->items[ $index ]->cross_count = $this->calcTermCount( $termPostsFlipped, $filteredAllPostsIds, $allPostsIds, $filter );
1051 }
1052 }
1053
1054 foreach ( $queryRelatedSets as $set_id ) {
1055 $flrt_json_data[$set_id]['allEntities'] = $allEntities;
1056 }
1057
1058
1059 $container->storeParam( $key, true );
1060 }
1061 }
1062
1063 /**
1064 * @param array $terms list of terms
1065 * @param string $sortby
1066 * @return array sorted list of terms
1067 */
1068 private function sortTerms( $terms, $sortby )
1069 {
1070 if( ! $sortby ){
1071 $sortby = 'nameasc';
1072 }
1073
1074 switch( $sortby ){
1075 case 'nameasc':
1076 usort( $terms, self::compareAsc('name') );
1077 break;
1078 case 'postcountasc':
1079 usort( $terms, self::compareAsc('count') );
1080 break;
1081 case 'idasc':
1082 usort( $terms, self::compareAsc('term_id') );
1083 break;
1084 case 'menuasc':
1085 usort( $terms, self::compareAsc('menu_order') );
1086 break;
1087 case 'namedesc':
1088 usort( $terms, self::compareDesc('name') );
1089 break;
1090 case 'postcountdesc':
1091 usort( $terms, self::compareDesc('count') );
1092 break;
1093 case 'iddesc':
1094 usort( $terms, self::compareDesc('term_id') );
1095 break;
1096 case 'menudesc':
1097 usort( $terms, self::compareDesc('menu_order') );
1098 break;
1099 }
1100
1101 return $terms;
1102 }
1103
1104 public static function compareAsc( $key ){
1105
1106 return function ($a, $b) use ($key) {
1107 $value_1 = isset( $a->$key ) ? strtolower($a->$key) : 0;
1108 $value_2 = isset( $b->$key ) ? strtolower($b->$key) : 0;
1109
1110 if ($value_1 == $value_2) {
1111 return 0;
1112 }
1113
1114 return ($value_1 > $value_2) ? +1 : -1;
1115 };
1116 }
1117
1118 public static function compareDesc( $key ){
1119 return function ($a, $b) use ($key) {
1120 $value_1 = isset( $a->$key ) ? strtolower($a->$key) : 0;
1121 $value_2 = isset( $b->$key ) ? strtolower($b->$key) : 0;
1122
1123 if ($value_1 == $value_2) {
1124 return 0;
1125 }
1126
1127 return ($value_1 < $value_2) ? +1 : -1;
1128 };
1129 }
1130
1131
1132
1133 public function calcTermCount( $termPostsIds, $filteredPostsIds, $allPostsIds, $filter)
1134 {
1135 if( empty( $termPostsIds ) ){
1136 return 0;
1137 }
1138
1139 $e_name = $filter['e_name'];
1140 $logic = $filter['logic'];
1141
1142 if( ! isset( $filteredPostsIds[$e_name] ) ){
1143 $filteredPostsIds[$e_name] = [];
1144 }
1145
1146 // Intersection for logic OR between filter terms
1147 if ( $logic === 'or' ) {
1148
1149 $filteredPostsIds[$e_name] += $termPostsIds;
1150
1151 // Intersection for logic AND between filter terms
1152 } elseif ( $logic === 'and' ) {
1153 if( ! empty( $filteredPostsIds[$e_name] ) ){
1154 $filteredPostsIds[$e_name] = array_intersect_key( $allPostsIds, $filteredPostsIds[$e_name], $termPostsIds );
1155 }else{
1156 $filteredPostsIds[$e_name] = array_intersect_key( $allPostsIds, $termPostsIds );
1157 }
1158 }
1159
1160 $betweenFiltersIntersect = $this->getBetweenFiltersIntersect( $filteredPostsIds, $allPostsIds );
1161 $finalInterSection = apply_filters( 'wpc_from_variations_to_products', array_flip( array_intersect_key( $betweenFiltersIntersect, $termPostsIds ) ) );
1162
1163 return count( $finalInterSection );
1164 }
1165
1166 public function prepareForCalcTermCount($termPostsIds, $filteredPostsIds, $allPostsIds, $filter)
1167 {
1168 if( empty( $termPostsIds ) ){
1169 return 0;
1170 }
1171
1172 $e_name = $filter['e_name'];
1173
1174 if( ! isset( $filteredPostsIds[$e_name] ) ){
1175 $filteredPostsIds[$e_name] = [];
1176 }
1177 $filteredPostsIds[$e_name] = $allPostsIds;
1178 $termPostsIds = $allPostsIds;
1179
1180
1181 $betweenFiltersIntersect = $this->getBetweenFiltersIntersect( $filteredPostsIds, $allPostsIds );
1182 $finalInterSection = apply_filters( 'wpc_from_variations_to_products', array_flip( array_intersect_key( $betweenFiltersIntersect, $termPostsIds ) ) );
1183
1184 return $finalInterSection;
1185 }
1186
1187 private function calculateRangeCounts( $termItem, $filter, $termPostsFlipped, $filteredAllPostsIds, $allPostsIds ){
1188 foreach ( $filter['range_list_input'] as $rangeKey => $rangeList ) {
1189 $rangePostsIds = $this->prepareForCalcTermCount( $termPostsFlipped, $filteredAllPostsIds, $allPostsIds, $filter );
1190
1191 $minRange = (is_float($rangeList['range_list_min_val']) ) ? $rangeList['range_list_min_val']: (float) $rangeList['range_list_min_val'];
1192 $maxRange = (is_float($rangeList['range_list_max_val']) ) ? $rangeList['range_list_max_val']: (float) $rangeList['range_list_max_val'];
1193
1194 $validPostsCount = [];
1195
1196 if(!empty($rangePostsIds) && is_array($rangePostsIds)){
1197 foreach ( $rangePostsIds as $postId ) {
1198 if ( ! isset( $termItem->meta_values[ $postId ] ) ) {
1199 continue;
1200 }
1201
1202 $values = $termItem->meta_values[ $postId ];
1203
1204 if(is_array($values)){
1205 foreach ($values as $val) {
1206 if ( $val < $minRange ) {
1207 continue;
1208 }
1209 if ( $val > $maxRange && $maxRange != 0) {
1210 continue;
1211 }
1212 $validPostsCount[] = $postId;
1213 }
1214 }
1215 }
1216 }
1217 $validPostsCount = apply_filters( 'wpc_from_variations_to_products', $validPostsCount );
1218 $termItem->range_list_input[ $rangeKey ] = count($validPostsCount);
1219 }
1220 }
1221
1222 public function getBetweenFiltersIntersect( $filteredPostsIds, $allPostsIds )
1223 {
1224 $betweenFiltersAND = [];
1225 /**
1226 * @bug when intersection is already empty, this count another intersection as more than 0
1227 * example - http://filter.stepasyuk.com/shop/category-hoodies/color-red/alphabet-alpha/
1228 * when there are hidden product, counter also doesn't work properly
1229 * example - http://filter.stepasyuk.com/shop/category-music/
1230 */
1231
1232 // This implements logic AND between separate filters
1233 if ( ! empty( $filteredPostsIds ) ) {
1234 $i = 1;
1235 foreach ( $filteredPostsIds as $e_name => $singleFilterPostsIds ) {
1236 if( $i !== 1 ) {
1237 $betweenFiltersAND = array_intersect_key( $allPostsIds, $betweenFiltersAND, $singleFilterPostsIds );
1238 } else {
1239 $betweenFiltersAND = array_intersect_key( $allPostsIds, $singleFilterPostsIds );
1240 }
1241 $i++;
1242 }
1243 }
1244
1245 // Final intersect between All posts and Filtered
1246 return array_intersect_key( $allPostsIds, $betweenFiltersAND );
1247 }
1248
1249 public function getTaxonomyTermsForDropdown( $taxonomyName, $optionGroup = false )
1250 {
1251 $terms = [];
1252 if( ! $taxonomyName ){
1253 return $terms;
1254 }
1255
1256 $filter['e_name'] = $taxonomyName;
1257 $filter['entity'] = 'taxonomy';
1258 $entity = $this->getEntityByFilter( $filter );
1259
1260 if( ! $entity ){
1261 return $terms;
1262 }
1263
1264 return $entity->getTermsForSelect( $optionGroup );
1265 }
1266
1267 public function getAuthorTermsForDropdown( $optionGroup = false )
1268 {
1269 $args = array(
1270 'has_published_posts' => true,
1271 'orderby' => 'display_name'
1272 );
1273
1274 $key = 'wpc_users';
1275 if( $optionGroup ){
1276 $key .= '_group';
1277 }
1278
1279 if( ! $authors = $this->getData( $key ) ) {
1280
1281 $authors = [];
1282
1283 $users = get_users($args);
1284
1285 foreach ($users as $user) {
1286 if ($optionGroup) {
1287 $authors['author:' . $user->ID] = $user->data->display_name;
1288 } else {
1289 $authors[$user->ID] = $user->data->display_name;
1290 }
1291 }
1292
1293 $this->storeData( $key, $authors );
1294 }
1295
1296 return $authors;
1297 }
1298
1299 public function safeExplodeFilterValues($params, $slug, $sep, $explode = true)
1300 {
1301 if (!is_string($params)) {
1302 $params = (string) $params;
1303 }
1304
1305 if ($sep === '') {
1306 return $explode ? [$params] : $params;
1307 }
1308
1309 // Get all term slugs for the given filter slug
1310 $allEntityTerms = $this->getEntityAllTermsSlugs($slug);
1311
1312 // Extract pure values (slugs)
1313 $terms = array_values($allEntityTerms);
1314
1315 // Skip empty terms
1316 $terms = array_filter($terms, static function ($t) {
1317 return $t !== '';
1318 });
1319
1320 // Keep only terms that contain the separator
1321 $terms = array_filter($terms, static function ($t) use ($sep) {
1322 return mb_strpos($t, $sep) !== false;
1323 });
1324
1325 // Sort by length in descending order to protect longer terms first
1326 usort($terms, static function ($a, $b) {
1327 return mb_strlen($b) <=> mb_strlen($a);
1328 });
1329
1330 // Replace the separator inside matched terms with a placeholder
1331 // so explode() won't split inside a single term
1332 foreach ($terms as $entityTerm) {
1333 $escapedTerm = preg_quote($entityTerm, '/');
1334 $pattern = "/$escapedTerm/u";
1335
1336 $params = preg_replace_callback($pattern, function ($matches) use ($sep) {
1337 // Replace internal separators only inside the matched term
1338 return str_replace($sep, '#', $matches[0]);
1339 }, $params);
1340 }
1341
1342 // Finally, split by the separator if requested
1343 if ($explode) {
1344 return explode($sep, $params);
1345 }
1346
1347 return $params;
1348 }
1349
1350
1351
1352 public function safeImplodeFilterValues( $filterValues, $sep )
1353 {
1354 if( ! is_array( $filterValues ) ){
1355 return [];
1356 }
1357 // Replace back
1358 array_walk($filterValues, function (&$value) use ($sep) {
1359 $value = str_replace('#', $sep, $value);
1360 });
1361
1362 return $filterValues;
1363 }
1364
1365 /**
1366 * @return array List of all queried posts or empty array
1367 */
1368 public function collectFilteredPostsIds( $setId )
1369 {
1370 $wpManager = Container::instance()->getWpManager();
1371 $fss = Container::instance()->getFilterSetService();
1372
1373 $theSet = $fss->getSet( $setId );
1374 $postType = isset( $theSet['post_type']['value'] ) ? $theSet['post_type']['value'] : '';
1375 $filtered_wp_query = isset( $theSet['wp_filter_query'] ) ? $theSet['wp_filter_query']: '-1';
1376
1377 $current_set = array(
1378 'ID' => $setId,
1379 'filtered_post_type' => $postType,
1380 'query' => $filtered_wp_query
1381 );
1382
1383 $allSetPostsIds = $this->getAllSetWpQueriedPostIds( $setId );
1384 $queriedFilters = $wpManager->getQueryVar('queried_values');
1385
1386 if ( ! $wpManager->getQueryVar('wpc_is_filter_request') ) {
1387 return [];
1388 }
1389
1390 if( $allSetPostsIds ){
1391 $allSetPostsIds = array_flip( $allSetPostsIds );
1392 }
1393
1394 $allSetPostsIds = apply_filters( 'wpc_from_products_to_variations', $allSetPostsIds );
1395
1396 $queriedAllPosts = [];
1397
1398 $all_sets = $wpManager->getQueryVar( 'wpc_page_related_set_ids' );
1399 $queryRelatedSets = flrt_get_sets_with_the_same_query( $all_sets, $current_set );
1400
1401 $set_filter_keys = $this->getSetFilterKeys( $queryRelatedSets );
1402
1403 foreach ( $queriedFilters as $slug => $queriedFilter ) {
1404
1405 $queried_value_key = $queriedFilter['entity'].'#'.$queriedFilter['e_name'];
1406 $do_filter_request = apply_filters( 'wpc_do_filter_request', true, $queriedFilter, null );
1407
1408 if( ! in_array( $queried_value_key, $set_filter_keys ) || ! $do_filter_request ) {
1409 continue;
1410 }
1411
1412 $entity = $this->getEntityByFilter( $queriedFilter, $postType );
1413 $e_name = $queriedFilter['e_name'];
1414 $queriedAllPosts[$e_name] = [];
1415
1416 // Allows to replace product IDs with their variation IDs
1417 $entity->items = apply_filters( 'wpc_items_before_calc_term_count', $entity->items, $entity, $queriedFilter['used_for_variations'] );
1418 foreach ( $entity->items as $term ) {
1419
1420 if ( ! isset( $term->posts ) ) {
1421 continue;
1422 }
1423
1424 if ( in_array( $queriedFilter['entity'], [ 'post_meta_num', 'tax_numeric', 'post_date', 'post_meta_date' ] ) ) {
1425 $doCalculate = in_array( $term->slug, array_keys( $queriedFilter['values'] ) );
1426 } else {
1427 $doCalculate = in_array( $term->slug, $queriedFilter['values'] );
1428 }
1429
1430 if ( $doCalculate ) {
1431
1432 // Intersection for logic OR between filter terms
1433 if ( $queriedFilter['logic'] === 'or' ) {
1434
1435 $queriedAllPosts[$e_name] += array_flip( $term->posts );
1436
1437 // Intersection for logic AND between filter terms
1438 } elseif ( $queriedFilter['logic'] === 'and' ) {
1439
1440 if( ! empty( $queriedAllPosts[$e_name] ) ){
1441 $queriedAllPosts[$e_name] = array_intersect_key( $allSetPostsIds, $queriedAllPosts[$e_name], array_flip($term->posts) );
1442 } else {
1443 $queriedAllPosts[$e_name] = array_intersect_key( $allSetPostsIds, array_flip($term->posts) );
1444 }
1445 }
1446 }
1447 }
1448 }
1449
1450 return apply_filters( 'wpc_queried_all_posts', $queriedAllPosts, $allSetPostsIds, $queriedFilters, $setId );
1451 }
1452
1453 public function getSetFilterKeys( $setIds )
1454 {
1455 $set_filter_keys = [];
1456
1457 if( ! $setIds || empty( $setIds )){
1458 return $set_filter_keys;
1459 }
1460
1461 $sets = [];
1462 foreach( $setIds as $setId ){
1463 $sets[] = array( 'ID' => $setId );
1464 }
1465
1466 $set_filters = $this->getSetsRelatedFilters( $sets );
1467
1468 foreach ( $set_filters as $filter ){
1469 $set_filter_keys[] = $filter['entity'].'#'.$filter['e_name'];
1470 }
1471
1472 return $set_filter_keys;
1473 }
1474
1475 public function getEntityTermsBySlug( $slug )
1476 {
1477 // Slug is not unique param, but it's ok for this case.
1478 $filterEntity = $this->getFilterBySlug( $slug /*, array( 'entity', 'e_name' )*/ );
1479 $entity = $this->getEntityByFilter( $filterEntity );
1480
1481 return $entity->getAllExistingTerms();
1482 }
1483
1484 public function addTermsToWpQuery( $queried_value, $wp_query )
1485 {
1486 $entity = $this->getEntityByFilter( $queried_value );
1487 return $entity->addTermsToWpQuery( $queried_value, $wp_query );
1488 }
1489 }