diff --git a/helpers/QueryHelper.php b/helpers/QueryHelper.php index 79de32bc0b..69fe6eed84 100644 --- a/helpers/QueryHelper.php +++ b/helpers/QueryHelper.php @@ -252,6 +252,7 @@ public static function insert_multiple_rows( $table, $request, $return_ids = fal * Otherwise the clause would be `WHERE column_name = 'value'` * * @since 3.0.0 + * @since 3.9.7 added prepared statement for value. * * @param array $where The where clause array. e.g. array( 'id', 'IN', array(1, 2, 3) ) or array( 'id', '=', 1 ). * @@ -261,8 +262,16 @@ public static function make_clause( array $where ) { list ( $field, $operator, $value ) = $where; $upper_operator = strtoupper( $operator ); + if ( in_array( $upper_operator, array( 'IN', 'NOT IN' ), true ) ) { $value = '(' . self::prepare_in_clause( $value ) . ')'; + } elseif ( in_array( $upper_operator, array( 'BETWEEN', 'NOT BETWEEN' ), true ) ) { + $value = array_map( fn( $val ) => self::prepare_value( $val ), $value ); + $value = implode( ' AND ', $value ); + } elseif ( strtoupper( $value ) === 'NULL' ) { + $value = 'NULL'; + } else { + $value = self::prepare_value( $value ); } return "{$field} {$upper_operator} {$value}"; @@ -346,15 +355,13 @@ public static function prepare_where_clause( array $where ) { case 'BETWEEN': case 'NOT BETWEEN': if ( is_array( $val ) && count( $val ) === 2 ) { - $val1 = is_numeric( $val[0] ) ? $val[0] : "'" . $val[0] . "'"; - $val2 = is_numeric( $val[1] ) ? $val[1] : "'" . $val[1] . "'"; - $clause = array( $field, $operator, "{$val1} AND {$val2}" ); + $clause = array( $field, $operator, $val ); } break; case 'IS': case 'IS NOT': - $val = strtoupper( $val ) === 'NULL' ? 'NULL' : "'" . $val . "'"; + $val = strtoupper( $val ) === 'NULL' ? 'NULL' : $val; $clause = array( $field, $operator, $val ); break; case 'RAW': @@ -365,16 +372,14 @@ public static function prepare_where_clause( array $where ) { $clause = $final_query; break; default: // =, !=, <, >, <=, >=, LIKE, NOT LIKE, <> - $val = is_numeric( $val ) ? $val : "'" . $val . "'"; $clause = array( $field, $operator, $val ); break; } } elseif ( is_array( $value ) ) { $clause = array( $field, 'IN', $value ); } elseif ( 'null' === strtolower( $value ) ) { - $clause = array( $field, 'IS', 'NULL' ); + $clause = array( $field, 'IS', 'NULL' ); } else { - $value = is_numeric( $value ) ? $value : "'" . $value . "'"; $clause = array( $field, '=', $value ); } @@ -910,32 +915,41 @@ public static function prepare_set_clause( array $data ) { return rtrim( $set, ',' ); } + /** + * Prepare value before using in query. + * + * @since 3.9.7 + * + * @param string|int|float $value the value to prepare. + * + * @return mixed + */ + public static function prepare_value( $value ) { + global $wpdb; + $escaped_value = null; + if ( is_int( $value ) ) { + $escaped_value = $wpdb->prepare( '%d', $value ); + } elseif ( is_float( $value ) ) { + list( $whole, $decimal ) = explode( '.', $value ); + $expression = '%.'. strlen( $decimal ) . 'f'; + $escaped_value = $wpdb->prepare( $expression, $value ); + } else { + $escaped_value = $wpdb->prepare( '%s', $value ); + } + return $escaped_value; + } + /** * Make sanitized SQL IN clause value from an array * + * @since 2.1.1 + * * @param array $arr a sequential array. + * * @return string - * @since 2.1.1 */ public static function prepare_in_clause( array $arr ) { - $escaped = array_map( - function( $value ) { - global $wpdb; - $escaped_value = null; - if ( is_int( $value ) ) { - $escaped_value = $wpdb->prepare( '%d', $value ); - } else if( is_float( $value ) ) { - list( $whole, $decimal ) = explode( '.', $value ); - $expression = '%.'. strlen( $decimal ) . 'f'; - $escaped_value = $wpdb->prepare( $expression, $value ); - } else { - $escaped_value = $wpdb->prepare( '%s', $value ); - } - return $escaped_value; - }, - $arr - ); - + $escaped = array_map( fn( $value ) => self::prepare_value( $value ), $arr ); return implode( ',', $escaped ); } diff --git a/models/CouponModel.php b/models/CouponModel.php index 2445fe705e..61a7275462 100644 --- a/models/CouponModel.php +++ b/models/CouponModel.php @@ -810,7 +810,7 @@ public function get_coupon_details_for_checkout( $coupon_code = '' ) { } else { $coupon = $this->get_coupon( array( - 'coupon_code' => esc_sql( $coupon_code ), + 'coupon_code' => $coupon_code, 'coupon_status' => self::STATUS_ACTIVE, ) ); diff --git a/tests/phpunit/QueryHelperTest.php b/tests/phpunit/QueryHelperTest.php index 59833d87b8..bcb608f8cb 100644 --- a/tests/phpunit/QueryHelperTest.php +++ b/tests/phpunit/QueryHelperTest.php @@ -68,6 +68,53 @@ public function test_prepare_set_clause() { * * @return void */ + public function test_prepare_where_clause() { + + $test1 = array( + 'age' => 18, + 'height' => array( '>=', '5feet7inch' ), + 'hobbies' => array( 'coding', 'biking', 'swimming' ), + ); + $expect1 = "age = 18 AND height >= '5feet7inch' AND hobbies IN ('coding','biking','swimming')"; + $actual1 = QueryHelper::prepare_where_clause( $test1 ); + + $test2 = array( + 'salary' => array( 'BETWEEN', array( 10, 20 ) ), + 'name' => array( 'NOT BETWEEN', array( 'b', 'c' ) ), + ); + + $expect2 = "salary BETWEEN 10 AND 20 AND name NOT BETWEEN 'b' AND 'c'"; + $actual2 = QueryHelper::prepare_where_clause( $test2 ); + + $test3 = array( + 'name' => array( 'LIKE', 'test' ), + 'age' => array( 'NOT IN', array( 18, 19, 20 ) ), + ); + + $expect3 = "name LIKE 'test' AND age NOT IN (18,19,20)"; + $actual3 = QueryHelper::prepare_where_clause( $test3 ); + + $test4 = array( + 'name' => 'NULL', + 'age' => array( 'IS NOT', 'NULL' ), + ); + + $expect4 = 'name IS NULL AND age IS NOT NULL'; + $actual4 = QueryHelper::prepare_where_clause( $test4 ); + + $this->assertEquals( $expect1, trim( $actual1 ) ); + $this->assertEquals( $expect2, trim( $actual2 ) ); + $this->assertEquals( $expect3, trim( $actual3 ) ); + $this->assertEquals( $expect4, trim( $actual4 ) ); + } + + /** + * Test QueryHelper Raw query. + * + * @since 3.6.0 + * + * @return void + */ public function test_prepare_raw_query() { $case_1 = array( "username = '%s'" => array( @@ -88,7 +135,7 @@ public function test_prepare_raw_query() { ); $case_3 = array( - 'id' => array( + 'id' => array( 'BETWEEN', array( 10, 20 ), ),