| @@ -9,9 +9,17 @@ | ||
| 9 | 9 | class PostMetaNumEntity implements Entity |
| 10 | 10 | { |
| 11 | 11 | use PostMetaTrait; |
| 12 | 12 | public $items = []; |
| 13 | + /** | |
| 14 | + * Declared explicitly: assigned from the outside in | |
| 15 | + * EntityManager::prepareEntitiesToDisplay(); dynamic properties are | |
| 16 | + * deprecated since PHP 8.2. | |
| 17 | + */ | |
| 18 | + public $items_sort = []; | |
| 13 | 19 | |
| 20 | + public $filter = []; | |
| 21 | + | |
| 14 | 22 | protected $product_cache = []; |
| 15 | 23 | |
| 16 | 24 | public $entityName = ''; |
| 17 | 25 | |
| @@ -175,8 +183,9 @@ | ||
| 175 | 183 | 'min' => 0, |
| 176 | 184 | 'max' => 0 |
| 177 | 185 | ]; |
| 178 | 186 | $post_and_types = []; |
| 187 | + $post_and_meta_values = []; | |
| 179 | 188 | $translatable_post_type_exists = false; |
| 180 | 189 | |
| 181 | 190 | /** |
| 182 | 191 | * Set Post types |
| @@ -194,9 +203,13 @@ | ||
| 194 | 203 | * Set transient key |
| 195 | 204 | */ |
| 196 | 205 | $transient_key = flrt_get_terms_transient_key( 'post_meta_num_'. $this->getName() . $key_in ); |
| 197 | 206 | |
| 198 | - if ( false === ( $result = flrt_get_transient( $transient_key ) ) ) { | |
| 207 | + $result = flrt_get_transient( $transient_key ); | |
| 208 | + | |
| 209 | + // Cache format v2: ['vals' => [post_id => float|float[]], 'types' => [post_id => post_type]]. | |
| 210 | + // Legacy format cached raw SQL rows — detect and rebuild those. | |
| 211 | + if ( ! is_array( $result ) || ! isset( $result['vals'], $result['types'] ) ) { | |
| 199 | 212 | // Get all post meta values |
| 200 | 213 | $sql[] = "SELECT {$wpdb->postmeta}.post_id,{$wpdb->postmeta}.meta_value,{$wpdb->posts}.post_type"; |
| 201 | 214 | $sql[] = "FROM {$wpdb->postmeta}"; |
| 202 | 215 | $sql[] = "LEFT JOIN {$wpdb->posts} ON ({$wpdb->postmeta}.post_id = {$wpdb->posts}.ID)"; |
| @@ -259,29 +272,54 @@ | ||
| 259 | 272 | * Filters terms SQL-query and allows to modify it |
| 260 | 273 | */ |
| 261 | 274 | $sql = apply_filters( 'wpc_filter_get_post_meta_num_terms_sql', $sql, $e_name ); |
| 262 | 275 | |
| 263 | - $result = $wpdb->get_results( $sql, ARRAY_A ); | |
| 276 | + $rows = $wpdb->get_results( $sql, ARRAY_A ); | |
| 264 | 277 | |
| 265 | - $clean_from_non_numeric = []; | |
| 266 | - foreach ( $result as $single_post ) { | |
| 278 | + // Aggregate raw rows into the compact v2 maps. A post can have | |
| 279 | + // several meta rows with different values (e.g. WC variable | |
| 280 | + // products keep one _price row per distinct child price) — such | |
| 281 | + // posts get an array of floats instead of a single float. | |
| 282 | + $vals = []; | |
| 283 | + $types = []; | |
| 284 | + foreach ( $rows as $single_post ) { | |
| 267 | 285 | if ( preg_match( '/[^\d\.\-]+/', $single_post['meta_value'] ) ) { |
| 268 | 286 | continue; |
| 269 | 287 | } |
| 270 | 288 | |
| 271 | - $clean_from_non_numeric[] = $single_post; | |
| 289 | + $post_id = (int) $single_post['post_id']; | |
| 290 | + $meta_value = (float) $single_post['meta_value']; | |
| 291 | + | |
| 292 | + if ( isset( $vals[ $post_id ] ) ) { | |
| 293 | + if ( ! is_array( $vals[ $post_id ] ) ) { | |
| 294 | + $vals[ $post_id ] = array( $vals[ $post_id ] ); | |
| 295 | + } | |
| 296 | + $vals[ $post_id ][] = $meta_value; | |
| 297 | + } else { | |
| 298 | + $vals[ $post_id ] = $meta_value; | |
| 299 | + } | |
| 300 | + | |
| 301 | + $types[ $post_id ] = $single_post['post_type']; | |
| 272 | 302 | } |
| 273 | - $result = $clean_from_non_numeric; | |
| 303 | + unset( $rows ); | |
| 274 | 304 | |
| 305 | + $result = array( 'vals' => $vals, 'types' => $types ); | |
| 306 | + unset( $vals, $types ); | |
| 307 | + | |
| 275 | 308 | flrt_set_transient( $transient_key, $result, FLRT_TRANSIENT_PERIOD_HOURS * HOUR_IN_SECONDS ); |
| 276 | 309 | } |
| 277 | 310 | |
| 278 | - if( ! empty( $result ) ) { | |
| 311 | + $meta_vals = isset( $result['vals'] ) ? $result['vals'] : []; | |
| 312 | + $meta_types = isset( $result['types'] ) ? $result['types'] : []; | |
| 279 | 313 | |
| 314 | + if( ! empty( $meta_vals ) ) { | |
| 315 | + | |
| 280 | 316 | $postsIn_flipped = array_flip( $alreadyFilteredPosts ); |
| 281 | 317 | $wpManager = Container::instance()->getWpManager(); |
| 282 | 318 | $queried_values = $wpManager->getQueryVar( 'queried_values', [] ); |
| 319 | + $fss = Container::instance()->getFilterSetService(); | |
| 283 | 320 | $filter_slug = false; |
| 321 | + $setId = false; | |
| 284 | 322 | |
| 285 | 323 | /** |
| 286 | 324 | * Check if this filter was queried |
| 287 | 325 | */ |
| @@ -287,8 +325,9 @@ | ||
| 287 | 325 | */ |
| 288 | 326 | foreach ( $queried_values as $slug => $filter ) { |
| 289 | 327 | if ( $filter[ 'e_name' ] === $this->getName() ) { |
| 290 | 328 | $filter_slug = $slug; |
| 329 | + $setId = $filter['parent']; | |
| 291 | 330 | break; |
| 292 | 331 | } |
| 293 | 332 | } |
| 294 | 333 | |
| @@ -311,19 +350,18 @@ | ||
| 311 | 350 | } |
| 312 | 351 | |
| 313 | 352 | if($this->is_woo_discount_rules && (strpos($this->entityName, '_price') !== false)){ |
| 314 | 353 | $wdr_woo_discount_rules = $this->getWooDiscountRulesClass(); |
| 315 | - $product_ids = array_map('intval', array_column($result, 'post_id')); | |
| 316 | - $this->preloadProducts($product_ids); | |
| 354 | + $this->preloadProducts( array_keys( $meta_vals ) ); | |
| 317 | 355 | } |
| 318 | 356 | |
| 319 | - foreach ( $result as $single_post ) { | |
| 357 | + foreach ( $meta_vals as $post_id => $post_values ) { | |
| 320 | 358 | /** |
| 321 | 359 | * If there are already filtered posts, we have to skip posts |
| 322 | 360 | * that are out of the queried list |
| 323 | 361 | */ |
| 324 | 362 | if( ! empty( $alreadyFilteredPosts ) ) { |
| 325 | - if( ! isset( $postsIn_flipped[ $single_post['post_id'] ] ) ) { | |
| 363 | + if( ! isset( $postsIn_flipped[ $post_id ] ) ) { | |
| 326 | 364 | continue; |
| 327 | 365 | } |
| 328 | 366 | } |
| 329 | 367 | |
| @@ -328,52 +366,89 @@ | ||
| 328 | 366 | } |
| 329 | 367 | |
| 330 | 368 | if ($this->is_woo_discount_rules) { |
| 331 | 369 | if (strpos($this->entityName, '_price') !== false) { |
| 332 | - $product = $this->getProductCached($single_post['post_id']); | |
| 370 | + $product = $this->getProductCached($post_id); | |
| 333 | 371 | if ($product) { |
| 334 | 372 | $wdr_product_has_sale = $wdr_woo_discount_rules->getProductPriceToDisplay($product); |
| 335 | 373 | if ($wdr_product_has_sale) { |
| 336 | - $single_post['meta_value'] = $wdr_product_has_sale['discounted_price']; | |
| 374 | + $post_values = (float) $wdr_product_has_sale['discounted_price']; | |
| 337 | 375 | } |
| 338 | 376 | } |
| 339 | 377 | } |
| 340 | 378 | } |
| 341 | 379 | |
| 342 | - | |
| 343 | - | |
| 344 | 380 | /** |
| 345 | 381 | * We have to generate and fill two arrays |
| 346 | 382 | * First to detect $min and $max values |
| 347 | 383 | * Second to map post_types with post IDs |
| 348 | 384 | */ |
| 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']; | |
| 385 | + foreach ( (array) $post_values as $meta_value ) { | |
| 386 | + $post_and_meta_values[ $post_id ][] = $meta_value; | |
| 387 | + $new_result[] = $meta_value; | |
| 351 | 388 | |
| 352 | - if ( $min !== false && $single_post['meta_value'] < $min ){ | |
| 353 | - continue; | |
| 354 | - } | |
| 389 | + if ( $min !== false && $meta_value < $min ){ | |
| 390 | + continue; | |
| 391 | + } | |
| 355 | 392 | |
| 356 | - if ( $max !== false && $single_post['meta_value'] > $max ){ | |
| 357 | - continue; | |
| 393 | + if ( $max !== false && $meta_value > $max ){ | |
| 394 | + continue; | |
| 395 | + } | |
| 396 | + | |
| 397 | + if($this->is_woo_discount_rules){ | |
| 398 | + $this->wdr_product_ids[] = $post_id; | |
| 399 | + } | |
| 400 | + | |
| 401 | + $post_and_types[ $post_id ] = $meta_types[ $post_id ]; | |
| 358 | 402 | } |
| 359 | - | |
| 360 | - $this->wdr_product_ids[] = (int)$single_post['post_id']; | |
| 361 | - $post_and_types[ $single_post['post_id'] ] = $single_post['post_type']; | |
| 362 | 403 | } |
| 363 | 404 | |
| 364 | 405 | } |
| 365 | - | |
| 406 | + $abs_min_and_max = [ | |
| 407 | + 'abs_min' => 0, | |
| 408 | + 'abs_max' => 0, | |
| 409 | + ]; | |
| 366 | 410 | if( ! empty( $new_result ) ){ |
| 367 | 411 | $min_and_max = [ |
| 368 | 412 | 'min' => apply_filters( 'wpc_set_num_shift', min( $new_result ), $this->getName(), 'min' ), |
| 369 | 413 | 'max' => apply_filters( 'wpc_set_num_shift', max( $new_result ), $this->getName(), 'max' ), |
| 370 | 414 | ]; |
| 415 | + | |
| 416 | + $flat = []; | |
| 417 | + foreach ($post_and_meta_values as $post_id => $values) { | |
| 418 | + foreach ($values as $value) { | |
| 419 | + $flat[$post_id][] = $value; | |
| 420 | + } | |
| 421 | + } | |
| 422 | + | |
| 423 | + $all_values = array_merge(...array_values($post_and_meta_values)); | |
| 424 | + | |
| 425 | + $abs_min = min($all_values); | |
| 426 | + $abs_max = max($all_values); | |
| 427 | + | |
| 428 | + $min_post_id = null; | |
| 429 | + $max_post_id = null; | |
| 430 | + | |
| 431 | + foreach ($post_and_meta_values as $post_id => $values) { | |
| 432 | + if (in_array($abs_min, $values)) { | |
| 433 | + $min_post_id = $post_id; | |
| 434 | + } | |
| 435 | + if (in_array($abs_max, $values)) { | |
| 436 | + $max_post_id = $post_id; | |
| 437 | + } | |
| 438 | + } | |
| 439 | + | |
| 440 | + $abs_min_and_max = [ | |
| 441 | + 'abs_min' => $abs_min, | |
| 442 | + 'abs_max' => $abs_max, | |
| 443 | + ]; | |
| 444 | + | |
| 445 | + $post_and_meta_values[$min_post_id] = [$min_and_max['min']]; | |
| 446 | + $post_and_meta_values[$max_post_id] = [$min_and_max['max']]; | |
| 371 | 447 | } |
| 372 | 448 | |
| 373 | 449 | $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 ); | |
| 450 | + return $this->convertSelectResult( $min_and_max, $post_and_types, $post_and_meta_values, $abs_min_and_max); | |
| 376 | 451 | } |
| 377 | 452 | |
| 378 | 453 | public function updateMinAndMaxValues( $postsIn ) |
| 379 | 454 | { |
| @@ -404,8 +479,11 @@ | ||
| 404 | 479 | $name = $name .' '. $slug; |
| 405 | 480 | } |
| 406 | 481 | } |
| 407 | 482 | |
| 483 | + $name = ($name === 'min') ? esc_html__('min', 'filter-everything') : $name; | |
| 484 | + $name = ($name === 'max') ? esc_html__('max', 'filter-everything') : $name; | |
| 485 | + | |
| 408 | 486 | if( isset( $queriedFilter['values'][$edge] ) ) { |
| 409 | 487 | $name = $name .' '. $queriedFilter['values'][$edge]; |
| 410 | 488 | }else{ |
| 411 | 489 | $name = $name .' '. $value; |
| @@ -413,9 +491,9 @@ | ||
| 413 | 491 | |
| 414 | 492 | return apply_filters( 'wpc_filter_post_meta_num_term_name', $name, $this->getName() ); |
| 415 | 493 | } |
| 416 | 494 | |
| 417 | - public function convertSelectResult( $result, $post_and_types = [] ){ | |
| 495 | + public function convertSelectResult( $result, $post_and_types = [], $post_and_meta_values = [], $abs_min_and_max = [] ){ | |
| 418 | 496 | $return = []; |
| 419 | 497 | |
| 420 | 498 | if( ! is_array( $result ) ){ |
| 421 | 499 | return $return; |
| @@ -432,13 +510,15 @@ | ||
| 432 | 510 | $termObject->slug = $edge; |
| 433 | 511 | $termObject->name = $this->createTermName( $edge, $value, $queried_values ); |
| 434 | 512 | $termObject->term_id = $edge . '_' . $this->getName(); |
| 435 | 513 | $termObject->posts = array_keys( $post_and_types ); |
| 514 | + $termObject->meta_values = $post_and_meta_values; | |
| 436 | 515 | $termObject->count = 0; |
| 437 | 516 | $termObject->cross_count = 0; |
| 438 | 517 | $termObject->post_types = $post_and_types; //[]; |
| 439 | 518 | $termObject->$edge = $value; |
| 440 | 519 | $termObject->wp_queried = false; |
| 520 | + $termObject->abs_values = $abs_min_and_max; | |
| 441 | 521 | |
| 442 | 522 | $return[ $edge ] = $termObject; |
| 443 | 523 | |
| 444 | 524 | $i++; |
| @@ -470,11 +550,32 @@ | ||
| 470 | 550 | if (empty($query_post_in)){ |
| 471 | 551 | $wp_query->set('posts_per_page', $query_post_in); |
| 472 | 552 | } |
| 473 | 553 | } else { |
| 474 | - if ($min !== false) { | |
| 554 | + if ($min !== false && $max !== false) { | |
| 475 | 555 | $min = apply_filters('wpc_unset_num_shift', $min, $this->getName()); |
| 556 | + $max = apply_filters('wpc_unset_num_shift', $max, $this->getName()); | |
| 476 | 557 | |
| 558 | + $type = ( $this->isDecimal($queried_value['step'], $min) || $this->isDecimal($queried_value['step'], $max) ) ? 'DECIMAL(15,6)' : 'NUMERIC'; | |
| 559 | + /** | |
| 560 | + * Both bounds must live in ONE clause. WP_Meta_Query gives every | |
| 561 | + * clause its own postmeta JOIN, so separate >= and <= clauses can | |
| 562 | + * match two DIFFERENT meta rows: a post with several values of the | |
| 563 | + * key (e.g. variable product _price rows) passed the range even | |
| 564 | + * when no single value was inside it, and the results disagreed | |
| 565 | + * with the term counters. BETWEEN checks the same row. | |
| 566 | + */ | |
| 567 | + $meta_query = array( | |
| 568 | + 'key' => $key, | |
| 569 | + 'value' => array( $min, $max ), | |
| 570 | + 'compare' => 'BETWEEN', | |
| 571 | + 'type' => $type | |
| 572 | + ); | |
| 573 | + $this->addMetaQueryArray($meta_query); | |
| 574 | + | |
| 575 | + } elseif ($min !== false) { | |
| 576 | + $min = apply_filters('wpc_unset_num_shift', $min, $this->getName()); | |
| 577 | + | |
| 477 | 578 | $type = $this->isDecimal($queried_value['step'], $min) ? 'DECIMAL(15,6)' : 'NUMERIC'; |
| 478 | 579 | $meta_query = array( |
| 479 | 580 | 'key' => $key, |
| 480 | 581 | 'value' => $min, |
| @@ -481,11 +582,10 @@ | ||
| 481 | 582 | 'compare' => '>=', |
| 482 | 583 | 'type' => $type |
| 483 | 584 | ); |
| 484 | 585 | $this->addMetaQueryArray($meta_query); |
| 485 | - } | |
| 486 | 586 | |
| 487 | - if ($max !== false) { | |
| 587 | + } elseif ($max !== false) { | |
| 488 | 588 | $max = apply_filters('wpc_unset_num_shift', $max, $this->getName()); |
| 489 | 589 | |
| 490 | 590 | $type = $this->isDecimal($queried_value['step'], $max) ? 'DECIMAL(15,6)' : 'NUMERIC'; |
| 491 | 591 | $meta_query = array( |
| @@ -495,10 +595,8 @@ | ||
| 495 | 595 | 'type' => $type |
| 496 | 596 | ); |
| 497 | 597 | $this->addMetaQueryArray($meta_query); |
| 498 | 598 | } |
| 499 | - | |
| 500 | - $this->addMetaQueryArray($meta_query); | |
| 501 | 599 | |
| 502 | 600 | if (count($this->new_meta_query) > 1) { |
| 503 | 601 | $this->new_meta_query['relation'] = 'AND'; |
| 504 | 602 | } |