Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Unset default customer state if it doesn't match country #8460

Merged
merged 8 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/StoreApi/Routes/V1/CartUpdateCustomer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\StoreApi\Routes\V1;

use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait;
use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils;

/**
* CartUpdateCustomer class.
Expand Down Expand Up @@ -215,16 +216,29 @@ protected function get_route_post_response( \WP_REST_Request $request ) {
* @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 ) ) {
senadir marked this conversation as resolved.
Show resolved Hide resolved
$billing_state = '';
}
return [
'first_name' => $customer->get_billing_first_name(),
'last_name' => $customer->get_billing_last_name(),
'company' => $customer->get_billing_company(),
'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(),
];
Expand Down
67 changes: 10 additions & 57 deletions src/StoreApi/Schemas/V1/AbstractAddressSchema.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace Automattic\WooCommerce\StoreApi\Schemas\V1;

use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils;

/**
* AddressSchema class.
*
Expand Down Expand Up @@ -87,6 +89,8 @@ public function get_properties() {
* @return array
*/
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['country'] = wc_strtoupper( wc_clean( wp_unslash( $address['country'] ) ) );
$address['first_name'] = wc_clean( wp_unslash( $address['first_name'] ) );
Expand All @@ -95,64 +99,12 @@ public function sanitize_callback( $address, $request, $param ) {
$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 ) {
senadir marked this conversation as resolved.
Show resolved Hide resolved
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.
*
Expand All @@ -164,8 +116,9 @@ protected function format_state( $state, $country ) {
* @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(
Expand All @@ -179,14 +132,14 @@ public function validate_callback( $address, $request, $param ) {
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'] ) ) )
)
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/StoreApi/Schemas/V1/BillingAddressSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\StoreApi\Schemas\V1;

use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils;

/**
* BillingAddressSchema class.
Expand Down Expand Up @@ -89,11 +90,12 @@ public function validate_callback( $address, $request, $param ) {
* @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 = '';
}

Expand Down
4 changes: 3 additions & 1 deletion src/StoreApi/Schemas/V1/ShippingAddressSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\StoreApi\Schemas\V1;

use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils;

/**
* ShippingAddressSchema class.
Expand Down Expand Up @@ -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 = '';
}

Expand Down
61 changes: 61 additions & 0 deletions src/StoreApi/Utilities/ValidationUtils.php
Original file line number Diff line number Diff line change
@@ -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 ) {
mikejolley marked this conversation as resolved.
Show resolved Hide resolved
return $country ? array_filter( (array) \wc()->countries->get_states( $country ) ) : [];
senadir marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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;
}
}