$key = \str_replace( $prefix, '', $meta_data_object->key ); if ( $all || $this->is_field( $key ) ) { $meta_data[ $key ] = $meta_data_object->value; } } } } $missing_fields = array_diff( array_keys( $this->get_fields_for_group( $group ) ), array_keys( $meta_data ) ); foreach ( $missing_fields as $missing_field ) { /** * Allow providing a default value for additional fields if no value is already set. * * @param null $value The default value for the filter, always null. * @param string $group The group of this key (shipping|billing|other). * @param WC_Data $wc_object The object to get the field value for. * * @since 8.9.0 */ $value = apply_filters( "woocommerce_get_default_value_for_{$missing_field}", null, $group, $wc_object ); if ( isset( $value ) ) { $meta_data[ $missing_field ] = $value; } } return $meta_data; } /** * Copies additional fields from an order to a customer. * * @param WC_Order $order The order to sync the fields for. * @param WC_Customer $customer The customer to sync the fields for. */ public function sync_customer_additional_fields_with_order( WC_Order $order, WC_Customer $customer ) { foreach ( $this->groups as $group ) { $order_additional_fields = $this->get_all_fields_from_object( $order, $group, true ); // Sync customer additional fields with order additional fields. foreach ( $order_additional_fields as $key => $value ) { if ( $this->is_customer_field( $key ) ) { $this->persist_field_for_customer( $key, $value, $customer, $group ); } } } } /** * Copies additional fields from a customer to an order. * * @param WC_Order $order The order to sync the fields for. * @param WC_Customer $customer The customer to sync the fields for. */ public function sync_order_additional_fields_with_customer( WC_Order $order, WC_Customer $customer ) { foreach ( $this->groups as $group ) { $customer_additional_fields = $this->get_all_fields_from_object( $customer, $group, true ); // Sync order additional fields with customer additional fields. foreach ( $customer_additional_fields as $key => $value ) { if ( $this->is_field( $key ) ) { $this->persist_field_for_order( $key, $value, $order, $group, false ); } } } } /** * From a set of fields, returns only the ones for a given location. * * @param array $fields The fields to filter. * @param string $location The location to validate the field for (address|contact|order). * @return array The filtered fields. */ public function filter_fields_for_location( array $fields, string $location ) { $location = $this->prepare_location_name( $location ); return array_filter( $fields, function ( $key ) use ( $location ) { return $this->get_field_location( $key ) === $location; }, ARRAY_FILTER_USE_KEY ); } /** * Filter fields for order confirmation. * * @param array $fields The fields to filter. * @param array $context Additional context for the filter. * @return array The filtered fields. */ public function filter_fields_for_order_confirmation( $fields, $context = array() ) { return array_filter( $fields, function ( $field ) use ( $fields, $context ) { /** * Filter fields for order confirmation (thank you page, email). * * Used in methods: * WC_Email::additional_checkout_fields * WC_Email::additional_address_fields * CheckoutFieldsFrontend::render_order_other_fields * CheckoutFieldsFrontend::render_order_address_fields * AdditionalFields::render_content * BillingAddress::render_content * ShippingAddress::render_content * * @param bool $show_field Whether the field should be shown. * @param array $field Field data. * @param array $fields All fields for better context when field should be shown or hidden based on other fields values. * @param array $context Additional context for the filter. Data depends in which method filter_fields_for_order_confirmation is called. * @param CheckoutFields $instance The CheckoutFields instance. * @since 10.1.0 */ return apply_filters( 'woocommerce_filter_fields_for_order_confirmation', ! empty( $field['show_in_order_confirmation'] ), $field, $fields, $context, $this ); } ); } /** * Get additional fields for an order. * * @param WC_Order $order Order object. * @param string $location The location to get fields for (address|contact|order). * @param string $group The group to get the field value for (shipping|billing|other). * @param string $context The context to get the field value for (edit|view). * @return array An array of fields definitions as well as their values formatted for display. */ public function get_order_additional_fields_with_values( WC_Order $order, string $location, string $group = 'other', string $context = 'edit' ) { // Because the Additional Checkout Fields API only applies to orders created with Store API, we should not // return any values unless it was created using Store API. This is mainly to prevent "empty" checkbox values // from being shown on the order confirmation page for orders placed using the shortcode. It's rare that this // will happen but not impossible. if ( 'store-api' !== $order->get_created_via() ) { return []; } $location = $this->prepare_location_name( $location ); $group = $this->prepare_group_name( $group ); $fields = $this->get_fields_for_location( $location ); $fields_with_values = []; foreach ( $fields as $field_key => $field ) { $value = $this->get_field_from_object( $field_key, $order, $group ); if ( '' === $value || null === $value ) { continue; } if ( 'view' === $context ) { $value = $this->format_additional_field_value( $value, $field ); } $field['value'] = $value; $fields_with_values[ $field_key ] = $field; } return $fields_with_values; } /** * Formats a raw field value for display based on its type definition. * * @param string $value Value to format. * @param array $field Additional field definition. * @return string */ public function format_additional_field_value( $value, $field ) { if ( 'checkbox' === $field['type'] ) { $value = $value ? __( 'Yes', 'woocommerce' ) : __( 'No', 'woocommerce' ); } if ( 'select' === $field['type'] ) { $options = array_column( $field['options'], 'label', 'value' ); $value = isset( $options[ $value ] ) ? $options[ $value ] : $value; } return $value; } /** * Prepares a group name for use. * * @param string $group The group name to prepare. * @return string The prepared group name. */ private function prepare_group_name( $group ) { if ( ! in_array( $group, $this->groups, true ) ) { $group = 'other'; } return $group; } /** * Prepares a location name for use. * * @param string $location The location name to prepare. * @return string The prepared location name. */ private function prepare_location_name( $location ) { if ( 'additional' === $location ) { $location = 'order'; } return $location; } /** * Returns a group meta prefix based on its name. * * @param string $group_name The group name (billing|shipping|other). * @return string The group meta prefix. */ public static function get_group_key( $group_name ) { if ( 'additional' === $group_name ) { wc_deprecated_argument( 'group_name', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' ); $group_name = 'other'; } if ( 'billing' === $group_name ) { return self::BILLING_FIELDS_PREFIX; } if ( 'shipping' === $group_name ) { return self::SHIPPING_FIELDS_PREFIX; } return self::OTHER_FIELDS_PREFIX; } /** * Returns a group name based on passed group key. * * @param string $group_key The group name (_wc_billing|_wc_shipping|_wc_other). * @return string The group meta prefix. */ public static function get_group_name( $group_key ) { if ( '_wc_additional' === $group_key ) { wc_deprecated_argument( 'group_key', '8.9.0', 'The "_wc_additional" group key is deprecated. Use "_wc_other" instead.' ); $group_key = '_wc_other'; } if ( 0 === \strpos( self::BILLING_FIELDS_PREFIX, $group_key ) ) { return 'billing'; } if ( 0 === \strpos( self::SHIPPING_FIELDS_PREFIX, $group_key ) ) { return 'shipping'; } return 'other'; } }