PluginProbe
Filter Everything — WordPress & WooCommerce Filters / 1.9.2.1
Filter Everything — WordPress & WooCommerce Filters v1.9.2.1
1.9.7 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 All 52 releases
filter-everything / src / Entities / PostMetaNumEntity.php

PostMetaNumEntity.php in Filter Everything — WordPress & WooCommerce Filters 1.9.2.1, at src/Entities/PostMetaNumEntity.php

571 lines 18.2 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 class PostMetaNumEntity implements Entity
10 {
11 use PostMetaTrait;
12 public $items = [];
13
14 protected $product_cache = [];
15
16 public $entityName = '';
17
18 public $excludedTerms = [];
19
20 public $isInclude = false;
21
22 public $new_meta_query = [];
23
24 public $postTypes = [];
25
26 public $wdr_product_ids = [];
27
28 public $is_woo_discount_rules = false;
29
30 public function __construct( $postMetaName, $postType ){
31 /**
32 * @feature clean code from unused methods
33 */
34 $this->entityName = $postMetaName;
35 if(flrt_is_woo_discount_rules()){
36 $this->is_woo_discount_rules = true;
37 }
38 $this->setPostTypes( array($postType) );
39 }
40
41 public function setPostTypes( $postTypes = false )
42 {
43 $wpManager = Container::instance()->getWpManager();
44 $wpQueriedObject = $wpManager->getQueryVar('wp_queried_object');
45
46 if ($postTypes) {
47 $this->postTypes = $postTypes;
48 }elseif( ! empty( $wpQueriedObject['post_types'] ) ){
49 $this->postTypes = $wpQueriedObject['post_types'];
50 }else{
51 $this->postTypes = array('post');
52 }
53
54 if( flrt_is_woocommerce()
55 && in_array( 'product', $this->postTypes )
56 && ! in_array( 'product_variation', $this->postTypes ) ){
57 $this->postTypes[] = 'product_variation';
58 }
59
60 $this->getAllExistingTerms();
61 }
62
63 public static function inputName( $slug, $edge = 'min' )
64 {
65 return $edge . '_' . $slug;
66 }
67
68 public function setExcludedTerms( $excludedTerms, $isInclude )
69 {
70 $this->excludedTerms = $excludedTerms;
71 $this->isInclude = $isInclude;
72 }
73
74 public function getName()
75 {
76 return $this->entityName;
77 }
78
79 function excludeTerms( $terms )
80 {
81 return $terms;
82 }
83
84 function getTerms()
85 {
86 return $this->excludeTerms( $this->getAllExistingTerms() );
87 }
88
89 /**
90 * @param int $id term id
91 * @return false|object term object of false
92 */
93 public function getTerm( $termId ){
94 if( ! $termId ){
95 return false;
96 }
97
98 foreach ( $this->getTerms() as $term ){
99 if( $termId == $term->term_id ){
100 return $term;
101 }
102 }
103
104 return false;
105 }
106
107 public function getTermId( $slug )
108 {
109 /**
110 * Post meta num value has no typical ID, so slug will be instead
111 */
112 return $slug . '_' . $this->getName();
113 }
114
115 /**
116 * @return array list of term_id and names useful to create Select dropdown
117 */
118 public function getTermsForSelect()
119 {
120 $toSelect = [];
121 foreach ( $this->getTerms() as $term ) {
122 $toSelect[$term->slug] = $term->name;
123 }
124
125 return $toSelect;
126 }
127
128 public function getTermsForSelect2()
129 {
130 $toSelect = [];
131 foreach ( $this->getTerms() as $term ) {
132 $toSelect[] = array( 'id' => $term->slug, 'text' =>$term->name );
133 }
134
135 return $toSelect;
136 }
137
138 function getAllExistingTerms( $force = false )
139 {
140 if( empty( $this->items ) || $force ) {
141 $this->items = $this->selectTerms();
142 }
143 return $this->items;
144 }
145
146 public function isDecimal( $step = 0, $value = 0 )
147 {
148 if( strpos( $step, '.') !== false ){
149 return true;
150 }
151
152 if( strpos( $value, '.') !== false ){
153 return true;
154 }
155
156 return false;
157 }
158
159 function populateTermsWithPostIds( $setId, $post_type )
160 {
161 // Does nothing. It was already done before.
162 }
163
164 /**
165 * @param array $alreadyFilteredPosts
166 * @return array
167 */
168 public function selectTerms( $alreadyFilteredPosts = [] ) {
169 global $wpdb;
170
171 $IN = false;
172 $key_in = '';
173 $new_result = [];
174 $min_and_max = [
175 'min' => 0,
176 'max' => 0
177 ];
178 $post_and_types = [];
179 $translatable_post_type_exists = false;
180
181 /**
182 * Set Post types
183 */
184 if( ! empty( $this->postTypes ) && isset($this->postTypes[0]) && $this->postTypes[0] ){
185 foreach ( $this->postTypes as $postType ){
186 $key_in .= '_' . $postType;
187 $pieces[] = $wpdb->prepare( "%s", $postType );
188 }
189
190 $IN = implode(", ", $pieces );
191 }
192
193 /**
194 * Set transient key
195 */
196 $transient_key = flrt_get_terms_transient_key( 'post_meta_num_'. $this->getName() . $key_in );
197
198 if ( false === ( $result = flrt_get_transient( $transient_key ) ) ) {
199 // Get all post meta values
200 $sql[] = "SELECT {$wpdb->postmeta}.post_id,{$wpdb->postmeta}.meta_value,{$wpdb->posts}.post_type";
201 $sql[] = "FROM {$wpdb->postmeta}";
202 $sql[] = "LEFT JOIN {$wpdb->posts} ON ({$wpdb->postmeta}.post_id = {$wpdb->posts}.ID)";
203
204 /**
205 * If post type is translatable with WPML, get post meta values only with current language
206 */
207 if( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) ){
208
209 $wpml_settings = get_option( 'icl_sitepress_settings' );
210
211 foreach ( $this->postTypes as $type ) {
212 if( isset( $wpml_settings['custom_posts_sync_option'][$type] ) ){
213 if( $wpml_settings['custom_posts_sync_option'][$type] === '1' ){
214 $translatable_post_type_exists = true;
215 break;
216 }
217 }
218 }
219
220 if ( $translatable_post_type_exists ) {
221 $sql[] = "LEFT JOIN {$wpdb->prefix}icl_translations AS wpml_translations";
222 $sql[] = "ON {$wpdb->postmeta}.post_id = wpml_translations.element_id";
223
224 if (!empty($this->postTypes)) {
225
226 $sql[] = "AND wpml_translations.element_type IN(";
227
228 foreach ($this->postTypes as $type) {
229 $LANG_IN[] = $wpdb->prepare("CONCAT('post_', '%s')", $type);
230 }
231 $sql[] = implode(",", $LANG_IN);
232
233 $sql[] = ")";
234 }
235 }
236 }
237 /**
238 * There is NULL problem because posts with meta_value = '' are also included in the list
239 * And condition (NULL <= 0) is true
240 * */
241 $sql[] = "WHERE {$wpdb->postmeta}.meta_key = %s";
242 $sql[] = "AND {$wpdb->postmeta}.meta_value IS NOT NULL";
243
244 if( $IN ){
245 $sql[] = "AND {$wpdb->posts}.post_type IN( {$IN} )";
246 }
247
248 if( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) && $translatable_post_type_exists ){
249
250 $sql[] = $wpdb->prepare("AND wpml_translations.language_code = '%s'", ICL_LANGUAGE_CODE);
251 }
252
253 $sql = implode(' ', $sql);
254
255 $e_name = wp_unslash( $this->entityName );
256 $sql = $wpdb->prepare( $sql, $e_name );
257
258 /**
259 * Filters terms SQL-query and allows to modify it
260 */
261 $sql = apply_filters( 'wpc_filter_get_post_meta_num_terms_sql', $sql, $e_name );
262
263 $result = $wpdb->get_results( $sql, ARRAY_A );
264
265 $clean_from_non_numeric = [];
266 foreach ( $result as $single_post ) {
267 if ( preg_match( '/[^\d\.\-]+/', $single_post['meta_value'] ) ) {
268 continue;
269 }
270
271 $clean_from_non_numeric[] = $single_post;
272 }
273 $result = $clean_from_non_numeric;
274
275 flrt_set_transient( $transient_key, $result, FLRT_TRANSIENT_PERIOD_HOURS * HOUR_IN_SECONDS );
276 }
277
278 if( ! empty( $result ) ) {
279
280 $postsIn_flipped = array_flip( $alreadyFilteredPosts );
281 $wpManager = Container::instance()->getWpManager();
282 $queried_values = $wpManager->getQueryVar( 'queried_values', [] );
283 $filter_slug = false;
284
285 /**
286 * Check if this filter was queried
287 */
288 foreach ( $queried_values as $slug => $filter ) {
289 if ( $filter[ 'e_name' ] === $this->getName() ) {
290 $filter_slug = $slug;
291 break;
292 }
293 }
294
295 $max = false;
296 $min = false;
297
298 /**
299 * If this filter was queried we have to receive its $max and $min values
300 */
301 if ( $filter_slug ) {
302 if ( isset( $queried_values[ $filter_slug ][ 'values' ][ 'max' ] ) ) {
303 $max = (float) $queried_values[ $filter_slug ][ 'values' ][ 'max' ];
304 $max = apply_filters( 'wpc_unset_num_shift', $max, $this->getName() );
305 }
306
307 if ( isset( $queried_values[ $filter_slug ][ 'values' ][ 'min' ] ) ) {
308 $min = (float) $queried_values[ $filter_slug ][ 'values' ][ 'min' ];
309 $min = apply_filters( 'wpc_unset_num_shift', $min, $this->getName() );
310 }
311 }
312
313 if($this->is_woo_discount_rules && (strpos($this->entityName, '_price') !== false)){
314 $wdr_woo_discount_rules = $this->getWooDiscountRulesClass();
315 $product_ids = array_map('intval', array_column($result, 'post_id'));
316 $this->preloadProducts($product_ids);
317 }
318
319 foreach ( $result as $single_post ) {
320 /**
321 * If there are already filtered posts, we have to skip posts
322 * that are out of the queried list
323 */
324 if( ! empty( $alreadyFilteredPosts ) ) {
325 if( ! isset( $postsIn_flipped[ $single_post['post_id'] ] ) ) {
326 continue;
327 }
328 }
329
330 if ($this->is_woo_discount_rules) {
331 if (strpos($this->entityName, '_price') !== false) {
332 $product = $this->getProductCached($single_post['post_id']);
333 if ($product) {
334 $wdr_product_has_sale = $wdr_woo_discount_rules->getProductPriceToDisplay($product);
335 if ($wdr_product_has_sale) {
336 $single_post['meta_value'] = $wdr_product_has_sale['discounted_price'];
337 }
338 }
339 }
340 }
341
342
343
344 /**
345 * We have to generate and fill two arrays
346 * First to detect $min and $max values
347 * Second to map post_types with post IDs
348 */
349 $single_post['meta_value'] = (is_float($single_post['meta_value']) ) ? $single_post['meta_value'] : (float) $single_post['meta_value'];
350 $new_result[] = $single_post['meta_value'];
351
352 if ( $min !== false && $single_post['meta_value'] < $min ){
353 continue;
354 }
355
356 if ( $max !== false && $single_post['meta_value'] > $max ){
357 continue;
358 }
359
360 $this->wdr_product_ids[] = (int)$single_post['post_id'];
361 $post_and_types[ $single_post['post_id'] ] = $single_post['post_type'];
362 }
363
364 }
365
366 if( ! empty( $new_result ) ){
367 $min_and_max = [
368 'min' => apply_filters( 'wpc_set_num_shift', min( $new_result ), $this->getName(), 'min' ),
369 'max' => apply_filters( 'wpc_set_num_shift', max( $new_result ), $this->getName(), 'max' ),
370 ];
371 }
372
373 $min_and_max = apply_filters( 'wpc_set_min_max', $min_and_max, $this->getName() );
374
375 return $this->convertSelectResult( $min_and_max, $post_and_types );
376 }
377
378 public function updateMinAndMaxValues( $postsIn )
379 {
380 if( ! empty( $this->items ) && ! empty( $postsIn ) ){
381 $newItems = $this->selectTerms( $postsIn );
382 foreach ( $this->items as $index => $term ) {
383 if( isset( $this->items[$index]->$index ) ){
384 $this->items[$index]->$index = $newItems[$index]->$index;
385 }
386 }
387 }
388 }
389
390 private function createTermName( $edge, $value, $queried_values )
391 {
392 $name = $edge;
393 $queriedFilter = false;
394
395 if( $queried_values ){
396 foreach ( $queried_values as $slug => $filter ){
397 if( $filter['e_name'] === $this->getName() ){
398 $queriedFilter = $filter;
399 break;
400 }
401 }
402
403 if ( $queriedFilter ) {
404 $name = $name .' '. $slug;
405 }
406 }
407
408 if( isset( $queriedFilter['values'][$edge] ) ) {
409 $name = $name .' '. $queriedFilter['values'][$edge];
410 }else{
411 $name = $name .' '. $value;
412 }
413
414 return apply_filters( 'wpc_filter_post_meta_num_term_name', $name, $this->getName() );
415 }
416
417 public function convertSelectResult( $result, $post_and_types = [] ){
418 $return = [];
419
420 if( ! is_array( $result ) ){
421 return $return;
422 }
423
424 // To make standard format for terms array;
425 $i = 1;
426 $wpManager = Container::instance()->getWpManager();
427 $queried_values = $wpManager->getQueryVar( 'queried_values' );
428
429 foreach ( $result as $edge => $value ){
430
431 $termObject = new \stdClass();
432 $termObject->slug = $edge;
433 $termObject->name = $this->createTermName( $edge, $value, $queried_values );
434 $termObject->term_id = $edge . '_' . $this->getName();
435 $termObject->posts = array_keys( $post_and_types );
436 $termObject->count = 0;
437 $termObject->cross_count = 0;
438 $termObject->post_types = $post_and_types; //[];
439 $termObject->$edge = $value;
440 $termObject->wp_queried = false;
441
442 $return[ $edge ] = $termObject;
443
444 $i++;
445 }
446
447 return $return;
448 }
449
450 /**
451 * @return object WP_Query
452 */
453 public function addTermsToWpQuery( $queried_value, $wp_query )
454 {
455 $meta_query = [];
456 $key = $queried_value['e_name'];
457 // Add existing Meta Query if present
458 $this->importExistingMetaQuery( $wp_query );
459
460 /**
461 * @bug for Woo Products if we don't specify Max value it makes it 0.0000
462 */
463 $min = isset( $queried_value['values']['min'] ) ? $queried_value['values']['min'] : false;
464 $max = isset( $queried_value['values']['max'] ) ? $queried_value['values']['max'] : false;
465
466 // Compare with false because $min can be 0
467 if ((strpos($key, '_price') !== false) && flrt_is_woocommerce() && $this->is_woo_discount_rules) {
468 $query_post_in = $this->wdrAddProductsRangeToFilterQuery($wp_query);
469 $wp_query->set('post__in', $query_post_in);
470 if (empty($query_post_in)){
471 $wp_query->set('posts_per_page', $query_post_in);
472 }
473 } else {
474 if ($min !== false) {
475 $min = apply_filters('wpc_unset_num_shift', $min, $this->getName());
476
477 $type = $this->isDecimal($queried_value['step'], $min) ? 'DECIMAL(15,6)' : 'NUMERIC';
478 $meta_query = array(
479 'key' => $key,
480 'value' => $min,
481 'compare' => '>=',
482 'type' => $type
483 );
484 $this->addMetaQueryArray($meta_query);
485 }
486
487 if ($max !== false) {
488 $max = apply_filters('wpc_unset_num_shift', $max, $this->getName());
489
490 $type = $this->isDecimal($queried_value['step'], $max) ? 'DECIMAL(15,6)' : 'NUMERIC';
491 $meta_query = array(
492 'key' => $key,
493 'value' => $max,
494 'compare' => '<=',
495 'type' => $type
496 );
497 $this->addMetaQueryArray($meta_query);
498 }
499
500 $this->addMetaQueryArray($meta_query);
501
502 if (count($this->new_meta_query) > 1) {
503 $this->new_meta_query['relation'] = 'AND';
504 }
505 }
506
507 $wp_query->set('meta_query', $this->new_meta_query);
508
509 $this->new_meta_query = [];
510
511 return $wp_query;
512 }
513
514 public function wdrAddProductsRangeToFilterQuery($wp_query)
515 {
516 $args = array(
517 'status' => 'publish',
518 'limit' => -1,
519 'return' => 'ids'
520 );
521
522 //Added for work woo_discount_rules and query hash
523 $args['flrt_wdr_pagination'] = true;
524
525 if (isset($wp_query->queried_object->taxonomy) && isset($wp_query->queried_object->taxonomy)) {
526 $args['tax_query'] = array(
527 array(
528 'taxonomy' => $wp_query->queried_object->taxonomy,
529 'field' => 'term_id',
530 'terms' => array($wp_query->queried_object->term_id),
531 )
532 );
533 }
534
535 $products = wc_get_products($args);
536 $query_post_in = array_intersect($products, $this->wdr_product_ids);
537 if(isset($wp_query->query_vars['post__in']) && !empty($wp_query->query_vars['post__in'])){
538 $query_post_in = array_intersect($query_post_in, $wp_query->query_vars['post__in']);
539 }
540 if(empty($query_post_in)){
541 return [0];
542 }
543 return $query_post_in;
544 }
545
546 public function getWooDiscountRulesClass(){
547 return flrt_woo_discount_rules_class();
548 }
549
550 protected function preloadProducts($product_ids) {
551 if (empty($product_ids)) {
552 return;
553 }
554
555 $products = wc_get_products([
556 'include' => $product_ids,
557 'limit' => -1,
558 ]);
559
560 foreach ($products as $product) {
561 $this->product_cache[$product->get_id()] = $product;
562 }
563 }
564
565 protected function getProductCached($product_id) {
566 if (!isset($this->product_cache[$product_id])) {
567 $this->product_cache[$product_id] = wc_get_product($product_id);
568 }
569 return $this->product_cache[$product_id];
570 }
571 }