array( 'Österreichische Post', 'DPD', 'GLS', 'DHL', 'Hermes' ), 'BE' => array( 'bpost', 'PostNL', 'DPD', 'GLS', 'DHL' ), 'DE' => array( 'DHL', 'Hermes', 'DPD', 'GLS', 'UPS' ), 'ES' => array( 'Correos', 'SEUR', 'MRW', 'GLS', 'CTT Express' ), 'FR' => array( 'Colissimo', 'Chronopost', 'Mondial Relay', 'DPD', 'GLS' ), 'GB' => array( 'Royal Mail', 'Evri', 'DPD', 'Parcelforce', 'UPS' ), 'IT' => array( 'Poste Italiane', 'BRT', 'GLS', 'SDA', 'DHL' ), 'NL' => array( 'PostNL', 'DHL', 'DPD', 'GLS', 'UPS' ), 'PL' => array( 'InPost', 'Poczta Polska', 'DPD', 'DHL', 'GLS' ), 'PT' => array( 'CTT', 'DPD', 'GLS', 'SEUR' ), 'US' => array( 'USPS', 'UPS', 'FedEx', 'DHL Express' ), ); public static function hooks() : void { add_filter( 'woocommerce_get_country_locale', array(__CLASS__, 'turkish_address') ); add_filter( 'woocommerce_form_field_country', array(__CLASS__, 'single_country'), 20, 2 ); } /** * A store that sells (billing) or ships (shipping) to one country only: WooCommerce prints that country as plain text * under a "Country / Region" label with nothing to choose. The row is hidden (Almira tour, 27 Oct 2026); its hidden * input still sends the country, so WooCommerce's address script and checks keep working. */ public static function single_country( $field, $key = '' ) { if ( !is_string( $field ) || !function_exists( 'WC' ) || !WC()->countries || !apply_filters( 'storeruler_hide_single_country', true ) ) { return $field; } $countries = ( $key === 'shipping_country' ? WC()->countries->get_shipping_countries() : (( $key === 'billing_country' ? WC()->countries->get_allowed_countries() : array() )) ); if ( count( (array) $countries ) !== 1 ) { return $field; } return (string) preg_replace( '/^\\s* false, ) ); return $locale; } /** Whether the store sends parcels to Turkey: its own country, or a shipping zone with a Turkish location. */ public static function ships_to_turkey() : bool { if ( self::country() === 'TR' ) { return true; } if ( !class_exists( '\\WC_Shipping_Zones' ) ) { return false; } foreach ( \WC_Shipping_Zones::get_zones() as $zone ) { foreach ( (array) ($zone['zone_locations'] ?? array()) as $location ) { $code = strtoupper( (string) ($location->code ?? '') ); if ( ($location->type ?? '') === 'country' && $code === 'TR' || ($location->type ?? '') === 'state' && str_starts_with( $code, 'TR:' ) ) { return true; } } } return false; } /** Whether the checkout asks every shopper for a phone number (the block checkout's default is optional, the classic one's required). */ public static function checkout_phone_required() : bool { $page = ( function_exists( 'wc_get_page_id' ) ? get_post( wc_get_page_id( 'checkout' ) ) : null ); return get_option( 'woocommerce_checkout_phone_field', ( $page && has_block( 'woocommerce/checkout', $page ) ? 'optional' : 'required' ) ) === 'required'; } /** The store's country (WooCommerce's base country), upper case. */ private static function country( ?string $country = null ) : string { $country = $country ?? (( function_exists( 'WC' ) && WC() && WC()->countries ? (string) WC()->countries->get_base_country() : '' )); return strtoupper( trim( $country ) ); } /** A country's preset carriers, key => name ('preset_' keys; none for a country without a list). */ public static function presets( ?string $country = null ) : array { $out = array(); foreach ( self::PRESETS[self::country( $country )] ?? array() as $name ) { $out['preset_' . substr( hash( 'sha256', mb_strtolower( $name ) ), 0, 12 )] = $name; } return $out; } public static function carriers( ?string $country = null ) : array { $configured = array(); $known = array(); foreach ( CarrierProfiles::names() as $name ) { $normalized = mb_strtolower( $name ); if ( isset( $known[$normalized] ) ) { continue; } $known[$normalized] = true; $configured[CarrierProfiles::key( $name )] = array( 'label' => $name, 'url' => '', ); } $default_name = ''; // The label settings' carrier (paid build). if ( $default_name !== '' && !isset( $known[mb_strtolower( $default_name )] ) ) { $configured['store_default'] = array( 'label' => $default_name, 'url' => '', ); $known[mb_strtolower( $default_name )] = true; } foreach ( self::presets( $country ) as $key => $name ) { if ( !isset( $known[mb_strtolower( $name )] ) ) { $configured[$key] = array( 'label' => $name, 'url' => '', ); } } return array_merge( $configured, array( 'yurtici' => array( 'label' => 'Yurtiçi Kargo', 'url' => 'https://www.yurticikargo.com/tr/online-servisler/gonderi-sorgula', ), 'aras' => array( 'label' => 'Aras Kargo', 'url' => 'https://www.araskargo.com.tr/', ), 'mng' => array( 'label' => 'MNG Kargo', 'url' => 'https://www.mngkargo.com.tr/', ), 'surat' => array( 'label' => 'Sürat Kargo', 'url' => 'https://www.suratkargo.com.tr/KargoTakip/', ), 'ptt' => array( 'label' => 'PTT Kargo', 'url' => 'https://gonderitakip.ptt.gov.tr/', ), 'hepsijet' => array( 'label' => 'HepsiJET', 'url' => 'https://www.hepsijet.com/gonderi-takibi', ), 'kolaygelsin' => array( 'label' => 'Kolay Gelsin', 'url' => 'https://www.kolaygelsin.com/gonderi-takip', ), 'other' => array( 'label' => I18n::t( 'Other carrier / courier' ), 'url' => '', ), ) ); } public static function public_carriers( ?string $country = null ) : array { $country = self::country( $country ); $result = array(); $seen = array(); foreach ( self::carriers( $country ) as $key => $carrier ) { if ( $key === 'other' || $country !== 'TR' && $key !== 'store_default' && !str_starts_with( $key, 'favorite_' ) && !str_starts_with( $key, 'preset_' ) ) { continue; } $normalized = mb_strtolower( $carrier['label'] ); if ( isset( $seen[$normalized] ) ) { continue; } $seen[$normalized] = true; $result[$key] = $carrier['label']; } $result['other'] = I18n::t( 'Other carrier / courier' ); return $result; } /** * The carrier the shipment screens preselect: the one this person used last while it is still offered (ORD-8, Operations * remembers it), otherwise the first choice (the merchant's own names come first). */ public static function default_carrier( ?string $country = null ) : string { $choices = self::public_carriers( $country ); $last = ( get_current_user_id() ? get_user_meta( get_current_user_id(), Operations::LAST_CARRIER, true ) : '' ); $key = ( is_array( $last ) ? (string) ($last['key'] ?? '') : '' ); return ( $key !== '' && $key !== 'other' && isset( $choices[$key] ) ? $key : (string) array_key_first( $choices ) ); } public static function details( array $data ) : array { $tracking = Store::text( $data, 'tracking' ); if ( strlen( $tracking ) > 100 ) { throw new \InvalidArgumentException(I18n::t( 'The tracking number is too long.' )); } $carriers = self::carriers(); $key = Store::text( $data, 'carrier' ); // Older clients only sent a tracking number. Keep those records valid while // the new merchant UI requires an explicit carrier selection. if ( '' === $key ) { $key = 'other'; } if ( !isset( $carriers[$key] ) ) { throw new \InvalidArgumentException(I18n::t( 'Invalid carrier selection.' )); } $label = $carriers[$key]['label']; if ( 'other' === $key ) { $custom = Store::text( $data, 'carrier_other' ); if ( '' !== $custom ) { $label = mb_substr( $custom, 0, 80 ); } } $expected = Store::text( $data, 'expected_delivery' ); if ( '' !== $expected ) { $parsed = \DateTimeImmutable::createFromFormat( '!Y-m-d', $expected ); if ( !$parsed || $parsed->format( 'Y-m-d' ) !== $expected ) { throw new \InvalidArgumentException(I18n::t( 'Invalid estimated delivery date.' )); } } return array( 'id' => wp_generate_uuid4(), 'carrier' => $key, 'carrier_label' => $label, 'tracking' => $tracking, 'tracking_url' => $carriers[$key]['url'], 'expected_delivery' => $expected, ); } }