on get_route_post_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $customer = wc()->customer; // Get data from request object and merge with customer object, then sanitize. $billing = $this->schema->billing_address_schema->sanitize_callback( wp_parse_args( $request['billing_address'] ?? [], $this->get_customer_billing_address( $customer ) ), $request, 'billing_address' ); $shipping = $this->schema->billing_address_schema->sanitize_callback( wp_parse_args( $request['shipping_address'] ?? [], $this->get_customer_shipping_address( $customer ) ), $request, 'shipping_address' ); // If the cart does not need shipping, shipping address is forced to match billing address unless defined. if ( ! $cart->needs_shipping() && ! isset( $request['shipping_address'] ) ) { $shipping = $billing; } // Run validation and sanitization now that the cart and customer data is loaded. $billing = $this->schema->billing_address_schema->sanitize_callback( $billing, $request, 'billing_address' ); $shipping = $this->schema->shipping_address_schema->sanitize_callback( $shipping, $request, 'shipping_address' ); // Validate data now everything is clean.. $validation_check = $this->validate_address_params( $request, $billing, $shipping ); if ( is_wp_error( $validation_check ) ) { return rest_ensure_response( $validation_check ); } $customer->set_props( array( 'billing_first_name' => $billing['first_name'] ?? null, 'billing_last_name' => $billing['last_name'] ?? null, 'billing_company' => $billing['company'] ?? null, 'billing_address_1' => $billing['address_1'] ?? null, 'billing_address_2' => $billing['address_2'] ?? null, 'billing_city' => $billing['city'] ?? null, 'billing_state' => $billing['state'] ?? null, 'billing_postcode' => $billing['postcode'] ?? null, 'billing_country' => $billing['country'] ?? null, 'billing_phone' => $billing['phone'] ?? null, 'billing_email' => $billing['email'] ?? null, 'shipping_first_name' => $shipping['first_name'] ?? null, 'shipping_last_name' => $shipping['last_name'] ?? null, 'shipping_company' => $shipping['company'] ?? null, 'shipping_address_1' => $shipping['address_1'] ?? null, 'shipping_address_2' => $shipping['address_2'] ?? null, 'shipping_city' => $shipping['city'] ?? null, 'shipping_state' => $shipping['state'] ?? null, 'shipping_postcode' => $shipping['postcode'] ?? null, 'shipping_country' => $shipping['country'] ?? null, 'shipping_phone' => $shipping['phone'] ?? null, ) ); // We want to only get additional fields passed, since core ones are already saved. $core_fields = array_keys( $this->additional_fields_controller->get_core_fields() ); $additional_shipping_values = array_diff_key( $shipping, array_flip( $core_fields ) ); $additional_billing_values = array_diff_key( $billing, array_flip( $core_fields ) ); // We save them one by one, and we add the group prefix. foreach ( $additional_shipping_values as $key => $value ) { $this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'shipping' ); } foreach ( $additional_billing_values as $key => $value ) { $this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'billing' ); } wc_do_deprecated_action( 'woocommerce_blocks_cart_update_customer_from_request', array( $customer, $request, ), '7.2.0', 'woocommerce_store_api_cart_update_customer_from_request', 'This action was deprecated in WooCommerce Blocks version 7.2.0. Please use woocommerce_store_api_cart_update_customer_from_request instead.' ); /** * Fires when the Checkout Block/Store API updates a customer from the API request data. * * @since 7.2.0 * * @param \WC_Customer $customer Customer object. * @param \WP_REST_Request $request Full details about the request. */ do_action( 'woocommerce_store_api_cart_update_customer_from_request', $customer, $request ); $customer->save(); $this->cart_controller->calculate_totals(); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } /** * Get full customer billing address. * * @param \WC_Customer $customer Customer object. * @return array */ protected function get_customer_billing_address( \WC_Customer $customer ) { $additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $customer, 'billing' ); return array_merge( [ '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(), 'postcode' => $customer->get_billing_postcode(), 'country' => $customer->get_billing_country(), 'phone' => $customer->get_billing_phone(), 'email' => $customer->get_billing_email(), ], $additional_fields ); } /** * Get full customer shipping address. * * @param \WC_Customer $customer Customer object. * @return array */ protected function get_customer_shipping_address( \WC_Customer $customer ) { $additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $customer, 'shipping' ); return array_merge( [ 'first_name' => $customer->get_shipping_first_name(), 'last_name' => $customer->get_shipping_last_name(), 'company' => $customer->get_shipping_company(), 'address_1' => $customer->get_shipping_address_1(), 'address_2' => $customer->get_shipping_address_2(), 'city' => $customer->get_shipping_city(), 'state' => $customer->get_shipping_state(), 'postcode' => $customer->get_shipping_postcode(), 'country' => $customer->get_shipping_country(), 'phone' => $customer->get_shipping_phone(), ], $additional_fields ); } } on Class has a private injection method, can't reflect class, or the concrete is invalid. */ private function reflect_class_or_callable( string $class_name, $concrete ) { if ( ! isset( $concrete ) || is_string( $concrete ) && class_exists( $concrete ) ) { $class = $concrete ?? $class_name; if ( ! method_exists( $class, Definition::INJECTION_METHOD ) ) { return null; } $method = new \ReflectionMethod( $class, Definition::INJECTION_METHOD ); $missing_modifiers = array(); if ( ! $method->isFinal() ) { $missing_modifiers[] = 'final'; } if ( ! $method->isPublic() ) { $missing_modifiers[] = 'public'; } if ( ! empty( $missing_modifiers ) ) { throw new ContainerException( "Method '" . Definition::INJECTION_METHOD . "' of class '$class' isn't '" . implode( ' ', $missing_modifiers ) . "', instances can't be created." ); } return $method; } elseif ( is_callable( $concrete ) ) { try { return new \ReflectionFunction( $concrete ); } catch ( \ReflectionException $ex ) { throw new ContainerException( "Error when reflecting callable: {$ex->getMessage()}" ); } } return null; } /** * Register a class in the container and use reflection to guess the injection method arguments. * The class is registered as shared, so `get` on the container always returns the same instance. * * WARNING: this method uses reflection, so please have performance in mind when using it. * * @param string $class_name Class name to register. * @param mixed $concrete The concrete to register. Can be a shared instance, a factory callback, or a class name. * * @return DefinitionInterface The generated container definition. * * @throws ContainerException Error when reflecting the class, or class injection method is not public, or an argument has no valid type hint. */ protected function share_with_auto_arguments( string $class_name, $concrete = null ) : DefinitionInterface { return $this->add_with_auto_arguments( $class_name, $concrete, true ); } /** * Register an entry in the container. * * @param string $id Entry id (typically a class or interface name). * @param mixed|null $concrete Concrete entity to register under that id, null for automatic creation. * @param bool|null $shared Whether to register the class as shared (`get` always returns the same instance) or not. * * @return DefinitionInterface The generated container definition. */ protected function add( string $id, $concrete = null, bool $shared = null ) : DefinitionInterface { return $this->getContainer()->add( $id, $concrete, $shared ); } /** * Register a shared entry in the container (`get` always returns the same instance). * * @param string $id Entry id (typically a class or interface name). * @param mixed|null $concrete Concrete entity to register under that id, null for automatic creation. * * @return DefinitionInterface The generated container definition. */ protected function share( string $id, $concrete = null ) : DefinitionInterface { return $this->add( $id, $concrete, true ); } }