From 9dcf4355697a8435cade78dfa7e246ec3467c9e2 Mon Sep 17 00:00:00 2001 From: Seghir Nadir Date: Fri, 17 Feb 2023 14:46:10 +0100 Subject: [PATCH] Unset default customer state if it doesn't match country (https://github.com/woocommerce/woocommerce-blocks/pull/8460) * Unset default state * add controller for customers * rename validation file * explain fix inline * address feedback * revert back state logic * Update src/StoreApi/Utilities/ValidationUtils.php Co-authored-by: Mike Jolley --------- Co-authored-by: Mike Jolley --- .../StoreApi/Routes/V1/CartUpdateCustomer.php | 18 ++++- .../Schemas/V1/AbstractAddressSchema.php | 67 +++---------------- .../Schemas/V1/BillingAddressSchema.php | 4 +- .../Schemas/V1/ShippingAddressSchema.php | 4 +- .../StoreApi/Utilities/ValidationUtils.php | 61 +++++++++++++++++ 5 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 plugins/woocommerce-blocks/src/StoreApi/Utilities/ValidationUtils.php diff --git a/plugins/woocommerce-blocks/src/StoreApi/Routes/V1/CartUpdateCustomer.php b/plugins/woocommerce-blocks/src/StoreApi/Routes/V1/CartUpdateCustomer.php index 330c9c1c573..264e3da6cc2 100644 --- a/plugins/woocommerce-blocks/src/StoreApi/Routes/V1/CartUpdateCustomer.php +++ b/plugins/woocommerce-blocks/src/StoreApi/Routes/V1/CartUpdateCustomer.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait; +use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils; /** * CartUpdateCustomer class. @@ -215,6 +216,19 @@ class CartUpdateCustomer extends AbstractCartRoute { * @return array */ protected function get_customer_billing_address( \WC_Customer $customer ) { + $validation_util = new ValidationUtils(); + $billing_country = $customer->get_billing_country(); + $billing_state = $customer->get_billing_state(); + + /** + * There's a bug in WooCommerce core in which not having a state ("") would result in us validating against the store's state. + * This resets the state to an empty string if it doesn't match the country. + * + * @todo Removing this handling once we fix the issue with the state value always being the store one. + */ + if ( ! $validation_util->validate_state( $billing_state, $billing_country ) ) { + $billing_state = ''; + } return [ 'first_name' => $customer->get_billing_first_name(), 'last_name' => $customer->get_billing_last_name(), @@ -222,9 +236,9 @@ class CartUpdateCustomer extends AbstractCartRoute { 'address_1' => $customer->get_billing_address_1(), 'address_2' => $customer->get_billing_address_2(), 'city' => $customer->get_billing_city(), - 'state' => $customer->get_billing_state(), + 'state' => $billing_state, 'postcode' => $customer->get_billing_postcode(), - 'country' => $customer->get_billing_country(), + 'country' => $billing_country, 'phone' => $customer->get_billing_phone(), 'email' => $customer->get_billing_email(), ]; diff --git a/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/AbstractAddressSchema.php b/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/AbstractAddressSchema.php index cae085878d1..3d1e313b4a8 100644 --- a/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/AbstractAddressSchema.php +++ b/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/AbstractAddressSchema.php @@ -1,6 +1,8 @@ get_properties() ), '' ), (array) $address ); $address['country'] = wc_strtoupper( wc_clean( wp_unslash( $address['country'] ) ) ); $address['first_name'] = wc_clean( wp_unslash( $address['first_name'] ) ); @@ -95,64 +99,12 @@ abstract class AbstractAddressSchema extends AbstractSchema { $address['address_1'] = wc_clean( wp_unslash( $address['address_1'] ) ); $address['address_2'] = wc_clean( wp_unslash( $address['address_2'] ) ); $address['city'] = wc_clean( wp_unslash( $address['city'] ) ); - $address['state'] = $this->format_state( wc_clean( wp_unslash( $address['state'] ) ), $address['country'] ); + $address['state'] = $validation_util->format_state( wc_clean( wp_unslash( $address['state'] ) ), $address['country'] ); $address['postcode'] = $address['postcode'] ? wc_format_postcode( wc_clean( wp_unslash( $address['postcode'] ) ), $address['country'] ) : ''; $address['phone'] = wc_clean( wp_unslash( $address['phone'] ) ); return $address; } - /** - * Get list of states for a country. - * - * @param string $country Country code. - * @return array Array of state names indexed by state keys. - */ - protected function get_states_for_country( $country ) { - return $country ? array_filter( (array) \wc()->countries->get_states( $country ) ) : []; - } - - /** - * Validate provided state against a countries list of defined states. - * - * If there are no defined states for a country, any given state is valid. - * - * @param string $state State name or code (sanitized). - * @param string $country Country code. - * @return boolean Valid or not valid. - */ - protected function validate_state( $state, $country ) { - $states = $this->get_states_for_country( $country ); - - if ( count( $states ) && ! in_array( \wc_strtoupper( $state ), array_map( '\wc_strtoupper', array_keys( $states ) ), true ) ) { - return false; - } - - return true; - } - - /** - * Format a state based on the country. If country has defined states, will return a valid upper case state code. - * - * @param string $state State name or code (sanitized). - * @param string $country Country code. - * @return string - */ - protected function format_state( $state, $country ) { - $states = $this->get_states_for_country( $country ); - - if ( count( $states ) ) { - $state = \wc_strtoupper( $state ); - $state_values = array_map( 'wc_strtoupper', array_flip( array_map( '\wc_strtoupper', $states ) ) ); - - if ( isset( $state_values[ $state ] ) ) { - // Convert to state code if a state name was provided. - return $state_values[ $state ]; - } - } - - return $state; - } - /** * Validate the given address object. * @@ -164,8 +116,9 @@ abstract class AbstractAddressSchema extends AbstractSchema { * @return true|\WP_Error */ public function validate_callback( $address, $request, $param ) { - $errors = new \WP_Error(); - $address = $this->sanitize_callback( $address, $request, $param ); + $errors = new \WP_Error(); + $address = $this->sanitize_callback( $address, $request, $param ); + $validation_util = new ValidationUtils(); if ( ! empty( $address['country'] ) && ! in_array( $address['country'], array_keys( wc()->countries->get_countries() ), true ) ) { $errors->add( @@ -179,14 +132,14 @@ abstract class AbstractAddressSchema extends AbstractSchema { return $errors; } - if ( ! empty( $address['state'] ) && ! $this->validate_state( $address['state'], $address['country'] ) ) { + if ( ! empty( $address['state'] ) && ! $validation_util->validate_state( $address['state'], $address['country'] ) ) { $errors->add( 'invalid_state', sprintf( /* translators: %1$s given state, %2$s valid states */ __( 'The provided state (%1$s) is not valid. Must be one of: %2$s', 'woo-gutenberg-products-block' ), esc_html( $address['state'] ), - implode( ', ', array_keys( $this->get_states_for_country( $address['country'] ) ) ) + implode( ', ', array_keys( $validation_util->get_states_for_country( $address['country'] ) ) ) ) ); } diff --git a/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/BillingAddressSchema.php b/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/BillingAddressSchema.php index ee750f0c9e5..0ae1ca68e56 100644 --- a/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/BillingAddressSchema.php +++ b/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/BillingAddressSchema.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; +use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils; /** * BillingAddressSchema class. @@ -89,11 +90,12 @@ class BillingAddressSchema extends AbstractAddressSchema { * @return stdClass */ public function get_item_response( $address ) { + $validation_util = new ValidationUtils(); if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) { $billing_country = $address->get_billing_country(); $billing_state = $address->get_billing_state(); - if ( ! $this->validate_state( $billing_state, $billing_country ) ) { + if ( ! $validation_util->validate_state( $billing_state, $billing_country ) ) { $billing_state = ''; } diff --git a/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/ShippingAddressSchema.php b/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/ShippingAddressSchema.php index 75ee619bb42..9a24f27844f 100644 --- a/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/ShippingAddressSchema.php +++ b/plugins/woocommerce-blocks/src/StoreApi/Schemas/V1/ShippingAddressSchema.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\StoreApi\Schemas\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; +use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils; /** * ShippingAddressSchema class. @@ -32,11 +33,12 @@ class ShippingAddressSchema extends AbstractAddressSchema { * @return stdClass */ public function get_item_response( $address ) { + $validation_util = new ValidationUtils(); if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) { $shipping_country = $address->get_shipping_country(); $shipping_state = $address->get_shipping_state(); - if ( ! $this->validate_state( $shipping_state, $shipping_country ) ) { + if ( ! $validation_util->validate_state( $shipping_state, $shipping_country ) ) { $shipping_state = ''; } diff --git a/plugins/woocommerce-blocks/src/StoreApi/Utilities/ValidationUtils.php b/plugins/woocommerce-blocks/src/StoreApi/Utilities/ValidationUtils.php new file mode 100644 index 00000000000..1991b0ae65d --- /dev/null +++ b/plugins/woocommerce-blocks/src/StoreApi/Utilities/ValidationUtils.php @@ -0,0 +1,61 @@ +countries->get_states( $country ) ) : []; + } + + /** + * Validate provided state against a countries list of defined states. + * + * If there are no defined states for a country, any given state is valid. + * + * @param string $state State name or code (sanitized). + * @param string $country Country code. + * @return boolean Valid or not valid. + */ + public function validate_state( $state, $country ) { + $states = $this->get_states_for_country( $country ); + + if ( count( $states ) && ! in_array( \wc_strtoupper( $state ), array_map( '\wc_strtoupper', array_keys( $states ) ), true ) ) { + return false; + } + + return true; + } + + + /** + * Format a state based on the country. If country has defined states, will return a valid upper case state code. + * + * @param string $state State name or code (sanitized). + * @param string $country Country code. + * @return string + */ + public function format_state( $state, $country ) { + $states = $this->get_states_for_country( $country ); + + if ( count( $states ) ) { + $state = \wc_strtoupper( $state ); + $state_values = array_map( '\wc_strtoupper', array_flip( array_map( '\wc_strtoupper', $states ) ) ); + + if ( isset( $state_values[ $state ] ) ) { + // Convert to state code if a state name was provided. + return $state_values[ $state ]; + } + } + + return $state; + } +}