| @@ -337,8 +337,14 @@ | ||
| 337 | 337 | // Check if a string $compare_with is not in the array $compare |
| 338 | 338 | return !in_array( $compare_with, $compare, true ); |
| 339 | 339 | } |
| 340 | 340 | |
| 341 | + // A single stored value against a single option: without this both | |
| 342 | + // list conditions fell through and returned false either way. | |
| 343 | + if ( is_string( $compare ) && is_string( $compare_with ) ) { | |
| 344 | + return $compare !== $compare_with; | |
| 345 | + } | |
| 346 | + | |
| 341 | 347 | return false; |
| 342 | 348 | |
| 343 | 349 | case 'in_list': |
| 344 | 350 | // return $compare; |
| @@ -357,8 +363,13 @@ | ||
| 357 | 363 | // Check if a string $compare_with is in the array $compare |
| 358 | 364 | return in_array( $compare_with, $compare, true ); |
| 359 | 365 | } |
| 360 | 366 | |
| 367 | + // A single stored value against a single option. | |
| 368 | + if ( is_string( $compare ) && is_string( $compare_with ) ) { | |
| 369 | + return $compare === $compare_with; | |
| 370 | + } | |
| 371 | + | |
| 361 | 372 | return false; |
| 362 | 373 | |
| 363 | 374 | default: |
| 364 | 375 | return false; |
| @@ -435,30 +446,36 @@ | ||
| 435 | 446 | if ( !is_string( $compare ) ) { |
| 436 | 447 | $compare = (string) $compare; |
| 437 | 448 | } |
| 438 | 449 | |
| 439 | - switch ( $condition ) { | |
| 440 | - case 'equal': | |
| 441 | - return strtolower( $compare ) === strtolower( $compare_with ); | |
| 450 | + /** | |
| 451 | + * A field can hold more than one value. | |
| 452 | + * | |
| 453 | + * An ACF checkbox group, or any multi select, stores an array. Which | |
| 454 | + * comparison runs is decided by {@see self::get_type()} from the value | |
| 455 | + * the merchant typed and the condition, never from what the product | |
| 456 | + * side holds, so a plain "contains red" rule arrives here even when the | |
| 457 | + * stored value is an array. Handing that array to stripos(), strtolower() | |
| 458 | + * or substr() is fatal on PHP 8. | |
| 459 | + * | |
| 460 | + * Each stored value is compared in turn: a positive condition passes | |
| 461 | + * when any of them matches, a negative one only when none do. | |
| 462 | + */ | |
| 463 | + $matched = false; | |
| 442 | 464 | |
| 443 | - case 'not_equal': | |
| 444 | - return strtolower( $compare ) !== strtolower( $compare_with ); | |
| 465 | + foreach ( $this->comparable_values( $compare_with ) as $haystack ) { | |
| 466 | + if ( $this->matches_string_condition( $haystack, $compare, $condition ) ) { | |
| 467 | + $matched = true; | |
| 445 | 468 | |
| 446 | - case 'contain': | |
| 447 | - return stripos( $compare_with, $compare ) !== false; | |
| 469 | + break; | |
| 470 | + } | |
| 471 | + } | |
| 448 | 472 | |
| 449 | - case 'not_contain': | |
| 450 | - return stripos( $compare_with, $compare ) === false; | |
| 473 | + if ( in_array( $condition, array( 'not_equal', 'not_contain' ), true ) ) { | |
| 474 | + return ! $matched; | |
| 475 | + } | |
| 451 | 476 | |
| 452 | - case 'start_with': | |
| 453 | - return stripos( $compare_with, $compare ) === 0; | |
| 454 | - | |
| 455 | - case 'end_with': | |
| 456 | - return substr( $compare_with, -strlen( $compare ) ) === $compare; | |
| 457 | - | |
| 458 | - default: | |
| 459 | - return false; | |
| 460 | - }//end switch | |
| 477 | + return $matched; | |
| 461 | 478 | } |
| 462 | 479 | |
| 463 | 480 | /** |
| 464 | 481 | * @param array $filters Filter array. |
| @@ -514,8 +531,16 @@ | ||
| 514 | 531 | if ( in_array( $compare_with, $attributes_by_ids, true ) ) { |
| 515 | 532 | return 'id'; |
| 516 | 533 | } |
| 517 | 534 | |
| 535 | + /** | |
| 536 | + * ACF stores the choice value, while the option's name is its label, so | |
| 537 | + * matching on the name would compare "Red" against a stored "red". | |
| 538 | + */ | |
| 539 | + if ( is_string( $compare_with ) && strpos( $compare_with, 'acf_fields_' ) === 0 ) { | |
| 540 | + return 'id'; | |
| 541 | + } | |
| 542 | + | |
| 518 | 543 | return 'name'; |
| 519 | 544 | } |
| 520 | 545 | |
| 521 | 546 | /** |
| @@ -597,7 +622,80 @@ | ||
| 597 | 622 | |
| 598 | 623 | default: |
| 599 | 624 | return false; |
| 600 | 625 | } |
| 626 | + } | |
| 627 | + | |
| 628 | + /** | |
| 629 | + * Run one string condition against a single stored value. | |
| 630 | + * | |
| 631 | + * The negative conditions share the comparison of their positive | |
| 632 | + * counterpart; {@see self::string_compare()} inverts the result once every | |
| 633 | + * stored value has been checked. | |
| 634 | + * | |
| 635 | + * @param string $haystack One stored value. | |
| 636 | + * @param string $compare Value the merchant typed. | |
| 637 | + * @param string $condition Condition name. | |
| 638 | + */ | |
| 639 | + private function matches_string_condition( string $haystack, string $compare, string $condition ): bool { | |
| 640 | + switch ( $condition ) { | |
| 641 | + case 'equal': | |
| 642 | + case 'not_equal': | |
| 643 | + return strtolower( $haystack ) === strtolower( $compare ); | |
| 644 | + | |
| 645 | + case 'contain': | |
| 646 | + case 'not_contain': | |
| 647 | + return stripos( $haystack, $compare ) !== false; | |
| 648 | + | |
| 649 | + case 'start_with': | |
| 650 | + return stripos( $haystack, $compare ) === 0; | |
| 651 | + | |
| 652 | + case 'end_with': | |
| 653 | + return substr( $haystack, -strlen( $compare ) ) === $compare; | |
| 654 | + | |
| 655 | + default: | |
| 656 | + return false; | |
| 657 | + }//end switch | |
| 658 | + } | |
| 659 | + | |
| 660 | + /** | |
| 661 | + * Flatten an attribute value into the strings a comparison can run against. | |
| 662 | + * | |
| 663 | + * A single value yields one string, a multi value field one per entry. | |
| 664 | + * Anything that cannot be expressed as text, a nested array or an object, | |
| 665 | + * is dropped. An empty result still yields one empty string, so a negative | |
| 666 | + * condition has something to fail against rather than passing by default. | |
| 667 | + * | |
| 668 | + * @param mixed $value Attribute value. | |
| 669 | + * @return array<int, string> | |
| 670 | + */ | |
| 671 | + private function comparable_values( $value ): array { | |
| 672 | + $values = array( $value ); | |
| 673 | + | |
| 674 | + if ( is_array( $value ) ) { | |
| 675 | + $values = $value; | |
| 676 | + } | |
| 677 | + | |
| 678 | + $result = array(); | |
| 679 | + | |
| 680 | + foreach ( $values as $entry ) { | |
| 681 | + if ( null === $entry ) { | |
| 682 | + $result[] = ''; | |
| 683 | + | |
| 684 | + continue; | |
| 685 | + } | |
| 686 | + | |
| 687 | + if ( ! is_scalar( $entry ) ) { | |
| 688 | + continue; | |
| 689 | + } | |
| 690 | + | |
| 691 | + $result[] = (string) $entry; | |
| 692 | + } | |
| 693 | + | |
| 694 | + if ( empty( $result ) ) { | |
| 695 | + return array( '' ); | |
| 696 | + } | |
| 697 | + | |
| 698 | + return $result; | |
| 601 | 699 | } |
| 602 | 700 | |
| 603 | 701 | } |