diff --git a/includes/query/class-tmdo-post-query-router.php b/includes/query/class-tmdo-post-query-router.php index 443505e..623585a 100644 --- a/includes/query/class-tmdo-post-query-router.php +++ b/includes/query/class-tmdo-post-query-router.php @@ -678,11 +678,17 @@ class TMDO_Post_Query_Router { */ private static function route_clause( array $clause, array $field ): ?array { $compare = strtoupper( trim( (string) ( $clause['compare'] ?? '=' ) ) ); - $type = strtoupper( trim( (string) ( $clause['type'] ?? '' ) ) ); - $type = '' === $type ? 'CHAR' : $type; - $value = $clause['value'] ?? ''; - $kind = $field['type'] ?? 'text'; - $multi = in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ); + // Without 'value', WP_Meta_Query checks only that the key exists (it adds a + // value condition only when one is set): HivePress's featured-listing + // lookup, run on every search page, is array( 'key' => 'hp_featured' ). + if ( ! array_key_exists( 'value', $clause ) && ! in_array( $compare, array( 'EXISTS', 'NOT EXISTS' ), true ) ) { + $compare = 'EXISTS'; + } + $type = strtoupper( trim( (string) ( $clause['type'] ?? '' ) ) ); + $type = '' === $type ? 'CHAR' : $type; + $value = $clause['value'] ?? ''; + $kind = $field['type'] ?? 'text'; + $multi = in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ); if ( ! in_array( $compare, self::COMPARES, true ) || ( ! $multi && is_array( $value ) ) ) { return null; diff --git a/tests/integration/PostQueryRouterTest.php b/tests/integration/PostQueryRouterTest.php index 2f82d5b..83f94ac 100644 --- a/tests/integration/PostQueryRouterTest.php +++ b/tests/integration/PostQueryRouterTest.php @@ -412,4 +412,28 @@ class PostQueryRouterTest extends TestCase { $this->assertStringContainsString( "LIKE '%B1\\\\_1\\\\%%'", $router->posts_where( '', $query ) ); } + + /** + * A clause with no value tests only that the key exists (WP_Meta_Query adds + * a value condition only when 'value' is set), so it routes as EXISTS. + * HivePress's featured-listing lookup on every search page is one: + * array( 'key' => 'hp_featured' ) — it kept the whole query native (2.4 PR-122). + */ + public function test_a_clause_without_a_value_routes_as_exists(): void { + $this->set_post_mode( 'aeav_only' ); + $router = new WPDO_Post_Query_Router(); + $query = $this->make_query( + array( + 'post_type' => 'product', + 'meta_query' => array( + array( 'key' => '_stock' ), + array( 'key' => '_price', 'value' => array( 10, 20 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ), + ), + ) + ); + $router->pre_get_posts( $query ); + + $this->assertEmpty( $query->get( 'meta_query' ), 'both clauses routed' ); + $this->assertStringContainsString( '`_stock` IS NOT NULL', $router->posts_where( '', $query ) ); + } }