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,
);
}
}