countries(); * $localization->countriesOptions(); //format as options * $localization->states('BD'); * $localization->statesOptions('BD'); * $localization->phones(); * $localization->phonesOptions(); * $localization->timezones(); * $localization->timezonesOptions(); * $localization->continents(); * $localization->continents('EU'); * $localization->taxContinents('EU'); * $localization->continentsCountries('EU'); * $localization->continentsCountriesOptions('EU'); * $localization->continentFromCountry('BD'); * $localization->locales(); * $localization->locales('BD'); * $localization->localesOptions('BD'); * $localization->units(); * $localization->addressLocales('BD'); * $localization->postcode->isValid(‘NG51GJ', 'GB’) * $localization->postcode->formatPostcode(‘NG51GJ', 'GB’) * */ use FluentCart\App\Helpers\CartCheckoutHelper; use FluentCart\Framework\Support\Arr; use FluentCart\Framework\Support\Collection; class LocalizationManager { private static ?LocalizationManager $instance = null; static ?array $countries = null; static ?array $timeZones = null; static ?array $continents = null; static ?array $taxContinents = null; static ?array $locale = null; static ?array $addressLocales = null; static ?array $phones = null; static ?array $states = null; static ?array $units = null; private static ?PostcodeVerification $postcodeInstance = null; /** * Get the singleton instance */ public static function getInstance(): LocalizationManager { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * Get all countries */ public function countries(): array { if (is_array(self::$countries)) { return self::$countries; } self::$countries = require 'i18n/countries.php'; return self::$countries; } public function getCountryNameByCode($countryCode) { if (!$countryCode) { return ''; } return Arr::get($this->countries(), $countryCode, $countryCode); } /** * Get all timezones */ public function timezones(): array { if (is_array(self::$timeZones)) { return self::$timeZones; } self::$timeZones = require 'i18n/country_tz.php'; return self::$timeZones; } /** * Get all continents */ public function continents($countryCode = null): array { if (is_array(self::$continents)) { if ($countryCode) { return self::$continents[$countryCode] ?? []; } return self::$continents; } self::$continents = require 'i18n/continents.php'; if ($countryCode) { return self::$continents[$countryCode] ?? []; } return self::$continents; } /** * Get all continents */ public function taxContinents($countryCode = null): array { if (is_array(self::$taxContinents)) { if ($countryCode) { return self::$taxContinents[$countryCode] ?? []; } return self::$taxContinents; } self::$taxContinents = require 'i18n/eu_tax_countries.php'; if ($countryCode) { return self::$taxContinents[$countryCode] ?? []; } return self::$taxContinents; } /** * Get all phone codes */ public function phones(): array { if (is_array(self::$phones)) { return self::$phones; } self::$phones = require 'i18n/phone.php'; return self::$phones; } /** * Get all locale information */ public function locales($countryCode = null): array { if (is_array(self::$locale)) { if ($countryCode) { return self::$locale[$countryCode] ?? []; } return self::$locale; } self::$locale = require 'i18n/locale-info.php'; if ($countryCode) { return self::$locale[$countryCode] ?? []; } return self::$locale; } public function addressLocales($countryCode = null): array { if (!is_array(self::$addressLocales)) { self::$addressLocales = require 'i18n/address-locales.php'; } if ($countryCode) { return self::$addressLocales[$countryCode] ?? []; } return self::$addressLocales; } /** * Get all states/provinces */ public function states($countryCode = null): array { if (is_array(self::$states)) { if ($countryCode) { return self::$states[$countryCode] ?? []; } return self::$states; } self::$states = require 'i18n/states.php'; if ($countryCode) { return self::$states[$countryCode] ?? []; } return self::$states; } public function getStateNameByCode($stateCode, $countryCode = null) { if (empty($countryCode)) { $states = $this->states(); } else { $states = Arr::get($this->states(), $countryCode); if ($states) { $states = [ $countryCode => $states ]; } else { $states = []; } } $collection = new Collection($states); $flat = $collection->flatMap(function ($items) { return $items; }); return $flat->get($stateCode, $stateCode); } /** * Get all measurement units */ public function units(): array { if (is_array(self::$units)) { return self::$units; } self::$units = require 'i18n/units.php'; return self::$units; } /** * Convert array to options format */ private function toOptions(array $items, bool $includeCountries = false): array { $options = []; foreach ($items as $code => $item) { $option = [ 'value' => $code, 'name' => is_array($item) ? ($item['name'] ?? $code) : $item, ]; if ($includeCountries && isset($item['countries'])) { $option['options'] = $item['countries']; } $options[] = $option; } return $options; } /** * Get country ISO lists */ public function countryIsoList(): array { return $this->countries(); } /** * Get countries as options */ public function countriesOptions(): array { return $this->toOptions($this->countries()); } /** * Get phone codes as options */ public function phonesOptions(): array { return $this->toOptions($this->phones()); } /** * Get continents as options */ public function continentsOptions(): array { $continents = $this->continents(); $options = []; foreach ($continents as $code => $continent) { $options[] = [ 'value' => $code, 'name' => $continent, 'options' => $continent['countries'] ?? [] ]; } return $options; } public function continentsCountries(string $continentCode): array { $continents = $this->continents($continentCode); $contentsCountries = $continents['countries'] ?? []; $countries = []; foreach ($contentsCountries as $continent) { $countries[$continent] = $this->countries()[$continent] ?? $continent; } return $countries; } public function continentsCountriesOptions(string $continentCode): array { $continents = $this->continents($continentCode); $contentsCountries = []; $countries = []; if ($continentCode) { $contentsCountries = $continents['countries'] ?? []; } foreach ($contentsCountries as $continent) { $countries[] = [ 'value' => $continent, 'name' => $this->countries()[$continent] ?? $continent, ]; } return $countries; } public function timeZonesOptions(): array { return $this->toOptions($this->timezones(), true); } public function localesOptions(): array { $locales = $this->locales(); $options = []; foreach ($locales as $code => $localesInfo) { $options[] = [ 'value' => $localesInfo, 'name' => $code, ]; } return $options; } public function statesOptions($countryCode = null): array { if ($countryCode) { return $this->countryStatesOptions($countryCode); } return array_map(function ($states) { return [ 'options' => $states ]; }, $this->states()); } /** * Guess country code from timezone */ public function guessCountryTimezone(string $timezone): ?string { $timezoneMap = $this->timezones(); // Check for exact matches first if (isset($timezoneMap[$timezone])) { return $timezoneMap[$timezone]; } // Check for partial matches foreach ($timezoneMap as $prefix => $countryCode) { if (strpos($timezone, $prefix) === 0) { return $countryCode; } } return null; } /** * Get continent code for a country */ public function continentFromCountry(string $countryCode): string { $countryCode = trim(strtoupper($countryCode)); $continents = $this->continents(); $continents_and_codes = wp_list_pluck($continents, 'countries'); foreach ($continents_and_codes as $continentCode => $countries) { if (false !== array_search($countryCode, $countries, true)) { return $continentCode; } } return ''; } /** * Check whether a country is part of the EU VAT country list. */ public function isEuTaxCountry(string $countryCode): bool { $countryCode = trim(strtoupper($countryCode)); if ($countryCode === '') { return false; } $euCountries = Arr::get($this->taxContinents('EU'), 'countries', []); return in_array($countryCode, $euCountries, true); } /** * Get calling code for a country */ public function callingCode(string $countryCode): string { $calling_codes = $this->phones(); return $calling_codes[$countryCode] ?? ''; } /** * Get locale for a country */ public function localeForCountry(string $countryCode): string { $locales = $this->locales(); return $locales[$countryCode] ?? ''; } /** * Get states for a country */ public function countryStates(string $countryCode): array { $states = $this->states(); return Arr::get($states, $countryCode, []); } /** * Get states for a country as options */ public function countryStatesOptions(string $countryCode): array { $states = $this->countryStates($countryCode); $options = []; foreach ($states as $code => $state) { $options[] = [ 'value' => $code, 'name' => $state, ]; } return apply_filters("fluent_cart/country_state_options", $options, [ 'countryCode' => $countryCode, ]); } // Static proxy methods for backward compatibility public static function getCountries(): array { return self::getInstance()->countries(); } public static function getTimeZones(): array { return self::getInstance()->timezones(); } public static function getContinents(): array { return self::getInstance()->continents(); } public static function getPhones(): array { return self::getInstance()->phones(); } public static function getLocale($countryCode = null): array { return self::getInstance()->locales($countryCode); } public static function getStates(): array { return self::getInstance()->states(); } public static function getUnits(): array { return self::getInstance()->units(); } public static function getCountyIsoLists(): array { return self::getInstance()->countryIsoList(); } public static function guessCountryFromTimezone(string $timezone): ?string { return self::getInstance()->guessCountryTimezone($timezone); } public static function getCountryCallingCode($countryCode): string { return self::getInstance()->callingCode($countryCode); } public static function getCountryStates($countryCode): array { return self::getInstance()->countryStates($countryCode); } public static function getAddressLocales($countryCode): array { return self::getInstance()->addressLocales($countryCode); } public static function isValidCountryCode($code) { $countries = self::getInstance()->countries(); // dd($countries); } public static function getCountryInfoFromRequest($timezone, $countryCode): array { if (!$countryCode && $timezone) { $countryCode = self::guessCountryFromTimezone($timezone); } if (!$countryCode) { return [ 'country_code' => '', 'states' => [], 'address_locale' => [] ]; } $states = self::getInstance()->statesOptions($countryCode); $addressLocale = self::getInstance()->addressLocales($countryCode); return [ 'country_code' => $countryCode, 'states' => $states, 'address_locale' => $addressLocale ]; } /** * Magic method to access properties */ public function __get($name) { if ($name === 'postcode') { return $this->getPostcodeInstance(); } return null; } /** * Get postcode verification instance */ private function getPostcodeInstance(): PostcodeVerification { if (self::$postcodeInstance === null) { self::$postcodeInstance = new PostcodeVerification(); } return self::$postcodeInstance; } /** * Static method to get postcode verification instance */ public static function postcode() { return self::getInstance()->getPostcodeInstance(); } public function getZipCodeValidationRule($countryCode): array { if (!$countryCode) { return ['string', 'maxLength:192']; } $locale = $this->addressLocales($countryCode); $isZipcodeRequired = Arr::get($locale, 'postcode.required', true) !== false; if (!$isZipcodeRequired) { return ['string', 'maxLength:192']; } return ['required', 'string', 'maxLength:192']; } public function getValidationRule($data, $prefix = '', $defaultRoles = []): array { $prefix = $prefix ? $prefix . '_' : ''; $countries = $this->countries(); $country = Arr::get($data, $prefix . 'country'); $addressLocale = $this->addressLocales($country); $stateRequired = (string)(Arr::get($addressLocale, 'state.hidden', '')) !== '1'; if (Arr::get($addressLocale, 'state.required', true) === false) { $stateRequired = false; } $states = empty($country) ? [] : $this->countryStates($country); $defaultCountryRoles = Arr::wrap(Arr::get($defaultRoles, 'country', [])); $defaultStateRoles = Arr::wrap(Arr::get($defaultRoles, 'state', [])); $defaultPostcodeRoles = Arr::wrap(Arr::get($defaultRoles, 'postcode', [])); // Get current field structure based on prefix (billing/shipping) $checkoutHelper = CartCheckoutHelper::make(); $fields = $prefix === 'billing_' ? $checkoutHelper->getBillingAddressFields() : $checkoutHelper->getShippingAddressFields(); $addressSchema = Arr::get($fields, 'address_section.schema', []); $rules = []; if (isset($addressSchema['country'])) { $rules[$prefix . 'country'] = array_merge($defaultCountryRoles, [ static function ($attribute, $value) use ($countries) { $isValid = Arr::has($countries, $value); if (!$isValid) { return __('Invalid country code.', 'fluent-cart'); } } ]); } if (isset($addressSchema['state'])) { $rules[$prefix . 'state'] = array_merge($defaultStateRoles, [ 'required' => static function ($attribute, $value) use ($stateRequired) { if ($stateRequired && !$value) { return __('State is required.', 'fluent-cart'); } return null; }, 'invalid' => static function ($attribute, $value) use ($data, $country, $states) { if (!is_array($states)) { return null; } if (empty($states)) { return null; } if ($country && $value) { $isValid = Arr::has($states, $value); if (!$isValid) { return __('Invalid state code.', 'fluent-cart'); } } return null; } ]); } if (isset($cityZipSchema['postcode'])) { $rules[$prefix . 'postcode'] = array_merge( $defaultPostcodeRoles, $this->getZipCodeValidationRule($country) ); } return $rules; // return [ // $prefix . 'country' => array_merge($defaultCountryRoles, // [ // static function ($attribute, $value) use ($countries) { // $isValid = Arr::has($countries, $value); // if (!$isValid) { // return __('Invalid country code.', 'fluent-cart'); // } // } // ] // ), // $prefix . 'state' => array_merge($defaultStateRoles, // [ // static function ($attribute, $value) use ($stateRequired) { // if ($stateRequired && !$value) { // return __('State is required.', 'fluent-cart'); // } // }, // static function ($attribute, $value) use ($data, $country, $states) { // if ($country && $value) { // $isValid = Arr::has($states, $value); // if (!$isValid) { // return __('Invalid state code 2.', 'fluent-cart'); // } // } // } // ] // ), // $prefix . 'postcode' => array_merge( // $defaultPostcodeRoles, // $this->getZipCodeValidationRule($country) // ), // ]; } }