WordPress.org

Make WordPress Core

Ticket #36652: 36652.diff

File 36652.diff, 10.5 KB (added by boonebgorges, 5 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 ddafc61..5e08b00 100644
    class WP_Meta_Query { 
    282282        }
    283283
    284284        /**
     285         * Determines the 'type' (MySQL data type) for a clause.
     286         *
     287         * If a 'type' has been explicitly declared, that value is returned. If 'value' is
     288         * strictly numeric - integer, float, or an array that contains only integers and floats -
     289         * we assume that 'SIGNED` is attended. Otherwise, we return the default, which results
     290         * in `CAST()` being skipped (string values).
     291         *
     292         * @since 4.6.0
     293         * @access protected
     294         *
     295         * @param array $clause Meta query clause.
     296         * @return string
     297         */
     298        protected function get_type_for_clause( $clause ) {
     299                // If a 'type' has been provided, trust it.
     300                if ( isset( $clause['type'] ) ) {
     301                        return $clause['type'];
     302                }
     303
     304                if ( ! $this->clause_has_numeric_value( $clause ) ) {
     305                        return '';
     306                }
     307
     308                if ( is_array( $clause['value'] ) ) {
     309                        $signed_compares = array( 'BETWEEN', 'NOT BETWEEN' );
     310                } else {
     311                        $signed_compares = array( '=', '!=', '>', '>=', '<', '<=' );
     312                }
     313
     314                $type = '';
     315                if ( in_array( $clause['compare'], $signed_compares, true ) ) {
     316                        $type = 'SIGNED';
     317                }
     318
     319                return $type;
     320        }
     321
     322        /**
    285323         * Return the appropriate alias for the given meta type if applicable.
    286324         *
    287325         * @since 3.7.0
    class WP_Meta_Query { 
    306344        }
    307345
    308346        /**
     347         * Gets the type specifier (%s, %d, %f) for the 'value' of a meta query clause.
     348         *
     349         * Recursively determines the type specifier to be used by `$wpdb->prepare()` when
     350         * building MySQL queries from meta query clauses. When a value is numeric (integer
     351         * or float), '%d' or '%f' is used, as appropriate. '%s' is the fallback. When
     352         * 'value' is an array, this method returns an array of appropriate type specifiers.
     353         *
     354         * @since 4.6.0
     355         * @access protected
     356         *
     357         * @param array $clause Meta query clause.
     358         * @return string|array Returns '%s', '%d', '%f' as appropriate for single 'value', or
     359         *                      an array of these specifiers for an array 'value'.
     360         */
     361        protected function get_type_specifier_for_clause( $clause ) {
     362                if ( isset( $clause['value'] ) && is_array( $clause['value'] ) ) {
     363                        $type_specifier = array_fill( 0, count( $clause['value'] ), '%s' );
     364                } else {
     365                        $type_specifier = '%s';
     366                }
     367
     368                if ( 'SIGNED' !== $clause['cast'] ) {
     369                        return $type_specifier;
     370                }
     371
     372                if ( ! $this->clause_has_numeric_value( $clause ) ) {
     373                        return $type_specifier;
     374                }
     375
     376                if ( is_int( $clause['value'] ) ) {
     377                        $type_specifier = '%d';
     378                } elseif ( is_float( $clause['value'] ) ) {
     379                        $type_specifier = '%f';
     380                } elseif ( is_array( $clause ) ) {
     381                        $type_specifier = array();
     382                        foreach ( $clause['value'] as $key => $value ) {
     383                                $type_specifier[ $key ] = $this->get_type_specifier_for_clause( array( 'value' => $value, 'cast' => $clause['cast'] ) );
     384                        }
     385
     386                        $type_specifier = array_values( $type_specifier );
     387                }
     388
     389                return $type_specifier;
     390        }
     391
     392        /**
    309393         * Generates SQL clauses to be appended to a main query.
    310394         *
    311395         * @since 3.2.0
    class WP_Meta_Query { 
    548632                        $sql_chunks['join'][] = $join;
    549633                }
    550634
    551                 // Save the alias to this clause, for future siblings to find.
    552                 $clause['alias'] = $alias;
    553 
    554635                // Determine the data type.
    555                 $_meta_type = isset( $clause['type'] ) ? $clause['type'] : '';
     636                $_meta_type = $this->get_type_for_clause( $clause );
    556637                $meta_type  = $this->get_cast_for_type( $_meta_type );
    557638                $clause['cast'] = $meta_type;
    558639
     640                // If 'value' is a comma-separated list, convert to array so that the proper type specifiers are set.
     641                if ( isset( $clause['value'] ) && is_string( $clause['value'] ) && in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
     642                        $clause['value'] = preg_split( '/[,\s]+/', $clause['value'] );
     643                }
     644
     645                $type_specifier = $this->get_type_specifier_for_clause( $clause );
     646
     647                // Save the alias to this clause, for future siblings to find.
     648                $clause['alias'] = $alias;
     649
    559650                // Fallback for clause keys is the table alias. Key must be a string.
    560651                if ( is_int( $clause_key ) || ! $clause_key ) {
    561652                        $clause_key = $clause['alias'];
    class WP_Meta_Query { 
    585676
    586677                // meta_value.
    587678                if ( array_key_exists( 'value', $clause ) ) {
    588                         $meta_value = $clause['value'];
    589 
    590                         if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
    591                                 if ( ! is_array( $meta_value ) ) {
    592                                         $meta_value = preg_split( '/[,\s]+/', $meta_value );
    593                                 }
     679                        if ( is_array( $clause['value'] ) ) {
     680                                $meta_value = array_map( 'trim', $clause['value'] );
    594681                        } else {
    595                                 $meta_value = trim( $meta_value );
     682                                $meta_value = trim( $clause['value'] );
    596683                        }
    597684
    598685                        switch ( $meta_compare ) {
    599686                                case 'IN' :
    600687                                case 'NOT IN' :
    601                                         $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
     688                                        $meta_compare_string = '(' . implode( ',', $type_specifier ) . ')';
    602689                                        $where = $wpdb->prepare( $meta_compare_string, $meta_value );
    603690                                        break;
    604691
    605692                                case 'BETWEEN' :
    606693                                case 'NOT BETWEEN' :
    607694                                        $meta_value = array_slice( $meta_value, 0, 2 );
    608                                         $where = $wpdb->prepare( '%s AND %s', $meta_value );
     695                                        $where = $wpdb->prepare( "{$type_specifier[0]} AND {$type_specifier[1]}", $meta_value );
    609696                                        break;
    610697
    611698                                case 'LIKE' :
    class WP_Meta_Query { 
    626713                                        break;
    627714
    628715                                default :
    629                                         $where = $wpdb->prepare( '%s', $meta_value );
     716                                        $where = $wpdb->prepare( $type_specifier, $meta_value );
    630717                                        break;
    631718
    632719                        }
    633720
    634                         if ( $where ) {
     721                        if ( strlen( $where ) ) {
    635722                                if ( 'CHAR' === $meta_type ) {
    636723                                        $sql_chunks['where'][] = "$alias.meta_value {$meta_compare} {$where}";
    637724                                } else {
    class WP_Meta_Query { 
    747834        public function has_or_relation() {
    748835                return $this->has_or_relation;
    749836        }
     837
     838        /**
     839         * Determines whether a clause's 'value' is numeric.
     840         *
     841         * Returns true when 'value' is a float, an integer, or an array containing only
     842         * floats and integers.
     843         *
     844         * @since 4.6.0
     845         * @access protected
     846         *
     847         * @param array $clause Meta query clause. Passed by reference, so that the
     848         *                      'has_numeric_value' flag can be set, to avoid the need to
     849         *                      parse a given clause more than once.
     850         * @return bool
     851         */
     852        protected function clause_has_numeric_value( &$clause ) {
     853                if ( isset( $clause['has_numeric_value'] ) ) {
     854                        return $clause['has_numeric_value'];
     855                }
     856
     857                if ( ! isset( $clause['value'] ) ) {
     858                        return false;
     859                }
     860
     861                if ( is_array( $clause['value'] ) ) {
     862                        $has_numeric_value = true;
     863                        foreach ( $clause['value'] as $value ) {
     864                                if ( ! $has_numeric_value ) {
     865                                        break;
     866                                }
     867
     868                                $_clause = array( 'value' => $value );
     869                                $has_numeric_value = $this->clause_has_numeric_value( $_clause );
     870                        }
     871                } else {
     872                        $has_numeric_value = is_int( $clause['value'] ) || is_float( $clause['value'] );
     873                }
     874
     875                $clause['has_numeric_value'] = $has_numeric_value;
     876
     877                return $has_numeric_value;
     878        }
    750879}
  • tests/phpunit/tests/meta/query.php

    diff --git tests/phpunit/tests/meta/query.php tests/phpunit/tests/meta/query.php
    index 6050676..28221be 100644
    class Tests_Meta_Query extends WP_UnitTestCase { 
    884884
    885885                $this->assertTrue( $q->has_or_relation() );
    886886        }
     887
     888        /**
     889         * @ticket 36652
     890         * @dataProvider data_numeric_type_should_be_assumed_for_numeric_meta_value
     891         */
     892        public function test_numeric_type_should_be_assumed_for_numeric_meta_value( $value, $compare, $expected ) {
     893                global $wpdb;
     894
     895                $q = new WP_Meta_Query( array(
     896                        array(
     897                                'key' => 'foo',
     898                                'value' => $value,
     899                                'compare' => $compare,
     900                        ),
     901                ) );
     902
     903                $sql = $q->get_sql( 'post', $wpdb->posts, 'ID' );
     904
     905                $this->assertContains( $expected, $sql['where'] );
     906        }
     907
     908        public function data_numeric_type_should_be_assumed_for_numeric_meta_value() {
     909                global $wpdb;
     910
     911                return array(
     912                        array( 3,   '=',  "CAST($wpdb->postmeta.meta_value AS SIGNED) = 3" ),
     913                        array( 3.5, '=',  "CAST($wpdb->postmeta.meta_value AS SIGNED) = 3.5" ),
     914                        array( 3,   '!=', "CAST($wpdb->postmeta.meta_value AS SIGNED) != 3" ),
     915                        array( 3.5, '!=', "CAST($wpdb->postmeta.meta_value AS SIGNED) != 3.5" ),
     916                        array( 3,   '>',  "CAST($wpdb->postmeta.meta_value AS SIGNED) > 3" ),
     917                        array( 3.5, '>',  "CAST($wpdb->postmeta.meta_value AS SIGNED) > 3.5" ),
     918                        array( 3,   '>=', "CAST($wpdb->postmeta.meta_value AS SIGNED) >= 3" ),
     919                        array( 3.5, '>=', "CAST($wpdb->postmeta.meta_value AS SIGNED) >= 3.5" ),
     920                        array( 3,   '<=', "CAST($wpdb->postmeta.meta_value AS SIGNED) <= 3" ),
     921                        array( 3.5, '<=', "CAST($wpdb->postmeta.meta_value AS SIGNED) <= 3.5" ),
     922                        array( 3,   '<',  "CAST($wpdb->postmeta.meta_value AS SIGNED) < 3" ),
     923                        array( 3.5, '<',  "CAST($wpdb->postmeta.meta_value AS SIGNED) < 3.5" ),
     924
     925                        array( array( 1, 1.5 ), 'BETWEEN', "CAST($wpdb->postmeta.meta_value AS SIGNED) BETWEEN 1 AND 1.5" ),
     926                        array( array( 1, 1.5 ), 'NOT BETWEEN', "CAST($wpdb->postmeta.meta_value AS SIGNED) NOT BETWEEN 1 AND 1.5" ),
     927
     928                        // Not numeric.
     929                        array( '3', '=',  "$wpdb->postmeta.meta_value = '3'" ),
     930                        array( '3', '!=', "$wpdb->postmeta.meta_value != '3'" ),
     931                        array( '3', '>',  "$wpdb->postmeta.meta_value > '3'" ),
     932                        array( '3', '>=', "$wpdb->postmeta.meta_value >= '3'" ),
     933                        array( '3', '<=', "$wpdb->postmeta.meta_value <= '3'" ),
     934                        array( '3', '<',  "$wpdb->postmeta.meta_value < '3'" ),
     935
     936                        // Array not fully numeric.
     937                        array( array( 1, '2' ), 'BETWEEN', "$wpdb->postmeta.meta_value BETWEEN '1' AND '2'" ),
     938                        array( array( 1, '2' ), 'NOT BETWEEN', "$wpdb->postmeta.meta_value NOT BETWEEN '1' AND '2'" ),
     939
     940                        // 'compare' doesn't suggest numeric.
     941                        array( 3, 'LIKE',       "$wpdb->postmeta.meta_value LIKE '%3%'" ),
     942                        array( 3, 'NOT LIKE',   "$wpdb->postmeta.meta_value NOT LIKE '%3%'" ),
     943                        array( 3, 'REGEXP',     "$wpdb->postmeta.meta_value REGEXP '3'" ),
     944                        array( 3, 'NOT REGEXP', "$wpdb->postmeta.meta_value NOT REGEXP '3'" ),
     945                        array( 3, 'RLIKE',      "$wpdb->postmeta.meta_value RLIKE '3'" ),
     946
     947                        array( 3.5, 'LIKE',       "$wpdb->postmeta.meta_value LIKE '%3.5%'" ),
     948                        array( 3.5, 'NOT LIKE',   "$wpdb->postmeta.meta_value NOT LIKE '%3.5%'" ),
     949                        array( 3.5, 'REGEXP',     "$wpdb->postmeta.meta_value REGEXP '3.5'" ),
     950                        array( 3.5, 'NOT REGEXP', "$wpdb->postmeta.meta_value NOT REGEXP '3.5'" ),
     951                        array( 3.5, 'RLIKE',      "$wpdb->postmeta.meta_value RLIKE '3.5'" ),
     952
     953
     954                        array( array( 1, 1.5 ), 'IN',     "$wpdb->postmeta.meta_value IN ('1','1.5')" ),
     955                        array( array( 1, 1.5 ), 'NOT IN', "$wpdb->postmeta.meta_value NOT IN ('1','1.5')" ),
     956                );
     957        }
    887958}