[ label, mode ]. */ class CoreFields { const OPTION = 'checkoutline_core'; const OWN = array( 'company', 'address_2', 'phone' ); public function register(): void { add_filter( 'woocommerce_get_country_locale', array( $this, 'locale' ), 99 ); add_filter( 'woocommerce_get_country_locale_default', array( $this, 'apply' ), 99 ); add_filter( 'woocommerce_billing_fields', array( $this, 'billing' ), 99 ); add_filter( 'woocommerce_shipping_fields', array( $this, 'shipping' ), 99 ); } /** The fields that can change, with WooCommerce's labels. */ public static function fields(): array { return array( 'first_name' => __( 'First name', 'checkoutline' ), 'last_name' => __( 'Last name', 'checkoutline' ), 'company' => __( 'Company', 'checkoutline' ), 'address_1' => __( 'Address', 'checkoutline' ), 'address_2' => __( 'Address line 2', 'checkoutline' ), 'city' => __( 'City', 'checkoutline' ), 'state' => __( 'State / County', 'checkoutline' ), 'postcode' => __( 'Postcode / ZIP', 'checkoutline' ), 'phone' => __( 'Phone', 'checkoutline' ), ); } /** Modes: '' (as WooCommerce has it, by country), required, optional, hidden. */ public static function modes( string $field ): array { $modes = array( 'optional' => __( 'Optional', 'checkoutline' ), 'required' => __( 'Required', 'checkoutline' ), 'hidden' => __( 'Hidden', 'checkoutline' ), ); return in_array( $field, self::OWN, true ) ? $modes : array( '' => __( 'As WooCommerce has it', 'checkoutline' ) ) + $modes; } /** @return array */ public static function all(): array { $saved = (array) get_option( self::OPTION, array() ); $out = array(); foreach ( array_keys( self::fields() ) as $field ) { $row = (array) ( $saved[ $field ] ?? array() ); $mode = (string) ( $row['mode'] ?? '' ); $out[ $field ] = array( 'label' => sanitize_text_field( (string) ( $row['label'] ?? '' ) ), 'mode' => in_array( $field, self::OWN, true ) ? self::own_mode( $field ) : ( array_key_exists( $mode, self::modes( $field ) ) ? $mode : '' ), ); } return $out; } /** WooCommerce's setting for company, address line 2 or phone. */ public static function own_mode( string $field ): string { $defaults = array( // WooCommerce's own (CartCheckoutUtils). 'company' => 'hidden', 'address_2' => 'optional', 'phone' => 'optional', ); $value = (string) get_option( 'woocommerce_checkout_' . $field . '_field', $defaults[ $field ] ); return in_array( $value, array( 'optional', 'required', 'hidden' ), true ) ? $value : $defaults[ $field ]; } /** @param array $rows */ public static function save( array $rows ): void { $clean = array(); foreach ( array_keys( self::fields() ) as $field ) { $row = (array) ( $rows[ $field ] ?? array() ); $label = sanitize_text_field( (string) ( $row['label'] ?? '' ) ); $mode = (string) ( $row['mode'] ?? '' ); if ( in_array( $field, self::OWN, true ) ) { if ( array_key_exists( $mode, self::modes( $field ) ) ) { update_option( 'woocommerce_checkout_' . $field . '_field', $mode ); } $mode = ''; } elseif ( ! array_key_exists( $mode, self::modes( $field ) ) ) { $mode = ''; } if ( '' !== $label || '' !== $mode ) { $clean[ $field ] = array( 'label' => $label, 'mode' => $mode, ); } } update_option( self::OPTION, $clean ); } /** * Applies the changes to one locale (a country's, or the default). * * @param array $locale Field => properties. * @param bool $partial A country's locale (only what differs from the default). */ public function apply( $locale, bool $partial = false ): array { $locale = (array) $locale; foreach ( self::all() as $field => $row ) { if ( '' === $row['label'] && '' === $row['mode'] ) { continue; } if ( $partial && ! isset( $locale[ $field ] ) && '' === $row['label'] && 'hidden' !== $row['mode'] ) { continue; // A country without its own rule for this field follows the default. } $props = (array) ( $locale[ $field ] ?? array() ); if ( '' !== $row['label'] ) { $props['label'] = $row['label']; } if ( 'hidden' === $row['mode'] ) { $props['hidden'] = true; $props['required'] = false; } elseif ( '' !== $row['mode'] && empty( $props['hidden'] ) ) { $props['required'] = 'required' === $row['mode']; } $locale[ $field ] = $props; } return $locale; } /** @param array $locales Country => locale. */ public function locale( $locales ): array { $locales = (array) $locales; foreach ( $locales as $country => $locale ) { $locales[ $country ] = $this->apply( $locale, true ); } return $locales; } private static function address_fields( array $fields, string $group ): array { foreach ( self::all() as $field => $row ) { $key = $group . '_' . $field; if ( ! isset( $fields[ $key ] ) ) { continue; } if ( 'hidden' === $row['mode'] ) { unset( $fields[ $key ] ); continue; } if ( '' !== $row['label'] ) { $fields[ $key ]['label'] = $row['label']; } if ( '' !== $row['mode'] && empty( $fields[ $key ]['hidden'] ) ) { $fields[ $key ]['required'] = 'required' === $row['mode']; } } return $fields; } /** Classic checkout and My Account's billing address. @param array $fields */ public function billing( $fields ): array { return self::address_fields( (array) $fields, 'billing' ); } /** @param array $fields */ public function shipping( $fields ): array { return self::address_fields( (array) $fields, 'shipping' ); } }