Ticket #53450: 53450_v5.diff
File 53450_v5.diff, 16.5 KB (added by , 3 years ago) |
---|
-
src/wp-includes/class-wp-meta-query.php
diff --git a/src/wp-includes/class-wp-meta-query.php b/src/wp-includes/class-wp-meta-query.php index 5a1b0c41479..313f42e7ea7 100644
a b class WP_Meta_Query { 102 102 * @since 5.1.0 Introduced $compare_key clause parameter, which enables LIKE key matches. 103 103 * @since 5.3.0 Increased the number of operators available to $compare_key. Introduced $type_key, 104 104 * which enables the $key to be cast to a new data type for comparisons. 105 * @since 5.9.0 Introduced $compare_key_like_mode and $compare_like_mode to LIKE queries 105 106 * 106 107 * @param array $meta_query { 107 108 * Array of meta query clauses. When first-order clauses or sub-clauses use strings as … … class WP_Meta_Query { 112 113 * @type array ...$0 { 113 114 * Optional. An array of first-order clause parameters, or another fully-formed meta query. 114 115 * 115 * @type string $key Meta key to filter by. 116 * @type string $compare_key MySQL operator used for comparing the $key. Accepts '=', '!=' 117 * 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'REGEXP', 'NOT REGEXP', 'RLIKE', 118 * 'EXISTS' (alias of '=') or 'NOT EXISTS' (alias of '!='). 119 * Default is 'IN' when `$key` is an array, '=' otherwise. 120 * @type string $type_key MySQL data type that the meta_key column will be CAST to for 121 * comparisons. Accepts 'BINARY' for case-sensitive regular expression 122 * comparisons. Default is ''. 123 * @type string $value Meta value to filter by. 124 * @type string $compare MySQL operator used for comparing the $value. Accepts '=', 125 * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 126 * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP', 127 * 'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'. 128 * Default is 'IN' when `$value` is an array, '=' otherwise. 129 * @type string $type MySQL data type that the meta_value column will be CAST to for 130 * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 131 * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'. 132 * Default is 'CHAR'. 116 * @type string $key Meta key to filter by. 117 * @type string $compare_key MySQL operator used for comparing the $key. Accepts '=', '!=' 118 * 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'REGEXP', 'NOT REGEXP', 'RLIKE', 119 * 'EXISTS' (alias of '=') or 'NOT EXISTS' (alias of '!='). 120 * Default is 'IN' when `$key` is an array, '=' otherwise. 121 * @type string $compare_key_like_mode Search mode for LIKE compares. Accepts 'startswith ', 'endswith' or 122 * 'contains'. Default is 'contains'. 123 * @type string $type_key MySQL data type that the meta_key column will be CAST to for 124 * comparisons. Accepts 'BINARY' for case-sensitive regular expression 125 * comparisons. Default is ''. 126 * @type string $value Meta value to filter by. 127 * @type string $compare MySQL operator used for comparing the $value. Accepts '=', 128 * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 129 * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP', 130 * 'NOT REGEXP', 'RLIKE', 'EXISTS' or 'NOT EXISTS'. 131 * Default is 'IN' when `$value` is an array, '=' otherwise. 132 * @type string $compare_like_mode Search mode for LIKE compares. Accepts 'startswith ', 'endswith' or 133 * 'contains'. Default is 'contains'. 134 * @type string $type MySQL data type that the meta_value column will be CAST to for 135 * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 136 * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'. 137 * Default is 'CHAR'. 133 138 * } 134 139 * } 135 140 */ … … public function parse_query_vars( $qv ) { 246 251 * the rest of the meta_query). 247 252 */ 248 253 $primary_meta_query = array(); 249 foreach ( array( 'key', 'compare ', 'type', 'compare_key', 'type_key' ) as $key ) {254 foreach ( array( 'key', 'compare_key_like_mode', 'compare', 'compare_like_mode', 'type', 'compare_key', 'type_key' ) as $key ) { 250 255 if ( ! empty( $qv[ "meta_$key" ] ) ) { 251 256 $primary_meta_query[ $key ] = $qv[ "meta_$key" ]; 252 257 } … … public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) 545 550 $meta_compare = $clause['compare']; 546 551 $meta_compare_key = $clause['compare_key']; 547 552 553 // LIKE and NOT LIKE supports three search modes. Startswith, Endswith and Contains. 554 // Default to 'contains' for backward compat 555 if ( 'LIKE' === $meta_compare_key || 'NOT LIKE' === $meta_compare_key ) { 556 // Key compares 557 if ( empty( $clause['compare_key_like_mode'] ) ) { 558 $clause['compare_key_like_mode'] = 'contains'; 559 } 560 $meta_compare_key_like_mode = $clause['compare_key_like_mode']; 561 } 562 if ( 'LIKE' === $meta_compare || 'NOT LIKE' === $meta_compare ) { 563 // Value compares 564 if ( empty( $clause['compare_like_mode'] ) ) { 565 $clause['compare_like_mode'] = 'contains'; 566 } 567 $meta_compare_like_mode = $clause['compare_like_mode']; 568 } 569 // Query templates for LIKE / NOT LIKE queries 570 $meta_compare_like_value_tpl['startswith'] = '%s%%'; 571 $meta_compare_like_value_tpl['endswith'] = '%%%s'; 572 $meta_compare_like_value_tpl['contains'] = '%%%s%%'; 573 548 574 // First build the JOIN clause, if one is required. 549 575 $join = ''; 550 576 … … public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) 632 658 $where = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared 633 659 break; 634 660 case 'LIKE': 635 $meta_compare_value = '%' . $wpdb->esc_like( trim( $clause['key'] ) ) . '%';661 $meta_compare_value = sprintf( $meta_compare_like_value_tpl[ $meta_compare_key_like_mode ], $wpdb->esc_like( trim( $clause['key'] ) ) ); 636 662 $where = $wpdb->prepare( "$alias.meta_key LIKE %s", $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared 637 663 break; 638 664 case 'IN': … … public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) 657 683 break; 658 684 case 'NOT LIKE': 659 685 $meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key LIKE %s " . $meta_compare_string_end; 660 661 $meta_compare_value = '%' . $wpdb->esc_like( trim( $clause['key'] ) ) . '%'; 662 $where = $wpdb->prepare( $meta_compare_string, $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared 686 $meta_compare_value = sprintf( $meta_compare_like_value_tpl[ $meta_compare_key_like_mode ], $wpdb->esc_like( trim( $clause['key'] ) ) ); 687 $where = $wpdb->prepare( $meta_compare_string, $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared 663 688 break; 664 689 case 'NOT IN': 665 690 $array_subclause = '(' . substr( str_repeat( ',%s', count( $clause['key'] ) ), 1 ) . ') '; … … public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) 709 734 710 735 case 'LIKE': 711 736 case 'NOT LIKE': 712 $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';737 $meta_value = sprintf( $meta_compare_like_value_tpl[ $meta_compare_like_mode ], $wpdb->esc_like( $meta_value ) ); 713 738 $where = $wpdb->prepare( '%s', $meta_value ); 714 739 break; 715 740 -
tests/phpunit/tests/query/metaQuery.php
diff --git a/tests/phpunit/tests/query/metaQuery.php b/tests/phpunit/tests/query/metaQuery.php index c9e73ec8a50..0a1963dfa0c 100644
a b public function test_meta_query_single_query_compare_not_like() { 271 271 $this->assertSameSets( $expected, $query->posts ); 272 272 } 273 273 274 public function test_meta_query_single_query_compare_startswith() { 275 $p1 = self::factory()->post->create(); 276 $p2 = self::factory()->post->create(); 277 $p3 = self::factory()->post->create(); 278 $p4 = self::factory()->post->create(); 279 $p5 = self::factory()->post->create(); 280 $p6 = self::factory()->post->create(); 281 282 add_post_meta( $p1, 'foo', 'barSTARTSWITH' ); 283 add_post_meta( $p2, 'foo', 'ENDSWITHbar' ); 284 add_post_meta( $p3, 'foo', 'CONTAINSbarCONTAINS' ); 285 add_post_meta( $p4, 'foo', 'nomatch' ); 286 add_post_meta( $p5, 'foo', 'barbar' ); 287 288 $query = new WP_Query( 289 array( 290 'update_post_meta_cache' => false, 291 'update_post_term_cache' => false, 292 'fields' => 'ids', 293 'meta_query' => array( 294 array( 295 'key' => 'foo', 296 'value' => 'bar', 297 'compare' => 'LIKE', 298 'compare_like_mode' => 'startswith', 299 ), 300 ), 301 ) 302 ); 303 304 $expected = array( $p1, $p5 ); 305 $this->assertSameSets( $expected, $query->posts ); 306 } 307 308 public function test_meta_query_single_query_compare_not_startswith() { 309 $p1 = self::factory()->post->create(); 310 $p2 = self::factory()->post->create(); 311 $p3 = self::factory()->post->create(); 312 $p4 = self::factory()->post->create(); 313 $p5 = self::factory()->post->create(); 314 $p6 = self::factory()->post->create(); 315 316 add_post_meta( $p1, 'foo', 'barSTARTSWITH' ); 317 add_post_meta( $p2, 'foo', 'ENDSWITHbar' ); 318 add_post_meta( $p3, 'foo', 'CONTAINSbarCONTAINS' ); 319 add_post_meta( $p4, 'foo', 'nomatch' ); 320 add_post_meta( $p5, 'foo', 'barbar' ); 321 322 $query = new WP_Query( 323 array( 324 'update_post_meta_cache' => false, 325 'update_post_term_cache' => false, 326 'fields' => 'ids', 327 'meta_query' => array( 328 array( 329 'key' => 'foo', 330 'value' => 'bar', 331 'compare' => 'NOT LIKE', 332 'compare_like_mode' => 'startswith', 333 ), 334 ), 335 ) 336 ); 337 338 $expected = array( $p2, $p3, $p4 ); 339 $this->assertSameSets( $expected, $query->posts ); 340 } 341 342 public function test_meta_query_single_query_compare_endswith() { 343 $p1 = self::factory()->post->create(); 344 $p2 = self::factory()->post->create(); 345 $p3 = self::factory()->post->create(); 346 $p4 = self::factory()->post->create(); 347 $p5 = self::factory()->post->create(); 348 $p6 = self::factory()->post->create(); 349 350 add_post_meta( $p1, 'foo', 'barSTARTSWITH' ); 351 add_post_meta( $p2, 'foo', 'ENDSWITHbar' ); 352 add_post_meta( $p3, 'foo', 'CONTAINSbarCONTAINS' ); 353 add_post_meta( $p4, 'foo', 'nomatch' ); 354 add_post_meta( $p5, 'foo', 'barbar' ); 355 356 $query = new WP_Query( 357 array( 358 'update_post_meta_cache' => false, 359 'update_post_term_cache' => false, 360 'fields' => 'ids', 361 'meta_query' => array( 362 array( 363 'key' => 'foo', 364 'value' => 'ar', 365 'compare' => 'LIKE', 366 'compare_like_mode' => 'endswith', 367 ), 368 ), 369 ) 370 ); 371 372 $expected = array( $p2, $p5 ); 373 $this->assertSameSets( $expected, $query->posts ); 374 } 375 376 public function test_meta_query_single_query_compare_not_endswith() { 377 $p1 = self::factory()->post->create(); 378 $p2 = self::factory()->post->create(); 379 $p3 = self::factory()->post->create(); 380 $p4 = self::factory()->post->create(); 381 $p5 = self::factory()->post->create(); 382 $p6 = self::factory()->post->create(); 383 384 add_post_meta( $p1, 'foo', 'barSTARTSWITH' ); 385 add_post_meta( $p2, 'foo', 'ENDSWITHbar' ); 386 add_post_meta( $p3, 'foo', 'CONTAINSbarCONTAINS' ); 387 add_post_meta( $p4, 'foo', 'nomatch' ); 388 add_post_meta( $p5, 'foo', 'barbar' ); 389 390 $query = new WP_Query( 391 array( 392 'update_post_meta_cache' => false, 393 'update_post_term_cache' => false, 394 'fields' => 'ids', 395 'meta_query' => array( 396 array( 397 'key' => 'foo', 398 'value' => 'bar', 399 'compare' => 'NOT LIKE', 400 'compare_like_mode' => 'endswith', 401 ), 402 ), 403 ) 404 ); 405 406 $expected = array( $p1, $p3, $p4 ); 407 $this->assertSameSets( $expected, $query->posts ); 408 } 409 274 410 public function test_meta_query_single_query_compare_between_not_between() { 275 411 $p1 = self::factory()->post->create(); 276 412 $p2 = self::factory()->post->create(); … … public function test_meta_query_decimal_results() { 1322 1458 public function test_meta_vars_should_be_converted_to_meta_query() { 1323 1459 $q = new WP_Query( 1324 1460 array( 1325 'meta_key' => 'foo', 1326 'meta_value' => '5', 1327 'meta_compare' => '>', 1328 'meta_type' => 'SIGNED', 1461 'meta_key' => 'foo', 1462 'meta_value' => '5', 1463 'meta_compare' => '>', 1464 'meta_type' => 'SIGNED', 1465 'meta_compare_key_like_mode' => 'endswith', 1466 'meta_compare_like_mode' => 'startswith', 1329 1467 ) 1330 1468 ); 1331 1469 … … public function test_meta_vars_should_be_converted_to_meta_query() { 1333 1471 $this->assertSame( '5', $q->meta_query->queries[0]['value'] ); 1334 1472 $this->assertSame( '>', $q->meta_query->queries[0]['compare'] ); 1335 1473 $this->assertSame( 'SIGNED', $q->meta_query->queries[0]['type'] ); 1474 $this->assertSame( 'startswith', $q->meta_query->queries[0]['compare_like_mode'] ); 1475 $this->assertSame( 'endswith', $q->meta_query->queries[0]['compare_key_like_mode'] ); 1336 1476 } 1337 1477 1338 1478 /** … … public function test_compare_key_like() { 1874 2014 $this->assertSameSets( array( $posts[0], $posts[2] ), $q->posts ); 1875 2015 } 1876 2016 2017 public function test_compare_key_like_startswith() { 2018 $posts = self::factory()->post->create_many( 3 ); 2019 2020 add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' ); 2021 add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' ); 2022 add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' ); 2023 2024 $q = new WP_Query( 2025 array( 2026 'meta_query' => array( 2027 array( 2028 'compare_key' => 'LIKE', 2029 'compare_key_like_mode' => 'startswith', 2030 'key' => 'aaa_foo', 2031 ), 2032 ), 2033 'fields' => 'ids', 2034 ) 2035 ); 2036 2037 $this->assertSameSets( array( $posts[0], $posts[2] ), $q->posts ); 2038 } 2039 2040 public function test_compare_key_like_endswith() { 2041 $posts = self::factory()->post->create_many( 3 ); 2042 2043 add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' ); 2044 add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' ); 2045 add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' ); 2046 2047 $q = new WP_Query( 2048 array( 2049 'meta_query' => array( 2050 array( 2051 'compare_key' => 'LIKE', 2052 'compare_key_like_mode' => 'endswith', 2053 'key' => 'foo_bbb', 2054 ), 2055 ), 2056 'fields' => 'ids', 2057 ) 2058 ); 2059 2060 $this->assertSameSets( array( $posts[2] ), $q->posts ); 2061 } 2062 2063 public function test_compare_key_like_explicit_contains() { 2064 $posts = self::factory()->post->create_many( 3 ); 2065 2066 add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' ); 2067 add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' ); 2068 add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' ); 2069 2070 $q = new WP_Query( 2071 array( 2072 'meta_query' => array( 2073 array( 2074 'compare_key' => 'LIKE', 2075 'compare_key_like_mode' => 'contains', 2076 'key' => 'a_foo_', 2077 ), 2078 ), 2079 'fields' => 'ids', 2080 ) 2081 ); 2082 2083 $this->assertSameSets( array( $posts[0], $posts[2] ), $q->posts ); 2084 } 2085 2086 public function test_compare_key_like_and_value_like() { 2087 $posts = self::factory()->post->create_many( 3 ); 2088 2089 add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' ); 2090 add_post_meta( $posts[1], 'aaa_bar_aaa', 'def' ); 2091 add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' ); 2092 2093 $q = new WP_Query( 2094 array( 2095 'meta_query' => array( 2096 array( 2097 'compare_key' => 'LIKE', 2098 'compare_key_like_mode' => 'endswith', 2099 'key' => 'aaa', 2100 'compare' => 'LIKE', 2101 'compare_like_mode' => 'startswith', 2102 'value' => 'de', 2103 ), 2104 ), 2105 'fields' => 'ids', 2106 ) 2107 ); 2108 2109 $this->assertSameSets( array( $posts[1] ), $q->posts ); 2110 } 2111 1877 2112 /** 1878 2113 * @ticket 42409 1879 2114 */