Make WordPress Core

Ticket #53450: 53450_v7.diff

File 53450_v7.diff, 16.6 KB (added by janthiel, 3 years ago)

Implemented Buds feedback on comment order

  • 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 0b3ffab033d..f57e1361379 100644
    a b class WP_Meta_Query { 
    102102         * @since 5.1.0 Introduced $compare_key clause parameter, which enables LIKE key matches.
    103103         * @since 5.3.0 Increased the number of operators available to $compare_key. Introduced $type_key,
    104104         *              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
    105106         *
    106107         * @param array $meta_query {
    107108         *     Array of meta query clauses. When first-order clauses or sub-clauses use strings as
    class WP_Meta_Query { 
    112113         *     @type array  ...$0 {
    113114         *         Optional. An array of first-order clause parameters, or another fully-formed meta query.
    114115         *
    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 'contains', 'startswith' or
     122         *                                              'endswith'. 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 'contains', 'startswith' or
     133         *                                              'endswith'. 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'.
    133138         *     }
    134139         * }
    135140         */
    public function parse_query_vars( $qv ) { 
    246251                 * the rest of the meta_query).
    247252                 */
    248253                $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 ) {
    250255                        if ( ! empty( $qv[ "meta_$key" ] ) ) {
    251256                                $primary_meta_query[ $key ] = $qv[ "meta_$key" ];
    252257                        }
    public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) 
    545550                $meta_compare     = $clause['compare'];
    546551                $meta_compare_key = $clause['compare_key'];
    547552
     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
    548574                // First build the JOIN clause, if one is required.
    549575                $join = '';
    550576
    public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) 
    632658                                                $where = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    633659                                                break;
    634660                                        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'] ) ) );
    636662                                                $where              = $wpdb->prepare( "$alias.meta_key LIKE %s", $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    637663                                                break;
    638664                                        case 'IN':
    public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) 
    657683                                                break;
    658684                                        case 'NOT LIKE':
    659685                                                $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
    663688                                                break;
    664689                                        case 'NOT IN':
    665690                                                $array_subclause     = '(' . substr( str_repeat( ',%s', count( $clause['key'] ) ), 1 ) . ') ';
    public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) 
    709734
    710735                                case 'LIKE':
    711736                                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 ) );
    713738                                        $where      = $wpdb->prepare( '%s', $meta_value );
    714739                                        break;
    715740
  • tests/phpunit/tests/query/metaQuery.php

    diff --git a/tests/phpunit/tests/query/metaQuery.php b/tests/phpunit/tests/query/metaQuery.php
    index c9e73ec8a50..312cb9fee35 100644
    a b public function test_meta_query_single_query_compare_not_like() { 
    271271                $this->assertSameSets( $expected, $query->posts );
    272272        }
    273273
     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
    274410        public function test_meta_query_single_query_compare_between_not_between() {
    275411                $p1 = self::factory()->post->create();
    276412                $p2 = self::factory()->post->create();
    public function test_meta_query_decimal_results() { 
    13221458        public function test_meta_vars_should_be_converted_to_meta_query() {
    13231459                $q = new WP_Query(
    13241460                        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',
    13291467                        )
    13301468                );
    13311469
    public function test_meta_vars_should_be_converted_to_meta_query() { 
    13331471                $this->assertSame( '5', $q->meta_query->queries[0]['value'] );
    13341472                $this->assertSame( '>', $q->meta_query->queries[0]['compare'] );
    13351473                $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'] );
    13361476        }
    13371477
    13381478        /**
    public function test_compare_key_like() { 
    18742014                $this->assertSameSets( array( $posts[0], $posts[2] ), $q->posts );
    18752015        }
    18762016
     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
    18772112        /**
    18782113         * @ticket 42409
    18792114         */