Edit File by line
/home/zeestwma/richards.../wp-conte.../plugins/woocomme.../src/StoreApi/Utilitie...
File: OrderController.php
<?php
[0] Fix | Delete
declare( strict_types = 1 );
[1] Fix | Delete
namespace Automattic\WooCommerce\StoreApi\Utilities;
[2] Fix | Delete
[3] Fix | Delete
use Exception;
[4] Fix | Delete
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
[5] Fix | Delete
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
[6] Fix | Delete
use Automattic\WooCommerce\Blocks\Package;
[7] Fix | Delete
use Automattic\WooCommerce\Utilities\DiscountsUtil;
[8] Fix | Delete
use Automattic\WooCommerce\Utilities\ShippingUtil;
[9] Fix | Delete
use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
[10] Fix | Delete
use Automattic\WooCommerce\StoreApi\Utilities\PaymentUtils;
[11] Fix | Delete
use Automattic\WooCommerce\StoreApi\Utilities\ArrayUtils;
[12] Fix | Delete
use Automattic\WooCommerce\Utilities\ArrayUtil;
[13] Fix | Delete
[14] Fix | Delete
/**
[15] Fix | Delete
* OrderController class.
[16] Fix | Delete
* Helper class which creates and syncs orders with the cart.
[17] Fix | Delete
*/
[18] Fix | Delete
class OrderController {
[19] Fix | Delete
[20] Fix | Delete
/**
[21] Fix | Delete
* Checkout fields controller.
[22] Fix | Delete
*
[23] Fix | Delete
* @var CheckoutFields
[24] Fix | Delete
*/
[25] Fix | Delete
private CheckoutFields $additional_fields_controller;
[26] Fix | Delete
[27] Fix | Delete
/**
[28] Fix | Delete
* Constructor.
[29] Fix | Delete
*/
[30] Fix | Delete
public function __construct() {
[31] Fix | Delete
$this->additional_fields_controller = Package::container()->get( CheckoutFields::class );
[32] Fix | Delete
}
[33] Fix | Delete
[34] Fix | Delete
/**
[35] Fix | Delete
* Create order and set props based on global settings.
[36] Fix | Delete
*
[37] Fix | Delete
* @throws RouteException Exception if invalid data is detected.
[38] Fix | Delete
*
[39] Fix | Delete
* @return \WC_Order A new order object.
[40] Fix | Delete
*/
[41] Fix | Delete
public function create_order_from_cart() {
[42] Fix | Delete
if ( wc()->cart->is_empty() ) {
[43] Fix | Delete
throw new RouteException(
[44] Fix | Delete
'woocommerce_rest_cart_empty',
[45] Fix | Delete
__( 'Cannot create order from empty cart.', 'woocommerce' ),
[46] Fix | Delete
400
[47] Fix | Delete
);
[48] Fix | Delete
}
[49] Fix | Delete
[50] Fix | Delete
add_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) );
[51] Fix | Delete
[52] Fix | Delete
$order = new \WC_Order();
[53] Fix | Delete
$order->set_status( 'checkout-draft' );
[54] Fix | Delete
$order->set_created_via( 'store-api' );
[55] Fix | Delete
$this->update_order_from_cart( $order );
[56] Fix | Delete
[57] Fix | Delete
remove_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) );
[58] Fix | Delete
[59] Fix | Delete
return $order;
[60] Fix | Delete
}
[61] Fix | Delete
[62] Fix | Delete
/**
[63] Fix | Delete
* Update an order using data from the current cart.
[64] Fix | Delete
*
[65] Fix | Delete
* @param \WC_Order $order The order object to update.
[66] Fix | Delete
* @param boolean $update_totals Whether to update totals or not.
[67] Fix | Delete
*/
[68] Fix | Delete
public function update_order_from_cart( \WC_Order $order, $update_totals = true ) {
[69] Fix | Delete
/**
[70] Fix | Delete
* This filter ensures that local pickup locations are still used for order taxes by forcing the address used to
[71] Fix | Delete
* calculate tax for an order to match the current address of the customer.
[72] Fix | Delete
*
[73] Fix | Delete
* - The method `$customer->get_taxable_address()` runs the filter `woocommerce_customer_taxable_address`.
[74] Fix | Delete
* - While we have a session, our `ShippingController::filter_taxable_address` function uses this hook to set
[75] Fix | Delete
* the customer address to the pickup location address if local pickup is the chosen method.
[76] Fix | Delete
*
[77] Fix | Delete
* Without this code in place, `$customer->get_taxable_address()` is not used when order taxes are calculated,
[78] Fix | Delete
* resulting in the wrong taxes being applied with local pickup.
[79] Fix | Delete
*
[80] Fix | Delete
* The alternative would be to instead use `woocommerce_order_get_tax_location` to return the pickup location
[81] Fix | Delete
* address directly, however since we have the customer filter in place we don't need to duplicate effort.
[82] Fix | Delete
*
[83] Fix | Delete
* @see \WC_Abstract_Order::get_tax_location()
[84] Fix | Delete
*/
[85] Fix | Delete
add_filter(
[86] Fix | Delete
'woocommerce_order_get_tax_location',
[87] Fix | Delete
function ( $location ) {
[88] Fix | Delete
[89] Fix | Delete
if ( ! is_null( wc()->customer ) ) {
[90] Fix | Delete
[91] Fix | Delete
$taxable_address = wc()->customer->get_taxable_address();
[92] Fix | Delete
[93] Fix | Delete
$location = array(
[94] Fix | Delete
'country' => $taxable_address[0],
[95] Fix | Delete
'state' => $taxable_address[1],
[96] Fix | Delete
'postcode' => $taxable_address[2],
[97] Fix | Delete
'city' => $taxable_address[3],
[98] Fix | Delete
);
[99] Fix | Delete
}
[100] Fix | Delete
[101] Fix | Delete
return $location;
[102] Fix | Delete
}
[103] Fix | Delete
);
[104] Fix | Delete
[105] Fix | Delete
// Ensure cart is current.
[106] Fix | Delete
if ( $update_totals ) {
[107] Fix | Delete
wc()->cart->calculate_totals();
[108] Fix | Delete
}
[109] Fix | Delete
[110] Fix | Delete
// Update the current order to match the current cart.
[111] Fix | Delete
$this->update_line_items_from_cart( $order );
[112] Fix | Delete
$this->update_addresses_from_cart( $order );
[113] Fix | Delete
$order->set_currency( get_woocommerce_currency() );
[114] Fix | Delete
$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
[115] Fix | Delete
$order->set_customer_id( get_current_user_id() );
[116] Fix | Delete
$order->set_customer_ip_address( \WC_Geolocation::get_ip_address() );
[117] Fix | Delete
$order->set_customer_user_agent( wc_get_user_agent() );
[118] Fix | Delete
$order->set_payment_method( PaymentUtils::get_default_payment_method() );
[119] Fix | Delete
$order->update_meta_data( 'is_vat_exempt', wc_bool_to_string( wc()->cart->get_customer()->get_is_vat_exempt() ) );
[120] Fix | Delete
$order->calculate_totals();
[121] Fix | Delete
}
[122] Fix | Delete
[123] Fix | Delete
/**
[124] Fix | Delete
* Copies order data to customer object (not the session), so values persist for future checkouts.
[125] Fix | Delete
*
[126] Fix | Delete
* @param \WC_Order $order Order object.
[127] Fix | Delete
*/
[128] Fix | Delete
public function sync_customer_data_with_order( \WC_Order $order ) {
[129] Fix | Delete
if ( $order->get_customer_id() ) {
[130] Fix | Delete
$customer = new \WC_Customer( $order->get_customer_id() );
[131] Fix | Delete
$customer->set_props(
[132] Fix | Delete
array(
[133] Fix | Delete
'billing_first_name' => $order->get_billing_first_name(),
[134] Fix | Delete
'billing_last_name' => $order->get_billing_last_name(),
[135] Fix | Delete
'billing_company' => $order->get_billing_company(),
[136] Fix | Delete
'billing_address_1' => $order->get_billing_address_1(),
[137] Fix | Delete
'billing_address_2' => $order->get_billing_address_2(),
[138] Fix | Delete
'billing_city' => $order->get_billing_city(),
[139] Fix | Delete
'billing_state' => $order->get_billing_state(),
[140] Fix | Delete
'billing_postcode' => $order->get_billing_postcode(),
[141] Fix | Delete
'billing_country' => $order->get_billing_country(),
[142] Fix | Delete
'billing_email' => $order->get_billing_email(),
[143] Fix | Delete
'billing_phone' => $order->get_billing_phone(),
[144] Fix | Delete
'shipping_first_name' => $order->get_shipping_first_name(),
[145] Fix | Delete
'shipping_last_name' => $order->get_shipping_last_name(),
[146] Fix | Delete
'shipping_company' => $order->get_shipping_company(),
[147] Fix | Delete
'shipping_address_1' => $order->get_shipping_address_1(),
[148] Fix | Delete
'shipping_address_2' => $order->get_shipping_address_2(),
[149] Fix | Delete
'shipping_city' => $order->get_shipping_city(),
[150] Fix | Delete
'shipping_state' => $order->get_shipping_state(),
[151] Fix | Delete
'shipping_postcode' => $order->get_shipping_postcode(),
[152] Fix | Delete
'shipping_country' => $order->get_shipping_country(),
[153] Fix | Delete
'shipping_phone' => $order->get_shipping_phone(),
[154] Fix | Delete
)
[155] Fix | Delete
);
[156] Fix | Delete
[157] Fix | Delete
$this->additional_fields_controller->sync_customer_additional_fields_with_order( $order, $customer );
[158] Fix | Delete
[159] Fix | Delete
$customer->save();
[160] Fix | Delete
}
[161] Fix | Delete
}
[162] Fix | Delete
[163] Fix | Delete
/**
[164] Fix | Delete
* Final validation ran before payment is taken.
[165] Fix | Delete
*
[166] Fix | Delete
* By this point we have an order populated with customer data and items.
[167] Fix | Delete
*
[168] Fix | Delete
* @throws RouteException Exception if invalid data is detected.
[169] Fix | Delete
* @param \WC_Order $order Order object.
[170] Fix | Delete
*/
[171] Fix | Delete
public function validate_order_before_payment( \WC_Order $order ) {
[172] Fix | Delete
$needs_shipping = wc()->cart->needs_shipping();
[173] Fix | Delete
$chosen_shipping_methods = wc()->session->get( 'chosen_shipping_methods', [] );
[174] Fix | Delete
[175] Fix | Delete
$this->validate_coupons( $order );
[176] Fix | Delete
$this->validate_email( $order );
[177] Fix | Delete
$this->validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods );
[178] Fix | Delete
$this->validate_addresses( $order, $needs_shipping );
[179] Fix | Delete
[180] Fix | Delete
// Perform custom validations.
[181] Fix | Delete
$this->perform_custom_order_validation( $order );
[182] Fix | Delete
}
[183] Fix | Delete
[184] Fix | Delete
/**
[185] Fix | Delete
* Final validation for existing orders, ran before payment is taken.
[186] Fix | Delete
*
[187] Fix | Delete
* By this point we have an order populated with customer data and items.
[188] Fix | Delete
*
[189] Fix | Delete
* Since the cart is not involved, we don't validate shipping methods and assume the order already
[190] Fix | Delete
* contains the correct shipping items.
[191] Fix | Delete
*
[192] Fix | Delete
* @throws RouteException Exception if invalid data is detected.
[193] Fix | Delete
* @param \WC_Order $order Order object.
[194] Fix | Delete
*/
[195] Fix | Delete
public function validate_existing_order_before_payment( \WC_Order $order ) {
[196] Fix | Delete
$needs_shipping = $order->needs_shipping();
[197] Fix | Delete
[198] Fix | Delete
$this->validate_coupons( $order, true );
[199] Fix | Delete
$this->validate_email( $order );
[200] Fix | Delete
$this->validate_addresses( $order, $needs_shipping );
[201] Fix | Delete
[202] Fix | Delete
// Perform custom validations.
[203] Fix | Delete
$this->perform_custom_order_validation( $order );
[204] Fix | Delete
}
[205] Fix | Delete
[206] Fix | Delete
/**
[207] Fix | Delete
* Perform custom order validation via WooCommerce hooks.
[208] Fix | Delete
*
[209] Fix | Delete
* Allows plugins to perform custom validation before payment.
[210] Fix | Delete
*
[211] Fix | Delete
* @param \WC_Order $order Order object.
[212] Fix | Delete
* @throws RouteException Exception if validation fails.
[213] Fix | Delete
*/
[214] Fix | Delete
protected function perform_custom_order_validation( \WC_Order $order ) {
[215] Fix | Delete
$validation_errors = new \WP_Error();
[216] Fix | Delete
[217] Fix | Delete
/**
[218] Fix | Delete
* Allow plugins to perform custom validation before payment.
[219] Fix | Delete
*
[220] Fix | Delete
* Plugins can add errors to the $validation_errors object.
[221] Fix | Delete
*
[222] Fix | Delete
* @param \WC_Order $order The order object.
[223] Fix | Delete
* @param \WP_Error $validation_errors WP_Error object to add custom errors to.
[224] Fix | Delete
* @since 9.9.0
[225] Fix | Delete
*/
[226] Fix | Delete
do_action( 'woocommerce_checkout_validate_order_before_payment', $order, $validation_errors );
[227] Fix | Delete
[228] Fix | Delete
// Check if there are any errors after custom validation.
[229] Fix | Delete
if ( $validation_errors->has_errors() ) {
[230] Fix | Delete
throw new RouteException(
[231] Fix | Delete
'woocommerce_rest_checkout_custom_validation_error',
[232] Fix | Delete
esc_html( implode( ' ', $validation_errors->get_error_messages() ) ),
[233] Fix | Delete
400
[234] Fix | Delete
);
[235] Fix | Delete
}
[236] Fix | Delete
}
[237] Fix | Delete
[238] Fix | Delete
/**
[239] Fix | Delete
* Convert a coupon code to a coupon object.
[240] Fix | Delete
*
[241] Fix | Delete
* @param string $coupon_code Coupon code.
[242] Fix | Delete
* @return \WC_Coupon Coupon object.
[243] Fix | Delete
*/
[244] Fix | Delete
protected function get_coupon( $coupon_code ) {
[245] Fix | Delete
return new \WC_Coupon( $coupon_code );
[246] Fix | Delete
}
[247] Fix | Delete
[248] Fix | Delete
/**
[249] Fix | Delete
* Validate coupons applied to the order and remove those that are not valid.
[250] Fix | Delete
*
[251] Fix | Delete
* @throws RouteException Exception if invalid data is detected.
[252] Fix | Delete
* @param \WC_Order $order Order object.
[253] Fix | Delete
* @param bool $use_order_data Whether to use order data or cart data.
[254] Fix | Delete
*/
[255] Fix | Delete
protected function validate_coupons( \WC_Order $order, bool $use_order_data = false ) {
[256] Fix | Delete
$coupon_codes = $order->get_coupon_codes();
[257] Fix | Delete
$coupons = array_filter( array_map( array( $this, 'get_coupon' ), $coupon_codes ) );
[258] Fix | Delete
$validators = array( 'validate_coupon_email_restriction', 'validate_coupon_usage_limit' );
[259] Fix | Delete
$coupon_errors = array();
[260] Fix | Delete
[261] Fix | Delete
foreach ( $coupons as $coupon ) {
[262] Fix | Delete
try {
[263] Fix | Delete
array_walk(
[264] Fix | Delete
$validators,
[265] Fix | Delete
function ( $validator, $index, $params ) {
[266] Fix | Delete
call_user_func_array( array( $this, $validator ), $params );
[267] Fix | Delete
},
[268] Fix | Delete
array( $coupon, $order )
[269] Fix | Delete
);
[270] Fix | Delete
} catch ( Exception $error ) {
[271] Fix | Delete
$coupon_errors[ $coupon->get_code() ] = $error->getMessage();
[272] Fix | Delete
}
[273] Fix | Delete
}
[274] Fix | Delete
[275] Fix | Delete
if ( $coupon_errors ) {
[276] Fix | Delete
// Remove all coupons that were not valid.
[277] Fix | Delete
if ( $use_order_data ) {
[278] Fix | Delete
$error_code = 'woocommerce_rest_order_coupon_errors';
[279] Fix | Delete
[280] Fix | Delete
foreach ( $coupon_errors as $coupon_code => $message ) {
[281] Fix | Delete
$order->remove_coupon( $coupon_code );
[282] Fix | Delete
}
[283] Fix | Delete
[284] Fix | Delete
// Recalculate totals.
[285] Fix | Delete
$order->calculate_totals();
[286] Fix | Delete
} else {
[287] Fix | Delete
$error_code = 'woocommerce_rest_cart_coupon_errors';
[288] Fix | Delete
[289] Fix | Delete
foreach ( $coupon_errors as $coupon_code => $message ) {
[290] Fix | Delete
wc()->cart->remove_coupon( $coupon_code );
[291] Fix | Delete
}
[292] Fix | Delete
[293] Fix | Delete
// Recalculate totals.
[294] Fix | Delete
wc()->cart->calculate_totals();
[295] Fix | Delete
[296] Fix | Delete
// Re-sync order with cart.
[297] Fix | Delete
$this->update_order_from_cart( $order );
[298] Fix | Delete
}
[299] Fix | Delete
[300] Fix | Delete
// Return exception so customer can review before payment.
[301] Fix | Delete
if ( 1 === count( $coupon_errors ) && $use_order_data ) {
[302] Fix | Delete
$error_message = sprintf(
[303] Fix | Delete
/* translators: %1$s Coupon codes, %2$s Reason */
[304] Fix | Delete
__( '"%1$s" was removed from the order. %2$s', 'woocommerce' ),
[305] Fix | Delete
array_keys( $coupon_errors )[0],
[306] Fix | Delete
array_values( $coupon_errors )[0],
[307] Fix | Delete
);
[308] Fix | Delete
} elseif ( 1 === count( $coupon_errors ) ) {
[309] Fix | Delete
$error_message = sprintf(
[310] Fix | Delete
/* translators: %1$s Coupon codes, %2$s Reason */
[311] Fix | Delete
__( '"%1$s" was removed from the cart. %2$s', 'woocommerce' ),
[312] Fix | Delete
array_keys( $coupon_errors )[0],
[313] Fix | Delete
array_values( $coupon_errors )[0],
[314] Fix | Delete
);
[315] Fix | Delete
} elseif ( $use_order_data ) {
[316] Fix | Delete
$error_message = sprintf(
[317] Fix | Delete
/* translators: %s Coupon codes. */
[318] Fix | Delete
__( 'Invalid coupons were removed from the order: "%s"', 'woocommerce' ),
[319] Fix | Delete
implode( '", "', array_keys( $coupon_errors ) )
[320] Fix | Delete
);
[321] Fix | Delete
} else {
[322] Fix | Delete
$error_message = sprintf(
[323] Fix | Delete
/* translators: %s Coupon codes. */
[324] Fix | Delete
__( 'Invalid coupons were removed from the cart: "%s"', 'woocommerce' ),
[325] Fix | Delete
implode( '", "', array_keys( $coupon_errors ) )
[326] Fix | Delete
);
[327] Fix | Delete
}
[328] Fix | Delete
[329] Fix | Delete
throw new RouteException( $error_code, $error_message, 409, array( 'removed_coupons' => $coupon_errors ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
[330] Fix | Delete
}
[331] Fix | Delete
}
[332] Fix | Delete
[333] Fix | Delete
/**
[334] Fix | Delete
* Validates the customer email. This is a required field.
[335] Fix | Delete
*
[336] Fix | Delete
* @throws RouteException Exception if invalid data is detected.
[337] Fix | Delete
* @param \WC_Order $order Order object.
[338] Fix | Delete
*/
[339] Fix | Delete
protected function validate_email( \WC_Order $order ) {
[340] Fix | Delete
$email = $order->get_billing_email();
[341] Fix | Delete
[342] Fix | Delete
if ( empty( $email ) ) {
[343] Fix | Delete
throw new RouteException(
[344] Fix | Delete
'woocommerce_rest_missing_email_address',
[345] Fix | Delete
__( 'A valid email address is required', 'woocommerce' ),
[346] Fix | Delete
400
[347] Fix | Delete
);
[348] Fix | Delete
}
[349] Fix | Delete
[350] Fix | Delete
if ( ! is_email( $email ) ) {
[351] Fix | Delete
throw new RouteException(
[352] Fix | Delete
'woocommerce_rest_invalid_email_address',
[353] Fix | Delete
sprintf(
[354] Fix | Delete
/* translators: %s provided email. */
[355] Fix | Delete
__( 'The provided email address (%s) is not valid—please provide a valid email address', 'woocommerce' ),
[356] Fix | Delete
esc_html( $email )
[357] Fix | Delete
),
[358] Fix | Delete
400
[359] Fix | Delete
);
[360] Fix | Delete
}
[361] Fix | Delete
}
[362] Fix | Delete
[363] Fix | Delete
/**
[364] Fix | Delete
* Validates customer address data based on the locale to ensure required fields are set.
[365] Fix | Delete
*
[366] Fix | Delete
* @throws RouteException Exception if invalid data is detected.
[367] Fix | Delete
* @param \WC_Order $order Order object.
[368] Fix | Delete
* @param bool $needs_shipping Whether the order needs shipping.
[369] Fix | Delete
*/
[370] Fix | Delete
protected function validate_addresses( \WC_Order $order, bool $needs_shipping ) {
[371] Fix | Delete
$errors = new \WP_Error();
[372] Fix | Delete
$billing_country = $order->get_billing_country();
[373] Fix | Delete
$shipping_country = $order->get_shipping_country();
[374] Fix | Delete
[375] Fix | Delete
if ( $needs_shipping ) {
[376] Fix | Delete
$local_pickup_method_ids = LocalPickupUtils::get_local_pickup_method_ids();
[377] Fix | Delete
$selected_shipping_rates = ShippingUtil::get_selected_shipping_rates_from_packages( WC()->shipping()->get_packages() );
[378] Fix | Delete
$selected_shipping_rates_are_all_local_pickup = ArrayUtil::array_all(
[379] Fix | Delete
$selected_shipping_rates,
[380] Fix | Delete
function ( $rate ) use ( $local_pickup_method_ids ) {
[381] Fix | Delete
return in_array( $rate->get_method_id(), $local_pickup_method_ids, true );
[382] Fix | Delete
}
[383] Fix | Delete
);
[384] Fix | Delete
[385] Fix | Delete
// If only local pickup is selected, we don't need to validate the shipping country.
[386] Fix | Delete
if ( ! $selected_shipping_rates_are_all_local_pickup && ! $this->validate_allowed_country( $shipping_country, (array) wc()->countries->get_shipping_countries() ) ) {
[387] Fix | Delete
throw new RouteException(
[388] Fix | Delete
'woocommerce_rest_invalid_address_country',
[389] Fix | Delete
sprintf(
[390] Fix | Delete
/* translators: %s country code. */
[391] Fix | Delete
esc_html__( 'Sorry, we do not ship orders to the provided country (%s)', 'woocommerce' ),
[392] Fix | Delete
esc_html( $shipping_country )
[393] Fix | Delete
),
[394] Fix | Delete
400,
[395] Fix | Delete
array(
[396] Fix | Delete
'allowed_countries' => array_map( 'esc_html', array_keys( wc()->countries->get_shipping_countries() ) ),
[397] Fix | Delete
)
[398] Fix | Delete
);
[399] Fix | Delete
}
[400] Fix | Delete
}
[401] Fix | Delete
[402] Fix | Delete
if ( ! $this->validate_allowed_country( $billing_country, (array) wc()->countries->get_allowed_countries() ) ) {
[403] Fix | Delete
throw new RouteException(
[404] Fix | Delete
'woocommerce_rest_invalid_address_country',
[405] Fix | Delete
sprintf(
[406] Fix | Delete
/* translators: %s country code. */
[407] Fix | Delete
esc_html__( 'Sorry, we do not allow orders from the provided country (%s)', 'woocommerce' ),
[408] Fix | Delete
esc_html( $billing_country )
[409] Fix | Delete
),
[410] Fix | Delete
400,
[411] Fix | Delete
array(
[412] Fix | Delete
'allowed_countries' => array_map( 'esc_html', array_keys( wc()->countries->get_allowed_countries() ) ),
[413] Fix | Delete
)
[414] Fix | Delete
);
[415] Fix | Delete
}
[416] Fix | Delete
[417] Fix | Delete
if ( $needs_shipping ) {
[418] Fix | Delete
$this->validate_address_fields( $order, 'shipping', $errors );
[419] Fix | Delete
}
[420] Fix | Delete
$this->validate_address_fields( $order, 'billing', $errors );
[421] Fix | Delete
[422] Fix | Delete
if ( ! $errors->has_errors() ) {
[423] Fix | Delete
return;
[424] Fix | Delete
}
[425] Fix | Delete
[426] Fix | Delete
$errors_by_code = array();
[427] Fix | Delete
$error_codes = $errors->get_error_codes();
[428] Fix | Delete
[429] Fix | Delete
foreach ( $error_codes as $code ) {
[430] Fix | Delete
$errors_by_code[ $code ] = $errors->get_error_messages( $code );
[431] Fix | Delete
}
[432] Fix | Delete
[433] Fix | Delete
// Surface errors from first code.
[434] Fix | Delete
foreach ( $errors_by_code as $code => $error_messages ) {
[435] Fix | Delete
throw new RouteException(
[436] Fix | Delete
'woocommerce_rest_invalid_address',
[437] Fix | Delete
sprintf(
[438] Fix | Delete
/* translators: %s Address type. */
[439] Fix | Delete
esc_html__( 'There was a problem with the provided %s:', 'woocommerce' ) . ' ' . esc_html( implode( ', ', $error_messages ) ),
[440] Fix | Delete
'shipping' === $code ? esc_html__( 'shipping address', 'woocommerce' ) : esc_html__( 'billing address', 'woocommerce' )
[441] Fix | Delete
),
[442] Fix | Delete
400,
[443] Fix | Delete
array(
[444] Fix | Delete
'errors' => $errors_by_code, // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
[445] Fix | Delete
)
[446] Fix | Delete
);
[447] Fix | Delete
}
[448] Fix | Delete
}
[449] Fix | Delete
[450] Fix | Delete
/**
[451] Fix | Delete
* Check all required address fields are set and return errors if not.
[452] Fix | Delete
*
[453] Fix | Delete
* @param string $country Country code.
[454] Fix | Delete
* @param array $allowed_countries List of valid country codes.
[455] Fix | Delete
* @return boolean True if valid.
[456] Fix | Delete
*/
[457] Fix | Delete
protected function validate_allowed_country( $country, array $allowed_countries ) {
[458] Fix | Delete
return array_key_exists( $country, $allowed_countries );
[459] Fix | Delete
}
[460] Fix | Delete
[461] Fix | Delete
/**
[462] Fix | Delete
* Check all required address fields are set and return errors if not.
[463] Fix | Delete
*
[464] Fix | Delete
* @param \WC_Order $order Order object.
[465] Fix | Delete
* @param string $address_type billing or shipping address, used in error messages.
[466] Fix | Delete
* @param \WP_Error $errors Error object.
[467] Fix | Delete
*/
[468] Fix | Delete
protected function validate_address_fields( \WC_Order $order, $address_type, \WP_Error $errors ) {
[469] Fix | Delete
$all_locales = wc()->countries->get_country_locale();
[470] Fix | Delete
$address = $order->get_address( $address_type );
[471] Fix | Delete
$current_locale = $all_locales[ $address['country'] ] ?? [];
[472] Fix | Delete
[473] Fix | Delete
foreach ( $all_locales['default'] as $key => $value ) {
[474] Fix | Delete
// If $current_locale[ $key ] is not empty, merge it with locale default, otherwise just use default locale.
[475] Fix | Delete
$current_locale[ $key ] = ! empty( $current_locale[ $key ] )
[476] Fix | Delete
? wp_parse_args( $current_locale[ $key ], $value )
[477] Fix | Delete
: $value;
[478] Fix | Delete
}
[479] Fix | Delete
[480] Fix | Delete
$additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $order, $address_type );
[481] Fix | Delete
[482] Fix | Delete
$address = array_merge( $address, $additional_fields );
[483] Fix | Delete
[484] Fix | Delete
foreach ( $current_locale as $address_field_key => $address_field ) {
[485] Fix | Delete
// Skip validation if field is not required or if it is hidden.
[486] Fix | Delete
if (
[487] Fix | Delete
true !== wc_string_to_bool( $address_field['required'] ?? false ) ||
[488] Fix | Delete
true === wc_string_to_bool( $address_field['hidden'] ?? false )
[489] Fix | Delete
) {
[490] Fix | Delete
continue;
[491] Fix | Delete
}
[492] Fix | Delete
[493] Fix | Delete
// Check if field is not set, is an empty string, or is an empty array.
[494] Fix | Delete
$is_empty = ! isset( $address[ $address_field_key ] ) ||
[495] Fix | Delete
( is_string( $address[ $address_field_key ] ) && '' === trim( $address[ $address_field_key ] ) ) ||
[496] Fix | Delete
( is_array( $address[ $address_field_key ] ) && 0 === count( $address[ $address_field_key ] ) );
[497] Fix | Delete
[498] Fix | Delete
if ( $is_empty ) {
[499] Fix | Delete
12
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function