Merge branch 'new-rest-api-product-filters'
This commit is contained in:
commit
307601c4c0
|
@ -319,6 +319,24 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta query.
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @param array $args Query args.
|
||||
* @param array $meta_query Meta query.
|
||||
* @return array
|
||||
*/
|
||||
protected function add_meta_query( $args, $meta_query ) {
|
||||
if ( ! empty( $args['meta_query'] ) ) {
|
||||
$args['meta_query'] = array();
|
||||
}
|
||||
|
||||
$args['meta_query'][] = $meta_query;
|
||||
|
||||
return $args['meta_query'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the batch schema, conforming to JSON Schema.
|
||||
*
|
||||
|
|
|
@ -186,15 +186,39 @@ class WC_REST_Products_Controller extends WC_REST_Posts_Controller {
|
|||
|
||||
// Filter by sku.
|
||||
if ( ! empty( $request['sku'] ) ) {
|
||||
if ( ! empty( $args['meta_query'] ) ) {
|
||||
$args['meta_query'] = array();
|
||||
}
|
||||
$args['meta_query'] = $this->add_meta_query( $args, array(
|
||||
'key' => '_sku',
|
||||
'value' => $request['sku'],
|
||||
) );
|
||||
}
|
||||
|
||||
$args['meta_query'][] = array(
|
||||
'key' => '_sku',
|
||||
'value' => $request['sku'],
|
||||
'compare' => '=',
|
||||
);
|
||||
// Filter featured.
|
||||
if ( is_bool( $request['featured'] ) ) {
|
||||
$args['meta_query'] = $this->add_meta_query( $args, array(
|
||||
'key' => '_featured',
|
||||
'value' => true === $request['featured'] ? 'yes' : 'no',
|
||||
) );
|
||||
}
|
||||
|
||||
// Filter by tax class.
|
||||
if ( ! empty( $request['tax_class'] ) ) {
|
||||
$args['meta_query'] = $this->add_meta_query( $args, array(
|
||||
'key' => '_tax_class',
|
||||
'value' => 'standard' !== $request['tax_class'] ? $request['tax_class'] : '',
|
||||
) );
|
||||
}
|
||||
|
||||
// Price filter.
|
||||
if ( ! empty( $request['min_price'] ) || ! empty( $request['max_price'] ) ) {
|
||||
$args['meta_query'] = $this->add_meta_query( $args, wc_get_min_max_price_meta_query( $request ) );
|
||||
}
|
||||
|
||||
// Filter product in stock or out of stock.
|
||||
if ( is_bool( $request['in_stock'] ) ) {
|
||||
$args['meta_query'] = $this->add_meta_query( $args, array(
|
||||
'key' => '_stock_status',
|
||||
'value' => true === $request['in_stock'] ? 'instock' : 'outofstock',
|
||||
) );
|
||||
}
|
||||
|
||||
// Apply all WP_Query filters again.
|
||||
|
@ -2689,20 +2713,32 @@ class WC_REST_Products_Controller extends WC_REST_Posts_Controller {
|
|||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['sku'] = array(
|
||||
'description' => __( 'Limit result set to products with a specific SKU.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['featured'] = array(
|
||||
'description' => __( 'Limit result set to featured products.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wc_string_to_bool',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['category'] = array(
|
||||
'description' => __( 'Limit result set to products assigned a specific category.', 'woocommerce' ),
|
||||
'description' => __( 'Limit result set to products assigned a specific category ID.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['tag'] = array(
|
||||
'description' => __( 'Limit result set to products assigned a specific tag.', 'woocommerce' ),
|
||||
'description' => __( 'Limit result set to products assigned a specific tag ID.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['shipping_class'] = array(
|
||||
'description' => __( 'Limit result set to products assigned a specific shipping class.', 'woocommerce' ),
|
||||
'description' => __( 'Limit result set to products assigned a specific shipping class ID.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
|
@ -2714,13 +2750,36 @@ class WC_REST_Products_Controller extends WC_REST_Posts_Controller {
|
|||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['attribute_term'] = array(
|
||||
'description' => __( 'Limit result set to products with a specific attribute term (required an assigned attribute).', 'woocommerce' ),
|
||||
'description' => __( 'Limit result set to products with a specific attribute term ID (required an assigned attribute).', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['sku'] = array(
|
||||
'description' => __( 'Limit result set to products with a specific SKU.', 'woocommerce' ),
|
||||
|
||||
if ( wc_tax_enabled() ) {
|
||||
$params['tax_class'] = array(
|
||||
'description' => __( 'Limit result set to products with a specific tax class.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => array_map( 'sanitize_title', array_merge( array( 'standard' ), WC_Tax::get_tax_classes() ) ),
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
}
|
||||
|
||||
$params['in_stock'] = array(
|
||||
'description' => __( 'Limit result set to products in stock or out of stock.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wc_string_to_bool',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['min_price'] = array(
|
||||
'description' => __( 'Limit result set to products based on a minimum price.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
$params['max_price'] = array(
|
||||
'description' => __( 'Limit result set to products based on a maximum price.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
|
|
|
@ -545,35 +545,12 @@ class WC_Query {
|
|||
*/
|
||||
private function price_filter_meta_query() {
|
||||
if ( isset( $_GET['max_price'] ) || isset( $_GET['min_price'] ) ) {
|
||||
$min = isset( $_GET['min_price'] ) ? floatval( $_GET['min_price'] ) : 0;
|
||||
$max = isset( $_GET['max_price'] ) ? floatval( $_GET['max_price'] ) : 9999999999;
|
||||
$meta_query = wc_get_min_max_price_meta_query( $_GET );
|
||||
$meta_query['price_filter'] = true;
|
||||
|
||||
/**
|
||||
* Adjust if the store taxes are not displayed how they are stored.
|
||||
* Max is left alone because the filter was already increased.
|
||||
* Kicks in when prices excluding tax are displayed including tax.
|
||||
*/
|
||||
if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) {
|
||||
$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() );
|
||||
$class_min = $min;
|
||||
|
||||
foreach ( $tax_classes as $tax_class ) {
|
||||
if ( $tax_rates = WC_Tax::get_rates( $tax_class ) ) {
|
||||
$class_min = $min - WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $min, $tax_rates ) );
|
||||
}
|
||||
}
|
||||
|
||||
$min = $class_min;
|
||||
}
|
||||
|
||||
return array(
|
||||
'key' => '_price',
|
||||
'value' => array( $min, $max ),
|
||||
'compare' => 'BETWEEN',
|
||||
'type' => 'DECIMAL',
|
||||
'price_filter' => true,
|
||||
);
|
||||
return $meta_query;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* @version 2.0-beta13.1
|
||||
* @version 2.0-beta15
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
@ -174,6 +174,10 @@ if ( ! function_exists( 'rest_validate_request_arg' ) ) {
|
|||
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s', 'woocommerce' ), $param, 'integer' ) );
|
||||
}
|
||||
|
||||
if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
|
||||
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s', 'woocommerce' ), $value, 'boolean' ) );
|
||||
}
|
||||
|
||||
if ( 'string' === $args['type'] && ! is_string( $value ) ) {
|
||||
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s', 'woocommerce' ), $param, 'string' ) );
|
||||
}
|
||||
|
@ -191,6 +195,11 @@ if ( ! function_exists( 'rest_validate_request_arg' ) ) {
|
|||
return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.', 'woocommerce' ) );
|
||||
}
|
||||
break;
|
||||
case 'ipv4' :
|
||||
if ( ! rest_is_ip_address( $value ) ) {
|
||||
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.', 'woocommerce' ), $value ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,6 +262,10 @@ if ( ! function_exists( 'rest_sanitize_request_arg' ) ) {
|
|||
return (int) $value;
|
||||
}
|
||||
|
||||
if ( 'boolean' === $args['type'] ) {
|
||||
return rest_sanitize_boolean( $value );
|
||||
}
|
||||
|
||||
if ( isset( $args['format'] ) ) {
|
||||
switch ( $args['format'] ) {
|
||||
case 'date-time' :
|
||||
|
@ -266,9 +279,113 @@ if ( ! function_exists( 'rest_sanitize_request_arg' ) ) {
|
|||
|
||||
case 'uri' :
|
||||
return esc_url_raw( $value );
|
||||
|
||||
case 'ipv4' :
|
||||
return sanitize_text_field( $value );
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( ! function_exists( 'rest_parse_request_arg' ) ) {
|
||||
/**
|
||||
* Parse a request argument based on details registered to the route.
|
||||
*
|
||||
* Runs a validation check and sanitizes the value, primarily to be used via
|
||||
* the `sanitize_callback` arguments in the endpoint args registration.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param WP_REST_Request $request
|
||||
* @param string $param
|
||||
* @return mixed
|
||||
*/
|
||||
function rest_parse_request_arg( $value, $request, $param ) {
|
||||
|
||||
$is_valid = rest_validate_request_arg( $value, $request, $param );
|
||||
|
||||
if ( is_wp_error( $is_valid ) ) {
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
$value = rest_sanitize_request_arg( $value, $request, $param );
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'rest_is_ip_address' ) ) {
|
||||
/**
|
||||
* Determines if a IPv4 address is valid.
|
||||
*
|
||||
* Does not handle IPv6 addresses.
|
||||
*
|
||||
* @param string $ipv4 IP 32-bit address.
|
||||
* @return string|false The valid IPv4 address, otherwise false.
|
||||
*/
|
||||
function rest_is_ip_address( $ipv4 ) {
|
||||
$pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
|
||||
|
||||
if ( ! preg_match( $pattern, $ipv4 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $ipv4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a boolean-like value into the proper boolean value.
|
||||
*
|
||||
* @param bool|string|int $value The value being evaluated.
|
||||
* @return boolean Returns the proper associated boolean value.
|
||||
*/
|
||||
if ( ! function_exists( 'rest_sanitize_boolean' ) ) {
|
||||
function rest_sanitize_boolean( $value ) {
|
||||
// String values are translated to `true`; make sure 'false' is false.
|
||||
if ( is_string( $value ) ) {
|
||||
$value = strtolower( $value );
|
||||
if ( in_array( $value, array( 'false', '0' ), true ) ) {
|
||||
$value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Everything else will map nicely to boolean.
|
||||
return (boolean) $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given value is boolean-like.
|
||||
*
|
||||
* @param bool|string $maybe_bool The value being evaluated.
|
||||
* @return boolean True if a boolean, otherwise false.
|
||||
*/
|
||||
if ( ! function_exists( 'rest_is_boolean' ) ) {
|
||||
function rest_is_boolean( $maybe_bool ) {
|
||||
if ( is_bool( $maybe_bool ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( is_string( $maybe_bool ) ) {
|
||||
$maybe_bool = strtolower( $maybe_bool );
|
||||
|
||||
$valid_boolean_values = array(
|
||||
'false',
|
||||
'true',
|
||||
'0',
|
||||
'1',
|
||||
);
|
||||
|
||||
return in_array( $maybe_bool, $valid_boolean_values, true );
|
||||
}
|
||||
|
||||
if ( is_int( $maybe_bool ) ) {
|
||||
return in_array( $maybe_bool, array( 0, 1 ), true );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -751,3 +751,40 @@ function wc_get_product_visibility_options() {
|
|||
'hidden' => __( 'Hidden', 'woocommerce' ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get min/max price meta query args.
|
||||
*
|
||||
* @since 2.7.0
|
||||
* @param array $args Min price and max price arguments.
|
||||
* @return array
|
||||
*/
|
||||
function wc_get_min_max_price_meta_query( $args ) {
|
||||
$min = isset( $args['min_price'] ) ? floatval( $args['min_price'] ) : 0;
|
||||
$max = isset( $args['max_price'] ) ? floatval( $args['max_price'] ) : 9999999999;
|
||||
|
||||
/**
|
||||
* Adjust if the store taxes are not displayed how they are stored.
|
||||
* Max is left alone because the filter was already increased.
|
||||
* Kicks in when prices excluding tax are displayed including tax.
|
||||
*/
|
||||
if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) {
|
||||
$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() );
|
||||
$class_min = $min;
|
||||
|
||||
foreach ( $tax_classes as $tax_class ) {
|
||||
if ( $tax_rates = WC_Tax::get_rates( $tax_class ) ) {
|
||||
$class_min = $min - WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $min, $tax_rates ) );
|
||||
}
|
||||
}
|
||||
|
||||
$min = $class_min;
|
||||
}
|
||||
|
||||
return array(
|
||||
'key' => '_price',
|
||||
'value' => array( $min, $max ),
|
||||
'compare' => 'BETWEEN',
|
||||
'type' => 'DECIMAL',
|
||||
);
|
||||
}
|
||||
|
|
|
@ -183,4 +183,20 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case {
|
|||
// Delete Product
|
||||
WC_Helper_Product::delete_product( $product->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test wc_get_min_max_price_meta_query()
|
||||
*
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public function test_wc_get_min_max_price_meta_query() {
|
||||
$meta_query = wc_get_min_max_price_meta_query( array( 'min_price' => 10, 'max_price' => 100 ) );
|
||||
|
||||
$this->assertEquals( array(
|
||||
'key' => '_price',
|
||||
'value' => array( 10, 100 ),
|
||||
'compare' => 'BETWEEN',
|
||||
'type' => 'DECIMAL',
|
||||
), $meta_query );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue