Settings > Shipping > Maple Parcel), * read with their types and defaults. * * They are the shipping method's own settings option, so WooCommerce's * settings API saves them; this class reads them anywhere else (checkout, * the connection test, the health notice) without building a method object. * Per-zone settings stay on the method instance. */ declare(strict_types=1); namespace BlueBrainMedia\MapleParcel\Settings; use BlueBrainMedia\MapleParcel\Api\Credentials; use BlueBrainMedia\MapleParcel\Shipping\Destination; use BlueBrainMedia\MapleParcel\Shipping\ParcelBuilder; defined( 'ABSPATH' ) || exit; final class Settings { public const OPTION = 'woocommerce_bbmmp_canada_post_settings'; public const QUOTE_COMMERCIAL = 'commercial'; public const QUOTE_COUNTER = 'counter'; /** @param array $values */ public function __construct( private readonly array $values ) { } public static function load(): self { $saved = get_option( self::OPTION, array() ); return new self( array_merge( Fields::globalDefaults(), is_array( $saved ) ? $saved : array() ) ); } public function environment(): string { return Credentials::PRODUCTION === $this->string( 'environment' ) ? Credentials::PRODUCTION : Credentials::TEST; } public function credentials(): Credentials { $env = $this->environment(); return new Credentials( $env, $this->string( $env . '_client_id' ), SecretBox::open( $this->string( $env . '_client_secret' ) ) ?? '' ); } /** A secret is saved for the chosen environment but cannot be decrypted (the site's salts changed). */ public function secretUnreadable(): bool { return null === SecretBox::open( $this->string( $this->environment() . '_client_secret' ) ); } public function customerNumber(): string { return $this->string( 'customer_number' ); } /** Blank for Solutions for Small Business members, who must not send one. */ public function contractId(): string { return $this->string( 'contract_id' ); } /** Commercial prices need a customer number; without one, counter prices are all there is. */ public function quoteType(): string { return self::QUOTE_COUNTER === $this->string( 'quote_type' ) || '' === $this->customerNumber() ? self::QUOTE_COUNTER : self::QUOTE_COMMERCIAL; } /** "V2S6X1", or '' when not set or not valid. */ public function originPostalCode(): string { return Destination::canadianPostcode( $this->string( 'origin_postal_code' ) ) ?? ''; } public function taxMode(): string { return 'due' === $this->string( 'tax_mode' ) ? 'due' : 'base'; } public function packing(): string { return ParcelBuilder::PER_ITEM === $this->string( 'packing' ) ? ParcelBuilder::PER_ITEM : ParcelBuilder::WHOLE_CART; } /** Kilograms, converted from the store's weight unit. */ public function defaultWeight(): float { $weight = (float) wc_get_weight( $this->number( 'default_weight' ), 'kg' ); return $weight > 0 ? $weight : 0.5; } /** @return array{0: float, 1: float, 2: float} Centimetres, converted from the store's dimension unit. */ public function defaultSides(): array { $sides = array(); foreach ( array( 'default_length', 'default_width', 'default_height' ) as $i => $key ) { $cm = (float) wc_get_dimension( $this->number( $key ), 'cm' ); $sides[] = $cm > 0 ? $cm : array( 20.0, 15.0, 10.0 )[ $i ]; } return array( $sides[0], $sides[1], $sides[2] ); } public function cacheMinutes(): int { return max( 0, (int) $this->number( 'cache_minutes' ) ); } public function debug(): bool { return 'yes' === $this->string( 'debug' ); } /** Canada Post answers service names and errors in English or French. */ public function language(): string { return str_starts_with( determine_locale(), 'fr' ) ? 'fr-CA' : 'en-CA'; } /** A saved value as stored, for settings a filter added (bbmmp_global_fields). */ public function raw( string $key ): mixed { return $this->values[ $key ] ?? null; } private function string( string $key ): string { $value = $this->values[ $key ] ?? ''; return is_scalar( $value ) ? trim( (string) $value ) : ''; } private function number( string $key ): float { $value = $this->string( $key ); return is_numeric( $value ) ? (float) $value : 0.0; } }