Make WordPress Core

Ticket #43446: 43446.diff

File 43446.diff, 15.2 KB (added by boonebgorges, 7 years ago)
  • src/wp-includes/class-wp-meta-query.php

    diff --git src/wp-includes/class-wp-meta-query.php src/wp-includes/class-wp-meta-query.php
    index 3732f25b8f..b8965033e7 100644
    class WP_Meta_Query { 
    100100         * @since 3.2.0
    101101         * @since 4.2.0 Introduced support for naming query clauses by associative array keys.
    102102         * @since 5.1.0 Introduced $compare_key clause parameter, which enables LIKE key matches.
     103         * @since 5.3.0 Expanded operators available to $compare_key.
     104         * @since 5.3.0 Introduced $type_key, which enables key casts.
    103105         *
    104106         * @param array $meta_query {
    105107         *     Array of meta query clauses. When first-order clauses or sub-clauses use strings as
    class WP_Meta_Query { 
    111113         *         Optional. An array of first-order clause parameters, or another fully-formed meta query.
    112114         *
    113115         *         @type string $key         Meta key to filter by.
    114          *         @type string $compare_key MySQL operator used for comparing the $key. Accepts '=' and 'LIKE'.
    115          *                                   Default '='.
     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' or 'NOT EXISTS'.
     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 regular expression comparisons.
     122         *                                   Default is '' for case-insensitive REGEXP comparisons.
    116123         *         @type string $value       Meta value to filter by.
    117124         *         @type string $compare     MySQL operator used for comparing the $value. Accepts '=',
    118125         *                                   '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE',
    class WP_Meta_Query { 
    239246                 * the rest of the meta_query).
    240247                 */
    241248                $primary_meta_query = array();
    242                 foreach ( array( 'key', 'compare', 'type', 'compare_key' ) as $key ) {
     249                foreach ( array( 'key', 'compare', 'type', 'compare_key', 'type_key' ) as $key ) {
    243250                        if ( ! empty( $qv[ "meta_$key" ] ) ) {
    244251                                $primary_meta_query[ $key ] = $qv[ "meta_$key" ];
    245252                        }
    class WP_Meta_Query { 
    498505                        $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
    499506                }
    500507
    501                 if ( ! in_array(
    502                         $clause['compare'],
    503                         array(
    504                                 '=',
    505                                 '!=',
    506                                 '>',
    507                                 '>=',
    508                                 '<',
    509                                 '<=',
    510                                 'LIKE',
    511                                 'NOT LIKE',
    512                                 'IN',
    513                                 'NOT IN',
    514                                 'BETWEEN',
    515                                 'NOT BETWEEN',
    516                                 'EXISTS',
    517                                 'NOT EXISTS',
    518                                 'REGEXP',
    519                                 'NOT REGEXP',
    520                                 'RLIKE',
    521                         )
    522                 ) ) {
     508                $non_numeric_operators = array(
     509                        '=',
     510                        '!=',
     511                        'LIKE',
     512                        'NOT LIKE',
     513                        'IN',
     514                        'NOT IN',
     515                        'EXISTS',
     516                        'NOT EXISTS',
     517                        'RLIKE',
     518                        'REGEXP',
     519                        'NOT REGEXP',
     520                );
     521
     522                $numeric_operators = array(
     523                        '>',
     524                        '>=',
     525                        '<',
     526                        '<=',
     527                        'BETWEEN',
     528                        'NOT BETWEEN',
     529                );
     530
     531                if ( ! in_array( $clause['compare'], $non_numeric_operators, true ) && ! in_array( $clause['compare'], $numeric_operators, true ) ) {
    523532                        $clause['compare'] = '=';
    524533                }
    525534
    526                 if ( isset( $clause['compare_key'] ) && 'LIKE' === strtoupper( $clause['compare_key'] ) ) {
     535                if ( isset( $clause['compare_key'] ) ) {
    527536                        $clause['compare_key'] = strtoupper( $clause['compare_key'] );
    528537                } else {
     538                        $clause['compare_key'] = isset( $clause['key'] ) && is_array( $clause['key'] ) ? 'IN' : '=';
     539                }
     540
     541                if ( ! in_array( $clause['compare_key'], $non_numeric_operators, true ) ) {
    529542                        $clause['compare_key'] = '=';
    530543                }
    531544
    class WP_Meta_Query { 
    594607                        if ( 'NOT EXISTS' === $meta_compare ) {
    595608                                $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
    596609                        } else {
    597                                 if ( 'LIKE' === $meta_compare_key ) {
    598                                         $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key LIKE %s", '%' . $wpdb->esc_like( trim( $clause['key'] ) ) . '%' );
    599                                 } else {
    600                                         $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
     610                                /**
     611                                 * In joined clauses negative operators have to be nested into a
     612                                 * NOT EXISTS clause and flipped, to avoid returning records with
     613                                 * matching post IDs but different meta keys. Here we prepare the
     614                                 * nested clause.
     615                                 */
     616                                if ( in_array( $meta_compare_key, array( '!=', 'NOT IN', 'NOT LIKE', 'NOT EXISTS', 'NOT REGEXP' ), true ) ) {
     617                                        // Negative clauses may be reused.
     618                                        $i                     = count( $this->table_aliases );
     619                                        $subquery_alias        = $i ? 'mt' . $i : $this->meta_table;
     620                                        $this->table_aliases[] = $subquery_alias;
     621
     622                                        $meta_compare_string_start  = 'NOT EXISTS (';
     623                                        $meta_compare_string_start .= "SELECT 1 FROM $wpdb->postmeta $subquery_alias ";
     624                                        $meta_compare_string_start .= "WHERE $subquery_alias.post_ID = $alias.post_ID ";
     625                                        $meta_compare_string_end    = 'LIMIT 1';
     626                                        $meta_compare_string_end   .= ')';
    601627                                }
     628
     629                                switch ( $meta_compare_key ) {
     630                                        case '=':
     631                                        case 'EXISTS':
     632                                                $where = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     633                                                break;
     634                                        case 'LIKE':
     635                                                $meta_compare_value = '%' . $wpdb->esc_like( trim( $clause['key'] ) ) . '%';
     636                                                $where              = $wpdb->prepare( "$alias.meta_key LIKE %s", $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     637                                                break;
     638                                        case 'IN':
     639                                                $meta_compare_string = "$alias.meta_key IN (" . substr( str_repeat( ',%s', count( $clause['key'] ) ), 1 ) . ')';
     640                                                $where               = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     641                                                break;
     642                                        case 'RLIKE':
     643                                        case 'REGEXP':
     644                                                $operator = $meta_compare_key;
     645                                                if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) {
     646                                                        $cast = 'BINARY';
     647                                                } else {
     648                                                        $cast = '';
     649                                                }
     650                                                $where = $wpdb->prepare( "$alias.meta_key $operator $cast %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     651                                                break;
     652
     653                                        case '!=':
     654                                        case 'NOT EXISTS':
     655                                                $meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key = %s " . $meta_compare_string_end;
     656                                                $where               = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     657                                                break;
     658                                        case 'NOT LIKE':
     659                                                $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
     663                                                break;
     664                                        case 'NOT IN':
     665                                                $array_subclause     = '(' . substr( str_repeat( ',%s', count( $clause['key'] ) ), 1 ) . ') ';
     666                                                $meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key IN " . $array_subclause . $meta_compare_string_end;
     667                                                $where               = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     668                                                break;
     669                                        case 'NOT REGEXP':
     670                                                $operator = $meta_compare_key;
     671                                                if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) {
     672                                                        $cast = 'BINARY';
     673                                                } else {
     674                                                        $cast = '';
     675                                                }
     676
     677                                                $meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key REGEXP $cast %s " . $meta_compare_string_end;
     678                                                $where               = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     679                                                break;
     680                                }
     681
     682                                $sql_chunks['where'][] = $where;
    602683                        }
    603684                }
    604685
  • src/wp-includes/class-wp-query.php

    diff --git src/wp-includes/class-wp-query.php src/wp-includes/class-wp-query.php
    index 57fd58a4f6..3069397f1f 100644
    class WP_Query { 
    614614         * @since 4.6.0 Added 'post_name__in' support for `$orderby`. Introduced the `$lazy_load_term_meta` argument.
    615615         * @since 4.9.0 Introduced the `$comment_count` parameter.
    616616         * @since 5.1.0 Introduced the `$meta_compare_key` parameter.
     617         * @since 5.3.0 Introduced the `$meta_type_key` parameter.
    617618         *
    618619         * @param string|array $query {
    619620         *     Optional. Array or string of Query parameters.
    class WP_Query { 
    655656         *     @type array        $meta_query              An associative array of WP_Meta_Query arguments. See WP_Meta_Query.
    656657         *     @type string       $meta_value              Custom field value.
    657658         *     @type int          $meta_value_num          Custom field value number.
     659         *     @type string       $meta_type_key           Cast for 'meta_key'. See WP_Meta_Query::construct().
    658660         *     @type int          $menu_order              The menu order of the posts.
    659661         *     @type int          $monthnum                The two-digit month. Default empty. Accepts numbers 1-12.
    660662         *     @type string       $name                    Post slug.
  • tests/phpunit/tests/query/metaQuery.php

    diff --git tests/phpunit/tests/query/metaQuery.php tests/phpunit/tests/query/metaQuery.php
    index 26b763e244..9303e60b95 100644
    class Tests_Query_MetaQuery extends WP_UnitTestCase { 
    19221922                $this->assertEqualSets( array( $posts[0] ), $q->posts );
    19231923
    19241924        }
     1925
     1926        /**
     1927         * @ticket 43446
     1928         */
     1929        public function test_compare_key_not_equals() {
     1930                $posts = self::factory()->post->create_many( 3 );
     1931
     1932                add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' );
     1933                add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' );
     1934                add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' );
     1935                add_post_meta( $posts[2], 'aaa_foo_ccc', 'abc' );
     1936
     1937                $q = new WP_Query(
     1938                        array(
     1939                                'meta_query' => array(
     1940                                        array(
     1941                                                'compare_key' => '!=',
     1942                                                'key'         => 'aaa_foo_bbb',
     1943                                                'value'       => 'abc',
     1944                                        ),
     1945                                ),
     1946                                'fields'     => 'ids',
     1947                        )
     1948                );
     1949
     1950                $this->assertEqualSets( array( $posts[0], $posts[1] ), $q->posts );
     1951        }
     1952
     1953        /**
     1954         * @ticket 43446
     1955         */
     1956        public function test_compare_key_not_like() {
     1957                $posts = self::factory()->post->create_many( 3 );
     1958
     1959                add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' );
     1960                add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' );
     1961                add_post_meta( $posts[1], 'aaa_bar_ccc', 'abc' );
     1962                add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' );
     1963
     1964                $q = new WP_Query(
     1965                        array(
     1966                                'meta_query' => array(
     1967                                        array(
     1968                                                'compare_key' => 'NOT LIKE',
     1969                                                'key'         => 'aaa_bar',
     1970                                                'value'       => 'abc',
     1971                                        ),
     1972                                ),
     1973                                'fields'     => 'ids',
     1974                        )
     1975                );
     1976
     1977                $this->assertEqualSets( array( $posts[0], $posts[2] ), $q->posts );
     1978        }
     1979
     1980        /**
     1981         * @ticket 43446
     1982         */
     1983        public function test_compare_key_in() {
     1984                $posts = self::factory()->post->create_many( 3 );
     1985
     1986                add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' );
     1987                add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' );
     1988                add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' );
     1989
     1990                $q = new WP_Query(
     1991                        array(
     1992                                'meta_query' => array(
     1993                                        array(
     1994                                                'compare_key' => 'IN',
     1995                                                'key'         => array( 'aaa_foo_bbb', 'aaa_bar_aaa' ),
     1996                                        ),
     1997                                ),
     1998                                'fields'     => 'ids',
     1999                        )
     2000                );
     2001
     2002                $this->assertEqualSets( array( $posts[1], $posts[2] ), $q->posts );
     2003        }
     2004
     2005        /**
     2006         * @ticket 43446
     2007         */
     2008        public function test_compare_key_not_in() {
     2009                $posts = self::factory()->post->create_many( 3 );
     2010
     2011                add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' );
     2012                add_post_meta( $posts[0], 'aaa_foo_ddd', 'abc' );
     2013                add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' );
     2014                add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' );
     2015                add_post_meta( $posts[2], 'aaa_foo_ccc', 'abc' );
     2016
     2017                $q = new WP_Query(
     2018                        array(
     2019                                'meta_query' => array(
     2020                                        array(
     2021                                                'compare_key' => 'NOT IN',
     2022                                                'key'         => array( 'aaa_foo_bbb', 'aaa_foo_ddd' ),
     2023                                        ),
     2024                                ),
     2025                                'fields'     => 'ids',
     2026                        )
     2027                );
     2028
     2029                $this->assertEqualSets( array( $posts[1] ), $q->posts );
     2030        }
     2031
     2032        /**
     2033         * @ticket 43446
     2034         */
     2035        public function test_compare_key_not_exists() {
     2036                $posts = self::factory()->post->create_many( 3 );
     2037
     2038                add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' );
     2039                add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' );
     2040                add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' );
     2041                add_post_meta( $posts[2], 'aaa_foo_ccc', 'abc' );
     2042
     2043                $q = new WP_Query(
     2044                        array(
     2045                                'meta_query' => array(
     2046                                        array(
     2047                                                'compare_key' => 'NOT EXISTS',
     2048                                                'key'         => 'aaa_foo_bbb',
     2049                                                'value'       => 'abc',
     2050                                        ),
     2051                                ),
     2052                                'fields'     => 'ids',
     2053                        )
     2054                );
     2055
     2056                $this->assertEqualSets( array( $posts[0], $posts[1] ), $q->posts );
     2057        }
     2058
     2059        /**
     2060         * @ticket 43446
     2061         */
     2062        public function test_compare_key_exists() {
     2063                $posts = self::factory()->post->create_many( 3 );
     2064
     2065                add_post_meta( $posts[0], 'aaa_foo_aaa', 'abc' );
     2066                add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' );
     2067                add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' );
     2068                add_post_meta( $posts[2], 'aaa_foo_ccc', 'abc' );
     2069
     2070                $q = new WP_Query(
     2071                        array(
     2072                                'meta_query' => array(
     2073                                        array(
     2074                                                'compare_key' => 'EXISTS',
     2075                                                'key'         => 'aaa_foo_bbb',
     2076                                                'value'       => 'abc',
     2077                                        ),
     2078                                ),
     2079                                'fields'     => 'ids',
     2080                        )
     2081                );
     2082
     2083                $this->assertEqualSets( array( $posts[2] ), $q->posts );
     2084        }
     2085
     2086        /**
     2087         * @ticket 43446
     2088         */
     2089        public function test_compare_key_regexp_rlike() {
     2090                $posts = self::factory()->post->create_many( 3 );
     2091
     2092                add_post_meta( $posts[0], 'AAA_FOO_AAA', 'abc' );
     2093                add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' );
     2094                add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' );
     2095                add_post_meta( $posts[2], 'aaa_foo_aaa', 'abc' );
     2096
     2097                $q = new WP_Query(
     2098                        array(
     2099                                'meta_query' => array(
     2100                                        array(
     2101                                                'compare_key' => 'REGEXP',
     2102                                                'key'         => 'AAA_foo_.*',
     2103                                        ),
     2104                                ),
     2105                                'fields'     => 'ids',
     2106                        )
     2107                );
     2108
     2109                $this->assertEqualSets( array( $posts[0], $posts[2] ), $q->posts );
     2110
     2111                $q = new WP_Query(
     2112                        array(
     2113                                'meta_query' => array(
     2114                                        array(
     2115                                                'compare_key' => 'RLIKE',
     2116                                                'key'         => 'AAA_FOO_.*',
     2117                                                'type_key'    => 'BINARY',
     2118                                        ),
     2119                                ),
     2120                                'fields'     => 'ids',
     2121                        )
     2122                );
     2123
     2124                $this->assertEqualSets( array( $posts[0] ), $q->posts );
     2125        }
     2126
     2127        /**
     2128         * @ticket 43446
     2129         */
     2130        public function test_compare_key_not_regexp() {
     2131                $posts = self::factory()->post->create_many( 3 );
     2132
     2133                add_post_meta( $posts[0], 'AAA_FOO_AAA', 'abc' );
     2134                add_post_meta( $posts[0], 'AAA_foo_AAA', 'abc' );
     2135                add_post_meta( $posts[1], 'aaa_bar_aaa', 'abc' );
     2136                add_post_meta( $posts[2], 'aaa_foo_bbb', 'abc' );
     2137                add_post_meta( $posts[2], 'aaa_foo_aaa', 'abc' );
     2138
     2139                $q = new WP_Query(
     2140                        array(
     2141                                'meta_query' => array(
     2142                                        array(
     2143                                                'compare_key' => 'NOT REGEXP',
     2144                                                'key'         => 'AAA_foo_.*',
     2145                                        ),
     2146                                ),
     2147                                'fields'     => 'ids',
     2148                        )
     2149                );
     2150
     2151                $this->assertEqualSets( array( $posts[1] ), $q->posts );
     2152
     2153                $q = new WP_Query(
     2154                        array(
     2155                                'meta_query' => array(
     2156                                        array(
     2157                                                'compare_key' => 'NOT REGEXP',
     2158                                                'key'         => 'AAA_FOO_.*',
     2159                                                'type_key'    => 'BINARY',
     2160                                        ),
     2161                                ),
     2162                                'fields'     => 'ids',
     2163                        )
     2164                );
     2165
     2166                $this->assertEqualSets( array( $posts[1], $posts[2] ), $q->posts );
     2167        }
    19252168}