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 <mike.jolley@me.com>

---------

Co-authored-by: Mike Jolley <mike.jolley@me.com>
This commit is contained in:
Seghir Nadir 2023-02-17 14:46:10 +01:00 committed by Saad Tarhi
parent 817b159e5c
commit 9dcf435569
5 changed files with 93 additions and 61 deletions

View File

@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\StoreApi\Routes\V1; namespace Automattic\WooCommerce\StoreApi\Routes\V1;
use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait; use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait;
use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils;
/** /**
* CartUpdateCustomer class. * CartUpdateCustomer class.
@ -215,6 +216,19 @@ class CartUpdateCustomer extends AbstractCartRoute {
* @return array * @return array
*/ */
protected function get_customer_billing_address( \WC_Customer $customer ) { 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 [ return [
'first_name' => $customer->get_billing_first_name(), 'first_name' => $customer->get_billing_first_name(),
'last_name' => $customer->get_billing_last_name(), 'last_name' => $customer->get_billing_last_name(),
@ -222,9 +236,9 @@ class CartUpdateCustomer extends AbstractCartRoute {
'address_1' => $customer->get_billing_address_1(), 'address_1' => $customer->get_billing_address_1(),
'address_2' => $customer->get_billing_address_2(), 'address_2' => $customer->get_billing_address_2(),
'city' => $customer->get_billing_city(), 'city' => $customer->get_billing_city(),
'state' => $customer->get_billing_state(), 'state' => $billing_state,
'postcode' => $customer->get_billing_postcode(), 'postcode' => $customer->get_billing_postcode(),
'country' => $customer->get_billing_country(), 'country' => $billing_country,
'phone' => $customer->get_billing_phone(), 'phone' => $customer->get_billing_phone(),
'email' => $customer->get_billing_email(), 'email' => $customer->get_billing_email(),
]; ];

View File

@ -1,6 +1,8 @@
<?php <?php
namespace Automattic\WooCommerce\StoreApi\Schemas\V1; namespace Automattic\WooCommerce\StoreApi\Schemas\V1;
use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils;
/** /**
* AddressSchema class. * AddressSchema class.
* *
@ -87,6 +89,8 @@ abstract class AbstractAddressSchema extends AbstractSchema {
* @return array * @return array
*/ */
public function sanitize_callback( $address, $request, $param ) { public function sanitize_callback( $address, $request, $param ) {
$validation_util = new ValidationUtils();
$address = array_merge( array_fill_keys( array_keys( $this->get_properties() ), '' ), (array) $address ); $address = array_merge( array_fill_keys( array_keys( $this->get_properties() ), '' ), (array) $address );
$address['country'] = wc_strtoupper( wc_clean( wp_unslash( $address['country'] ) ) ); $address['country'] = wc_strtoupper( wc_clean( wp_unslash( $address['country'] ) ) );
$address['first_name'] = wc_clean( wp_unslash( $address['first_name'] ) ); $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_1'] = wc_clean( wp_unslash( $address['address_1'] ) );
$address['address_2'] = wc_clean( wp_unslash( $address['address_2'] ) ); $address['address_2'] = wc_clean( wp_unslash( $address['address_2'] ) );
$address['city'] = wc_clean( wp_unslash( $address['city'] ) ); $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['postcode'] = $address['postcode'] ? wc_format_postcode( wc_clean( wp_unslash( $address['postcode'] ) ), $address['country'] ) : '';
$address['phone'] = wc_clean( wp_unslash( $address['phone'] ) ); $address['phone'] = wc_clean( wp_unslash( $address['phone'] ) );
return $address; 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. * Validate the given address object.
* *
@ -164,8 +116,9 @@ abstract class AbstractAddressSchema extends AbstractSchema {
* @return true|\WP_Error * @return true|\WP_Error
*/ */
public function validate_callback( $address, $request, $param ) { public function validate_callback( $address, $request, $param ) {
$errors = new \WP_Error(); $errors = new \WP_Error();
$address = $this->sanitize_callback( $address, $request, $param ); $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 ) ) { if ( ! empty( $address['country'] ) && ! in_array( $address['country'], array_keys( wc()->countries->get_countries() ), true ) ) {
$errors->add( $errors->add(
@ -179,14 +132,14 @@ abstract class AbstractAddressSchema extends AbstractSchema {
return $errors; 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( $errors->add(
'invalid_state', 'invalid_state',
sprintf( sprintf(
/* translators: %1$s given state, %2$s valid states */ /* 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' ), __( 'The provided state (%1$s) is not valid. Must be one of: %2$s', 'woo-gutenberg-products-block' ),
esc_html( $address['state'] ), 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'] ) ) )
) )
); );
} }

View File

@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\StoreApi\Schemas\V1; namespace Automattic\WooCommerce\StoreApi\Schemas\V1;
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils;
/** /**
* BillingAddressSchema class. * BillingAddressSchema class.
@ -89,11 +90,12 @@ class BillingAddressSchema extends AbstractAddressSchema {
* @return stdClass * @return stdClass
*/ */
public function get_item_response( $address ) { public function get_item_response( $address ) {
$validation_util = new ValidationUtils();
if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) { if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) {
$billing_country = $address->get_billing_country(); $billing_country = $address->get_billing_country();
$billing_state = $address->get_billing_state(); $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 = ''; $billing_state = '';
} }

View File

@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\StoreApi\Schemas\V1; namespace Automattic\WooCommerce\StoreApi\Schemas\V1;
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils;
/** /**
* ShippingAddressSchema class. * ShippingAddressSchema class.
@ -32,11 +33,12 @@ class ShippingAddressSchema extends AbstractAddressSchema {
* @return stdClass * @return stdClass
*/ */
public function get_item_response( $address ) { public function get_item_response( $address ) {
$validation_util = new ValidationUtils();
if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) { if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) {
$shipping_country = $address->get_shipping_country(); $shipping_country = $address->get_shipping_country();
$shipping_state = $address->get_shipping_state(); $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 = ''; $shipping_state = '';
} }

View File

@ -0,0 +1,61 @@
<?php
namespace Automattic\WooCommerce\StoreApi\Utilities;
/**
* ValidationUtils class.
* Helper class which validates and update customer info.
*/
class ValidationUtils {
/**
* Get list of states for a country.
*
* @param string $country Country code.
* @return array Array of state names indexed by state keys.
*/
public 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.
*/
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;
}
}