diff --git a/assets/js/base/components/cart-checkout/address-form/prepare-address-fields.ts b/assets/js/base/components/cart-checkout/address-form/prepare-address-fields.ts index 8fafc5fcca9..c73d9437f42 100644 --- a/assets/js/base/components/cart-checkout/address-form/prepare-address-fields.ts +++ b/assets/js/base/components/cart-checkout/address-form/prepare-address-fields.ts @@ -8,20 +8,12 @@ import { AddressFields, CountryAddressFields, defaultAddressFields, - getSetting, KeyedAddressField, LocaleSpecificAddressField, } from '@woocommerce/settings'; import { __, sprintf } from '@wordpress/i18n'; import { isNumber, isString } from '@woocommerce/types'; - -/** - * This is locale data from WooCommerce countries class. This doesn't match the shape of the new field data blocks uses, - * but we can import part of it to set which fields are required. - * - * This supports new properties such as optionalLabel which are not used by core (yet). - */ -const coreLocale = getSetting< CountryAddressFields >( 'countryLocale', {} ); +import { COUNTRY_LOCALE } from '@woocommerce/block-settings'; /** * Gets props from the core locale, then maps them to the shape we require in the client. @@ -72,7 +64,15 @@ const getSupportedCoreLocaleProps = ( return fields; }; -const countryAddressFields: CountryAddressFields = Object.entries( coreLocale ) +/** + * COUNTRY_LOCALE is locale data from WooCommerce countries class. This doesn't match the shape of the new field data blocks uses, + * but we can import part of it to set which fields are required. + * + * This supports new properties such as optionalLabel which are not used by core (yet). + */ +const countryAddressFields: CountryAddressFields = Object.entries( + COUNTRY_LOCALE +) .map( ( [ country, countryLocale ] ) => [ country, Object.entries( countryLocale ) diff --git a/assets/js/base/utils/address.ts b/assets/js/base/utils/address.ts index f894db86997..e786f78b4ec 100644 --- a/assets/js/base/utils/address.ts +++ b/assets/js/base/utils/address.ts @@ -11,9 +11,12 @@ import { defaultAddressFields, ShippingAddress, BillingAddress, - getSetting, } from '@woocommerce/settings'; import { decodeEntities } from '@wordpress/html-entities'; +import { + SHIPPING_COUNTRIES, + SHIPPING_STATES, +} from '@woocommerce/block-settings'; /** * Compare two addresses and see if they are the same. @@ -116,24 +119,16 @@ export const formatShippingAddress = ( if ( Object.values( address ).length === 0 ) { return null; } - const shippingCountries = getSetting< Record< string, string > >( - 'shippingCountries', - {} - ); - const shippingStates = getSetting< Record< string, string > >( - 'shippingStates', - {} - ); const formattedCountry = - typeof shippingCountries[ address.country ] === 'string' - ? decodeEntities( shippingCountries[ address.country ] ) + typeof SHIPPING_COUNTRIES[ address.country ] === 'string' + ? decodeEntities( SHIPPING_COUNTRIES[ address.country ] ) : ''; const formattedState = - typeof shippingStates[ address.country ] === 'object' && - typeof shippingStates[ address.country ][ address.state ] === 'string' + typeof SHIPPING_STATES[ address.country ] === 'object' && + typeof SHIPPING_STATES[ address.country ][ address.state ] === 'string' ? decodeEntities( - shippingStates[ address.country ][ address.state ] + SHIPPING_STATES[ address.country ][ address.state ] ) : address.state; diff --git a/assets/js/settings/blocks/constants.ts b/assets/js/settings/blocks/constants.ts index 2f721b08b43..a7276704189 100644 --- a/assets/js/settings/blocks/constants.ts +++ b/assets/js/settings/blocks/constants.ts @@ -1,7 +1,11 @@ /** * External dependencies */ -import { getSetting, STORE_PAGES } from '@woocommerce/settings'; +import { + getSetting, + STORE_PAGES, + LocaleSpecificAddressField, +} from '@woocommerce/settings'; export type WordCountType = | 'words' @@ -41,22 +45,69 @@ export const CART_URL = STORE_PAGES.cart.permalink; export const LOGIN_URL = STORE_PAGES.myaccount.permalink ? STORE_PAGES.myaccount.permalink : getSetting( 'wpLoginUrl', '/wp-login.php' ); -export const SHIPPING_COUNTRIES = getSetting< Record< string, string > >( - 'shippingCountries', - {} +export const LOCAL_PICKUP_ENABLED = getSetting< boolean >( + 'localPickupEnabled', + false ); -export const ALLOWED_COUNTRIES = getSetting< Record< string, string > >( - 'allowedCountries', + +type CountryData = { + allowBilling: boolean; + allowShipping: boolean; + states: Record< string, string >; + locale: Record< string, LocaleSpecificAddressField >; +}; + +// Contains country names. +const countries = getSetting< Record< string, string > >( 'countries', {} ); + +// Contains country settings. +const countryData = getSetting< Record< string, CountryData > >( + 'countryData', {} ); -export const SHIPPING_STATES = getSetting< - Record< string, Record< string, string > > ->( 'shippingStates', {} ); -export const ALLOWED_STATES = getSetting< Record< string, string > >( - 'allowedStates', - {} + +export const ALLOWED_COUNTRIES = Object.fromEntries( + Object.keys( countryData ) + .filter( ( countryCode ) => { + return countryData[ countryCode ].allowBilling === true; + } ) + .map( ( countryCode ) => { + return [ countryCode, countries[ countryCode ] || '' ]; + } ) ); -export const LOCAL_PICKUP_ENABLED = getSetting< boolean >( - 'localPickupEnabled', - false + +export const ALLOWED_STATES = Object.fromEntries( + Object.keys( countryData ) + .filter( ( countryCode ) => { + return countryData[ countryCode ].allowBilling === true; + } ) + .map( ( countryCode ) => { + return [ countryCode, countryData[ countryCode ].states || [] ]; + } ) +); + +export const SHIPPING_COUNTRIES = Object.fromEntries( + Object.keys( countryData ) + .filter( ( countryCode ) => { + return countryData[ countryCode ].allowShipping === true; + } ) + .map( ( countryCode ) => { + return [ countryCode, countries[ countryCode ] || '' ]; + } ) +); + +export const SHIPPING_STATES = Object.fromEntries( + Object.keys( countryData ) + .filter( ( countryCode ) => { + return countryData[ countryCode ].allowShipping === true; + } ) + .map( ( countryCode ) => { + return [ countryCode, countryData[ countryCode ].states || [] ]; + } ) +); + +export const COUNTRY_LOCALE = Object.fromEntries( + Object.keys( countryData ).map( ( countryCode ) => { + return [ countryCode, countryData[ countryCode ].locale || [] ]; + } ) ); diff --git a/assets/js/settings/shared/default-address-fields.ts b/assets/js/settings/shared/default-address-fields.ts index afa486fdcc7..c1aaed8ecd2 100644 --- a/assets/js/settings/shared/default-address-fields.ts +++ b/assets/js/settings/shared/default-address-fields.ts @@ -20,8 +20,8 @@ export interface AddressField { index: number; } -export interface LocaleSpecificAddressField extends AddressField { - priority: number; +export interface LocaleSpecificAddressField extends Partial< AddressField > { + priority?: number | undefined; } export interface AddressFields { diff --git a/src/BlockTypes/Cart.php b/src/BlockTypes/Cart.php index 73dea9b8b9c..4f49f0c2420 100644 --- a/src/BlockTypes/Cart.php +++ b/src/BlockTypes/Cart.php @@ -1,6 +1,8 @@ asset_data_registry->add( - 'shippingCountries', - function() { - return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() ); - }, - true - ); - $this->asset_data_registry->add( - 'shippingStates', - function() { - return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() ); - }, - true - ); - } - - $this->asset_data_registry->add( - 'countryLocale', - function() { - // Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944. - $country_locale = wc()->countries->get_country_locale(); - $states = wc()->countries->get_states(); - foreach ( $states as $country => $states ) { - if ( empty( $states ) ) { - $country_locale[ $country ]['state']['required'] = false; - $country_locale[ $country ]['state']['hidden'] = true; - } - } - return $country_locale; - }, - true - ); + $this->asset_data_registry->add( 'countryData', CartCheckoutUtils::get_country_data(), true ); $this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true ); $this->asset_data_registry->add( 'isShippingCalculatorEnabled', filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ), true ); $this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true ); @@ -216,30 +186,6 @@ function() { do_action( 'woocommerce_blocks_cart_enqueue_data' ); } - /** - * Removes accents from an array of values, sorts by the values, then returns the original array values sorted. - * - * @param array $array Array of values to sort. - * @return array Sorted array. - */ - protected function deep_sort_with_accents( $array ) { - if ( ! is_array( $array ) || empty( $array ) ) { - return $array; - } - - $array_without_accents = array_map( - function( $value ) { - return is_array( $value ) - ? $this->deep_sort_with_accents( $value ) - : remove_accents( wc_strtolower( html_entity_decode( $value ) ) ); - }, - $array - ); - - asort( $array_without_accents ); - return array_replace( $array_without_accents, $array ); - } - /** * Hydrate the cart block with data from the API. */ diff --git a/src/BlockTypes/Checkout.php b/src/BlockTypes/Checkout.php index f1e8854ab3f..18d69d6b78e 100644 --- a/src/BlockTypes/Checkout.php +++ b/src/BlockTypes/Checkout.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes; use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils; +use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils; /** * Checkout class. @@ -184,54 +185,7 @@ protected function is_checkout_endpoint() { protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); - $this->asset_data_registry->add( - 'allowedCountries', - function() { - return $this->deep_sort_with_accents( WC()->countries->get_allowed_countries() ); - }, - true - ); - $this->asset_data_registry->add( - 'allowedStates', - function() { - return $this->deep_sort_with_accents( WC()->countries->get_allowed_country_states() ); - }, - true - ); - if ( wc_shipping_enabled() ) { - $this->asset_data_registry->add( - 'shippingCountries', - function() { - return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() ); - }, - true - ); - $this->asset_data_registry->add( - 'shippingStates', - function() { - return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() ); - }, - true - ); - } - - $this->asset_data_registry->add( - 'countryLocale', - function() { - // Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944. - $country_locale = wc()->countries->get_country_locale(); - $states = wc()->countries->get_states(); - - foreach ( $states as $country => $states ) { - if ( empty( $states ) ) { - $country_locale[ $country ]['state']['required'] = false; - $country_locale[ $country ]['state']['hidden'] = true; - } - } - return $country_locale; - }, - true - ); + $this->asset_data_registry->add( 'countryData', CartCheckoutUtils::get_country_data(), true ); $this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true ); $this->asset_data_registry->add( 'checkoutAllowsGuest', @@ -373,26 +327,6 @@ protected function is_block_editor() { return $screen && $screen->is_block_editor(); } - /** - * Removes accents from an array of values, sorts by the values, then returns the original array values sorted. - * - * @param array $array Array of values to sort. - * @return array Sorted array. - */ - protected function deep_sort_with_accents( $array ) { - if ( ! is_array( $array ) || empty( $array ) ) { - return $array; - } - - if ( is_array( reset( $array ) ) ) { - return array_map( [ $this, 'deep_sort_with_accents' ], $array ); - } - - $array_without_accents = array_map( 'remove_accents', array_map( 'wc_strtolower', array_map( 'html_entity_decode', $array ) ) ); - asort( $array_without_accents ); - return array_replace( $array_without_accents, $array ); - } - /** * Get saved customer payment methods for use in checkout. */ diff --git a/src/Utils/CartCheckoutUtils.php b/src/Utils/CartCheckoutUtils.php index 8252ca0fcb3..24f59c46f2d 100644 --- a/src/Utils/CartCheckoutUtils.php +++ b/src/Utils/CartCheckoutUtils.php @@ -25,4 +25,54 @@ public static function is_checkout_block_default() { $checkout_page_id = wc_get_page_id( 'checkout' ); return $checkout_page_id && has_block( 'woocommerce/checkout', $checkout_page_id ); } + + /** + * Gets country codes, names, states, and locale information. + * + * @return array + */ + public static function get_country_data() { + $billing_countries = WC()->countries->get_allowed_countries(); + $shipping_countries = WC()->countries->get_shipping_countries(); + $country_locales = wc()->countries->get_country_locale(); + $country_states = wc()->countries->get_states(); + $all_countries = self::deep_sort_with_accents( array_unique( array_merge( $billing_countries, $shipping_countries ) ) ); + + $country_data = []; + + foreach ( array_keys( $all_countries ) as $country_code ) { + $country_data[ $country_code ] = [ + 'allowBilling' => isset( $billing_countries[ $country_code ] ), + 'allowShipping' => isset( $shipping_countries[ $country_code ] ), + 'states' => self::deep_sort_with_accents( $country_states[ $country_code ] ?? [] ), + 'locale' => $country_locales[ $country_code ] ?? [], + ]; + } + + return $country_data; + } + + /** + * Removes accents from an array of values, sorts by the values, then returns the original array values sorted. + * + * @param array $array Array of values to sort. + * @return array Sorted array. + */ + protected static function deep_sort_with_accents( $array ) { + if ( ! is_array( $array ) || empty( $array ) ) { + return $array; + } + + $array_without_accents = array_map( + function( $value ) { + return is_array( $value ) + ? self::deep_sort_with_accents( $value ) + : remove_accents( wc_strtolower( html_entity_decode( $value ) ) ); + }, + $array + ); + + asort( $array_without_accents ); + return array_replace( $array_without_accents, $array ); + } } diff --git a/tests/js/setup-globals.js b/tests/js/setup-globals.js index be0d2518c42..caea4aae7a4 100644 --- a/tests/js/setup-globals.js +++ b/tests/js/setup-globals.js @@ -5,7 +5,6 @@ require( '@wordpress/data' ); global.wcSettings = { adminUrl: 'https://vagrant.local/wp/wp-admin/', shippingMethodsExist: true, - countries: [], currency: { code: 'USD', precision: 2, @@ -32,14 +31,40 @@ global.wcSettings = { userLocale: 'en_US', weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], }, - shippingCountries: { + countries: { AT: 'Austria', CA: 'Canada', GB: 'United Kingdom (UK)', }, - shippingStates: { + countryData: { + AT: { + states: {}, + allowBilling: true, + allowShipping: true, + locale: { + postcode: { priority: 65 }, + state: { required: false, hidden: true }, + }, + }, CA: { - ON: 'Ontario', + states: { + ON: 'Ontario', + }, + allowBilling: true, + allowShipping: true, + locale: { + postcode: { label: 'Postal code' }, + state: { label: 'Province' }, + }, + }, + GB: { + states: {}, + allowBilling: true, + allowShipping: true, + locale: { + postcode: { label: 'Postcode' }, + state: { label: 'County', required: false }, + }, }, }, storePages: { @@ -74,20 +99,6 @@ global.wcSettings = { permalink: '', }, }, - countryLocale: { - GB: { - postcode: { label: 'Postcode' }, - state: { label: 'County', required: false }, - }, - AT: { - postcode: { priority: 65 }, - state: { required: false, hidden: true }, - }, - CA: { - postcode: { label: 'Postal code' }, - state: { label: 'Province' }, - }, - }, attributes: [ { attribute_id: '1', diff --git a/tests/php/BlockTypes/Cart.php b/tests/php/BlockTypes/Cart.php index b8f65d3ee0b..e3dbde4401c 100644 --- a/tests/php/BlockTypes/Cart.php +++ b/tests/php/BlockTypes/Cart.php @@ -1,7 +1,7 @@ cart_block_instance = new CartMock(); - } - /** * We ensure deep sort works with all sort of arrays. */ @@ -37,7 +23,7 @@ public function test_deep_sort_with_accents() { '2', '3', ); - $this->assertEquals( $test_array_1, $this->cart_block_instance->deep_sort_test( $test_array_1 ), '' ); - $this->assertEquals( $test_array_2, $this->cart_block_instance->deep_sort_test( $test_array_2 ), '' ); + $this->assertEquals( $test_array_1, CartCheckoutUtilsMock::deep_sort_test( $test_array_1 ), '' ); + $this->assertEquals( $test_array_2, CartCheckoutUtilsMock::deep_sort_test( $test_array_2 ), '' ); } } diff --git a/tests/php/mocks/CartCheckoutUtilsMock.php b/tests/php/mocks/CartCheckoutUtilsMock.php new file mode 100644 index 00000000000..2958a67e212 --- /dev/null +++ b/tests/php/mocks/CartCheckoutUtilsMock.php @@ -0,0 +1,18 @@ +get( API::class ), - Package::container()->get( AssetDataRegistry::class ), - new IntegrationRegistry(), - ); - } - - /** - * For now don't need to initialize anything in tests so let's - * just override the default behaviour. - */ - protected function initialize() { - } - - /** - * Protected test wrapper for deep_sort_with_accents. - * - * @param array $array The array we want to sort. - */ - public function deep_sort_test( $array ) { - return $this->deep_sort_with_accents( $array ); - } -}