PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / app / Services / Localization / LocalizationManager.php

LocalizationManager.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Services/Localization/LocalizationManager.php

697 lines 19.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Localization;
4
5 /*
6 * Use cases for LocalizationManager
7 * $localization = App::getInstance('localization');
8 * $countries = $localization->countries();
9 * $localization->countriesOptions(); //format as options
10 * $localization->states('BD');
11 * $localization->statesOptions('BD');
12 * $localization->phones();
13 * $localization->phonesOptions();
14 * $localization->timezones();
15 * $localization->timezonesOptions();
16 * $localization->continents();
17 * $localization->continents('EU');
18 * $localization->taxContinents('EU');
19 * $localization->continentsCountries('EU');
20 * $localization->continentsCountriesOptions('EU');
21 * $localization->continentFromCountry('BD');
22 * $localization->locales();
23 * $localization->locales('BD');
24 * $localization->localesOptions('BD');
25 * $localization->units();
26 * $localization->addressLocales('BD');
27 * $localization->postcode->isValid(‘NG51GJ', 'GB’)
28 * $localization->postcode->formatPostcode(‘NG51GJ', 'GB’)
29 *
30 */
31
32 use FluentCart\App\Helpers\CartCheckoutHelper;
33 use FluentCart\Framework\Support\Arr;
34 use FluentCart\Framework\Support\Collection;
35
36 class LocalizationManager
37 {
38 private static ?LocalizationManager $instance = null;
39
40 static ?array $countries = null;
41 static ?array $timeZones = null;
42 static ?array $continents = null;
43 static ?array $taxContinents = null;
44 static ?array $locale = null;
45 static ?array $addressLocales = null;
46 static ?array $phones = null;
47 static ?array $states = null;
48 static ?array $units = null;
49
50 private static ?PostcodeVerification $postcodeInstance = null;
51
52 /**
53 * Get the singleton instance
54 */
55 public static function getInstance(): LocalizationManager
56 {
57 if (self::$instance === null) {
58 self::$instance = new self();
59 }
60 return self::$instance;
61 }
62
63 /**
64 * Get all countries
65 */
66 public function countries(): array
67 {
68 if (is_array(self::$countries)) {
69 return self::$countries;
70 }
71 self::$countries = require 'i18n/countries.php';
72 return self::$countries;
73 }
74
75 public function getCountryNameByCode($countryCode)
76 {
77
78 if (!$countryCode) {
79 return '';
80 }
81
82 return Arr::get($this->countries(), $countryCode, $countryCode);
83 }
84
85 /**
86 * Get all timezones
87 */
88 public function timezones(): array
89 {
90 if (is_array(self::$timeZones)) {
91 return self::$timeZones;
92 }
93 self::$timeZones = require 'i18n/country_tz.php';
94 return self::$timeZones;
95 }
96
97 /**
98 * Get all continents
99 */
100 public function continents($countryCode = null): array
101 {
102 if (is_array(self::$continents)) {
103 if ($countryCode) {
104 return self::$continents[$countryCode] ?? [];
105 }
106 return self::$continents;
107 }
108 self::$continents = require 'i18n/continents.php';
109 if ($countryCode) {
110 return self::$continents[$countryCode] ?? [];
111 }
112 return self::$continents;
113 }
114
115 /**
116 * Get all continents
117 */
118 public function taxContinents($countryCode = null): array
119 {
120 if (is_array(self::$taxContinents)) {
121 if ($countryCode) {
122 return self::$taxContinents[$countryCode] ?? [];
123 }
124 return self::$taxContinents;
125 }
126 self::$taxContinents = require 'i18n/eu_tax_countries.php';
127 if ($countryCode) {
128 return self::$taxContinents[$countryCode] ?? [];
129 }
130 return self::$taxContinents;
131 }
132
133 /**
134 * Get all phone codes
135 */
136 public function phones(): array
137 {
138 if (is_array(self::$phones)) {
139 return self::$phones;
140 }
141 self::$phones = require 'i18n/phone.php';
142 return self::$phones;
143 }
144
145 /**
146 * Get all locale information
147 */
148 public function locales($countryCode = null): array
149 {
150 if (is_array(self::$locale)) {
151 if ($countryCode) {
152 return self::$locale[$countryCode] ?? [];
153 }
154 return self::$locale;
155 }
156 self::$locale = require 'i18n/locale-info.php';
157 if ($countryCode) {
158 return self::$locale[$countryCode] ?? [];
159 }
160 return self::$locale;
161 }
162
163 public function addressLocales($countryCode = null): array
164 {
165 if (!is_array(self::$addressLocales)) {
166 self::$addressLocales = require 'i18n/address-locales.php';
167 }
168
169 if ($countryCode) {
170 return self::$addressLocales[$countryCode] ?? [];
171 }
172 return self::$addressLocales;
173 }
174
175 /**
176 * Get all states/provinces
177 */
178 public function states($countryCode = null): array
179 {
180 if (is_array(self::$states)) {
181 if ($countryCode) {
182 return self::$states[$countryCode] ?? [];
183 }
184 return self::$states;
185 }
186 self::$states = require 'i18n/states.php';
187 if ($countryCode) {
188 return self::$states[$countryCode] ?? [];
189 }
190 return self::$states;
191 }
192
193 public function getStateNameByCode($stateCode, $countryCode = null)
194 {
195 if (empty($countryCode)) {
196 $states = $this->states();
197 } else {
198 $states = Arr::get($this->states(), $countryCode);
199 if ($states) {
200 $states = [
201 $countryCode => $states
202 ];
203 } else {
204 $states = [];
205 }
206 }
207
208 $collection = new Collection($states);
209 $flat = $collection->flatMap(function ($items) {
210 return $items;
211 });
212 return $flat->get($stateCode, $stateCode);
213 }
214
215 /**
216 * Get all measurement units
217 */
218 public function units(): array
219 {
220 if (is_array(self::$units)) {
221 return self::$units;
222 }
223 self::$units = require 'i18n/units.php';
224 return self::$units;
225 }
226
227 /**
228 * Convert array to options format
229 */
230 private function toOptions(array $items, bool $includeCountries = false): array
231 {
232 $options = [];
233 foreach ($items as $code => $item) {
234 $option = [
235 'value' => $code,
236 'name' => is_array($item) ? ($item['name'] ?? $code) : $item,
237 ];
238
239 if ($includeCountries && isset($item['countries'])) {
240 $option['options'] = $item['countries'];
241 }
242
243 $options[] = $option;
244 }
245 return $options;
246 }
247
248 /**
249 * Get country ISO lists
250 */
251 public function countryIsoList(): array
252 {
253 return $this->countries();
254 }
255
256 /**
257 * Get countries as options
258 */
259 public function countriesOptions(): array
260 {
261 return $this->toOptions($this->countries());
262 }
263
264 /**
265 * Get phone codes as options
266 */
267 public function phonesOptions(): array
268 {
269 return $this->toOptions($this->phones());
270 }
271
272 /**
273 * Get continents as options
274 */
275 public function continentsOptions(): array
276 {
277 $continents = $this->continents();
278 $options = [];
279 foreach ($continents as $code => $continent) {
280 $options[] = [
281 'value' => $code,
282 'name' => $continent,
283 'options' => $continent['countries'] ?? []
284 ];
285 }
286 return $options;
287 }
288
289 public function continentsCountries(string $continentCode): array
290 {
291 $continents = $this->continents($continentCode);
292 $contentsCountries = $continents['countries'] ?? [];
293 $countries = [];
294 foreach ($contentsCountries as $continent) {
295 $countries[$continent] = $this->countries()[$continent] ?? $continent;
296 }
297 return $countries;
298 }
299
300 public function continentsCountriesOptions(string $continentCode): array
301 {
302 $continents = $this->continents($continentCode);
303 $contentsCountries = [];
304 $countries = [];
305 if ($continentCode) {
306 $contentsCountries = $continents['countries'] ?? [];
307 }
308 foreach ($contentsCountries as $continent) {
309 $countries[] = [
310 'value' => $continent,
311 'name' => $this->countries()[$continent] ?? $continent,
312 ];
313 }
314 return $countries;
315 }
316
317 public function timeZonesOptions(): array
318 {
319 return $this->toOptions($this->timezones(), true);
320 }
321
322 public function localesOptions(): array
323 {
324 $locales = $this->locales();
325 $options = [];
326 foreach ($locales as $code => $localesInfo) {
327 $options[] = [
328 'value' => $localesInfo,
329 'name' => $code,
330 ];
331 }
332 return $options;
333 }
334
335 public function statesOptions($countryCode = null): array
336 {
337 if ($countryCode) {
338 return $this->countryStatesOptions($countryCode);
339 }
340 return array_map(function ($states) {
341 return [
342 'options' => $states
343 ];
344 }, $this->states());
345 }
346
347 /**
348 * Guess country code from timezone
349 */
350 public function guessCountryTimezone(string $timezone): ?string
351 {
352 $timezoneMap = $this->timezones();
353
354 // Check for exact matches first
355 if (isset($timezoneMap[$timezone])) {
356 return $timezoneMap[$timezone];
357 }
358
359 // Check for partial matches
360 foreach ($timezoneMap as $prefix => $countryCode) {
361 if (strpos($timezone, $prefix) === 0) {
362 return $countryCode;
363 }
364 }
365
366 return null;
367 }
368
369 /**
370 * Get continent code for a country
371 */
372 public function continentFromCountry(string $countryCode): string
373 {
374 $countryCode = trim(strtoupper($countryCode));
375 $continents = $this->continents();
376 $continents_and_codes = wp_list_pluck($continents, 'countries');
377 foreach ($continents_and_codes as $continentCode => $countries) {
378 if (false !== array_search($countryCode, $countries, true)) {
379 return $continentCode;
380 }
381 }
382 return '';
383 }
384
385 /**
386 * Check whether a country is part of the EU VAT country list.
387 */
388 public function isEuTaxCountry(string $countryCode): bool
389 {
390 $countryCode = trim(strtoupper($countryCode));
391
392 if ($countryCode === '') {
393 return false;
394 }
395
396 $euCountries = Arr::get($this->taxContinents('EU'), 'countries', []);
397
398 return in_array($countryCode, $euCountries, true);
399 }
400
401 /**
402 * Get calling code for a country
403 */
404 public function callingCode(string $countryCode): string
405 {
406 $calling_codes = $this->phones();
407 return $calling_codes[$countryCode] ?? '';
408 }
409
410 /**
411 * Get locale for a country
412 */
413 public function localeForCountry(string $countryCode): string
414 {
415 $locales = $this->locales();
416 return $locales[$countryCode] ?? '';
417 }
418
419 /**
420 * Get states for a country
421 */
422 public function countryStates(string $countryCode): array
423 {
424 $states = $this->states();
425 return Arr::get($states, $countryCode, []);
426 }
427
428 /**
429 * Get states for a country as options
430 */
431 public function countryStatesOptions(string $countryCode): array
432 {
433 $states = $this->countryStates($countryCode);
434 $options = [];
435 foreach ($states as $code => $state) {
436 $options[] = [
437 'value' => $code,
438 'name' => $state,
439 ];
440 }
441 return apply_filters("fluent_cart/country_state_options", $options, [
442 'countryCode' => $countryCode,
443 ]);
444 }
445
446 // Static proxy methods for backward compatibility
447 public static function getCountries(): array
448 {
449 return self::getInstance()->countries();
450 }
451
452 public static function getTimeZones(): array
453 {
454 return self::getInstance()->timezones();
455 }
456
457 public static function getContinents(): array
458 {
459 return self::getInstance()->continents();
460 }
461
462 public static function getPhones(): array
463 {
464 return self::getInstance()->phones();
465 }
466
467 public static function getLocale($countryCode = null): array
468 {
469 return self::getInstance()->locales($countryCode);
470 }
471
472 public static function getStates(): array
473 {
474 return self::getInstance()->states();
475 }
476
477 public static function getUnits(): array
478 {
479 return self::getInstance()->units();
480 }
481
482 public static function getCountyIsoLists(): array
483 {
484 return self::getInstance()->countryIsoList();
485 }
486
487 public static function guessCountryFromTimezone(string $timezone): ?string
488 {
489 return self::getInstance()->guessCountryTimezone($timezone);
490 }
491
492 public static function getCountryCallingCode($countryCode): string
493 {
494 return self::getInstance()->callingCode($countryCode);
495 }
496
497 public static function getCountryStates($countryCode): array
498 {
499 return self::getInstance()->countryStates($countryCode);
500 }
501
502 public static function getAddressLocales($countryCode): array
503 {
504 return self::getInstance()->addressLocales($countryCode);
505 }
506
507 public static function isValidCountryCode($code)
508 {
509 $countries = self::getInstance()->countries();
510 // dd($countries);
511 }
512
513 public static function getCountryInfoFromRequest($timezone, $countryCode): array
514 {
515 if (!$countryCode && $timezone) {
516 $countryCode = self::guessCountryFromTimezone($timezone);
517 }
518
519 if (!$countryCode) {
520 return [
521 'country_code' => '',
522 'states' => [],
523 'address_locale' => []
524 ];
525 }
526
527 $states = self::getInstance()->statesOptions($countryCode);
528 $addressLocale = self::getInstance()->addressLocales($countryCode);
529
530 return [
531 'country_code' => $countryCode,
532 'states' => $states,
533 'address_locale' => $addressLocale
534 ];
535 }
536
537 /**
538 * Magic method to access properties
539 */
540 public function __get($name)
541 {
542 if ($name === 'postcode') {
543 return $this->getPostcodeInstance();
544 }
545
546 return null;
547 }
548
549 /**
550 * Get postcode verification instance
551 */
552 private function getPostcodeInstance(): PostcodeVerification
553 {
554 if (self::$postcodeInstance === null) {
555 self::$postcodeInstance = new PostcodeVerification();
556 }
557 return self::$postcodeInstance;
558 }
559
560 /**
561 * Static method to get postcode verification instance
562 */
563 public static function postcode()
564 {
565 return self::getInstance()->getPostcodeInstance();
566 }
567
568 public function getZipCodeValidationRule($countryCode): array
569 {
570
571 if (!$countryCode) {
572 return ['string', 'maxLength:192'];
573 }
574 $locale = $this->addressLocales($countryCode);
575
576 $isZipcodeRequired = Arr::get($locale, 'postcode.required', true) !== false;
577 if (!$isZipcodeRequired) {
578 return ['string', 'maxLength:192'];
579 }
580 return ['required', 'string', 'maxLength:192'];
581 }
582
583 public function getValidationRule($data, $prefix = '', $defaultRoles = []): array
584 {
585 $prefix = $prefix ? $prefix . '_' : '';
586 $countries = $this->countries();
587 $country = Arr::get($data, $prefix . 'country');
588 $addressLocale = $this->addressLocales($country);
589 $stateRequired = (string)(Arr::get($addressLocale, 'state.hidden', '')) !== '1';
590
591 if (Arr::get($addressLocale, 'state.required', true) === false) {
592 $stateRequired = false;
593 }
594
595 $states = empty($country) ? [] : $this->countryStates($country);
596
597 $defaultCountryRoles = Arr::wrap(Arr::get($defaultRoles, 'country', []));
598 $defaultStateRoles = Arr::wrap(Arr::get($defaultRoles, 'state', []));
599 $defaultPostcodeRoles = Arr::wrap(Arr::get($defaultRoles, 'postcode', []));
600
601 // Get current field structure based on prefix (billing/shipping)
602 $checkoutHelper = CartCheckoutHelper::make();
603 $fields = $prefix === 'billing_'
604 ? $checkoutHelper->getBillingAddressFields()
605 : $checkoutHelper->getShippingAddressFields();
606
607 $addressSchema = Arr::get($fields, 'address_section.schema', []);
608
609 $rules = [];
610
611 if (isset($addressSchema['country'])) {
612 $rules[$prefix . 'country'] = array_merge($defaultCountryRoles, [
613 static function ($attribute, $value) use ($countries) {
614 $isValid = Arr::has($countries, $value);
615 if (!$isValid) {
616 return __('Invalid country code.', 'fluent-cart');
617 }
618 }
619 ]);
620 }
621
622 if (isset($addressSchema['state'])) {
623 $rules[$prefix . 'state'] = array_merge($defaultStateRoles, [
624 'required' => static function ($attribute, $value) use ($stateRequired) {
625
626 if ($stateRequired && !$value) {
627 return __('State is required.', 'fluent-cart');
628 }
629 return null;
630 },
631 'invalid' => static function ($attribute, $value) use ($data, $country, $states) {
632
633 if (!is_array($states)) {
634 return null;
635 }
636
637 if (empty($states)) {
638 return null;
639 }
640 if ($country && $value) {
641 $isValid = Arr::has($states, $value);
642 if (!$isValid) {
643 return __('Invalid state code.', 'fluent-cart');
644 }
645 }
646
647 return null;
648 }
649 ]);
650 }
651
652 if (isset($cityZipSchema['postcode'])) {
653 $rules[$prefix . 'postcode'] = array_merge(
654 $defaultPostcodeRoles,
655 $this->getZipCodeValidationRule($country)
656 );
657 }
658
659 return $rules;
660
661 // return [
662 // $prefix . 'country' => array_merge($defaultCountryRoles,
663 // [
664 // static function ($attribute, $value) use ($countries) {
665 // $isValid = Arr::has($countries, $value);
666 // if (!$isValid) {
667 // return __('Invalid country code.', 'fluent-cart');
668 // }
669 // }
670 // ]
671 // ),
672 // $prefix . 'state' => array_merge($defaultStateRoles,
673 // [
674 // static function ($attribute, $value) use ($stateRequired) {
675 // if ($stateRequired && !$value) {
676 // return __('State is required.', 'fluent-cart');
677 // }
678 // },
679 // static function ($attribute, $value) use ($data, $country, $states) {
680 // if ($country && $value) {
681 // $isValid = Arr::has($states, $value);
682 // if (!$isValid) {
683 // return __('Invalid state code 2.', 'fluent-cart');
684 // }
685 // }
686 // }
687 // ]
688 // ),
689 // $prefix . 'postcode' => array_merge(
690 // $defaultPostcodeRoles,
691 // $this->getZipCodeValidationRule($country)
692 // ),
693 // ];
694 }
695
696 }
697