PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.26
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.26
1.6.5 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 All 48 releases
fluent-cart / app / Services / Localization / LocalizationManager.php

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

681 lines 19.1 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::$continents)) {
121 if ($countryCode) {
122 return self::$continents[$countryCode] ?? [];
123 }
124 return self::$continents;
125 }
126 self::$continents = require 'i18n/eu_tax_countries.php';
127 if ($countryCode) {
128 return self::$continents[$countryCode] ?? [];
129 }
130 return self::$continents;
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 * Get calling code for a country
387 */
388 public function callingCode(string $countryCode): string
389 {
390 $calling_codes = $this->phones();
391 return $calling_codes[$countryCode] ?? '';
392 }
393
394 /**
395 * Get locale for a country
396 */
397 public function localeForCountry(string $countryCode): string
398 {
399 $locales = $this->locales();
400 return $locales[$countryCode] ?? '';
401 }
402
403 /**
404 * Get states for a country
405 */
406 public function countryStates(string $countryCode): array
407 {
408 $states = $this->states();
409 return Arr::get($states, $countryCode, []);
410 }
411
412 /**
413 * Get states for a country as options
414 */
415 public function countryStatesOptions(string $countryCode): array
416 {
417 $states = $this->countryStates($countryCode);
418 $options = [];
419 foreach ($states as $code => $state) {
420 $options[] = [
421 'value' => $code,
422 'name' => $state,
423 ];
424 }
425 return apply_filters("fluent_cart/country_state_options", $options, [
426 'countryCode' => $countryCode,
427 ]);
428 }
429
430 // Static proxy methods for backward compatibility
431 public static function getCountries(): array
432 {
433 return self::getInstance()->countries();
434 }
435
436 public static function getTimeZones(): array
437 {
438 return self::getInstance()->timezones();
439 }
440
441 public static function getContinents(): array
442 {
443 return self::getInstance()->continents();
444 }
445
446 public static function getPhones(): array
447 {
448 return self::getInstance()->phones();
449 }
450
451 public static function getLocale($countryCode = null): array
452 {
453 return self::getInstance()->locales($countryCode);
454 }
455
456 public static function getStates(): array
457 {
458 return self::getInstance()->states();
459 }
460
461 public static function getUnits(): array
462 {
463 return self::getInstance()->units();
464 }
465
466 public static function getCountyIsoLists(): array
467 {
468 return self::getInstance()->countryIsoList();
469 }
470
471 public static function guessCountryFromTimezone(string $timezone): ?string
472 {
473 return self::getInstance()->guessCountryTimezone($timezone);
474 }
475
476 public static function getCountryCallingCode($countryCode): string
477 {
478 return self::getInstance()->callingCode($countryCode);
479 }
480
481 public static function getCountryStates($countryCode): array
482 {
483 return self::getInstance()->countryStates($countryCode);
484 }
485
486 public static function getAddressLocales($countryCode): array
487 {
488 return self::getInstance()->addressLocales($countryCode);
489 }
490
491 public static function isValidCountryCode($code)
492 {
493 $countries = self::getInstance()->countries();
494 // dd($countries);
495 }
496
497 public static function getCountryInfoFromRequest($timezone, $countryCode): array
498 {
499 if (!$countryCode && $timezone) {
500 $countryCode = self::guessCountryFromTimezone($timezone);
501 }
502
503 if (!$countryCode) {
504 return [
505 'country_code' => '',
506 'states' => [],
507 'address_locale' => []
508 ];
509 }
510
511 $states = self::getInstance()->statesOptions($countryCode);
512 $addressLocale = self::getInstance()->addressLocales($countryCode);
513
514 return [
515 'country_code' => $countryCode,
516 'states' => $states,
517 'address_locale' => $addressLocale
518 ];
519 }
520
521 /**
522 * Magic method to access properties
523 */
524 public function __get($name)
525 {
526 if ($name === 'postcode') {
527 return $this->getPostcodeInstance();
528 }
529
530 return null;
531 }
532
533 /**
534 * Get postcode verification instance
535 */
536 private function getPostcodeInstance(): PostcodeVerification
537 {
538 if (self::$postcodeInstance === null) {
539 self::$postcodeInstance = new PostcodeVerification();
540 }
541 return self::$postcodeInstance;
542 }
543
544 /**
545 * Static method to get postcode verification instance
546 */
547 public static function postcode()
548 {
549 return self::getInstance()->getPostcodeInstance();
550 }
551
552 public function getZipCodeValidationRule($countryCode): array
553 {
554
555 if (!$countryCode) {
556 return ['string', 'maxLength:192'];
557 }
558 $locale = $this->addressLocales($countryCode);
559
560 $isZipcodeRequired = Arr::get($locale, 'postcode.required', true) !== false;
561 if (!$isZipcodeRequired) {
562 return ['string', 'maxLength:192'];
563 }
564 return ['required', 'string', 'maxLength:192'];
565 }
566
567 public function getValidationRule($data, $prefix = '', $defaultRoles = []): array
568 {
569 $prefix = $prefix ? $prefix . '_' : '';
570 $countries = $this->countries();
571 $country = Arr::get($data, $prefix . 'country');
572 $addressLocale = $this->addressLocales($country);
573 $stateRequired = (string)(Arr::get($addressLocale, 'state.hidden', '')) !== '1';
574
575 if (Arr::get($addressLocale, 'state.required', true) === false) {
576 $stateRequired = false;
577 }
578
579 $states = empty($country) ? [] : $this->countryStates($country);
580
581 $defaultCountryRoles = Arr::wrap(Arr::get($defaultRoles, 'country', []));
582 $defaultStateRoles = Arr::wrap(Arr::get($defaultRoles, 'state', []));
583 $defaultPostcodeRoles = Arr::wrap(Arr::get($defaultRoles, 'postcode', []));
584
585 // Get current field structure based on prefix (billing/shipping)
586 $checkoutHelper = CartCheckoutHelper::make();
587 $fields = $prefix === 'billing_'
588 ? $checkoutHelper->getBillingAddressFields()
589 : $checkoutHelper->getShippingAddressFields();
590
591 $addressSchema = Arr::get($fields, 'address_section.schema', []);
592
593 $rules = [];
594
595 if (isset($addressSchema['country'])) {
596 $rules[$prefix . 'country'] = array_merge($defaultCountryRoles, [
597 static function ($attribute, $value) use ($countries) {
598 $isValid = Arr::has($countries, $value);
599 if (!$isValid) {
600 return __('Invalid country code.', 'fluent-cart');
601 }
602 }
603 ]);
604 }
605
606 if (isset($addressSchema['state'])) {
607 $rules[$prefix . 'state'] = array_merge($defaultStateRoles, [
608 'required' => static function ($attribute, $value) use ($stateRequired) {
609
610 if ($stateRequired && !$value) {
611 return __('State is required.', 'fluent-cart');
612 }
613 return null;
614 },
615 'invalid' => static function ($attribute, $value) use ($data, $country, $states) {
616
617 if (!is_array($states)) {
618 return null;
619 }
620
621 if (empty($states)) {
622 return null;
623 }
624 if ($country && $value) {
625 $isValid = Arr::has($states, $value);
626 if (!$isValid) {
627 return __('Invalid state code.', 'fluent-cart');
628 }
629 }
630
631 return null;
632 }
633 ]);
634 }
635
636 if (isset($cityZipSchema['postcode'])) {
637 $rules[$prefix . 'postcode'] = array_merge(
638 $defaultPostcodeRoles,
639 $this->getZipCodeValidationRule($country)
640 );
641 }
642
643 return $rules;
644
645 // return [
646 // $prefix . 'country' => array_merge($defaultCountryRoles,
647 // [
648 // static function ($attribute, $value) use ($countries) {
649 // $isValid = Arr::has($countries, $value);
650 // if (!$isValid) {
651 // return __('Invalid country code.', 'fluent-cart');
652 // }
653 // }
654 // ]
655 // ),
656 // $prefix . 'state' => array_merge($defaultStateRoles,
657 // [
658 // static function ($attribute, $value) use ($stateRequired) {
659 // if ($stateRequired && !$value) {
660 // return __('State is required.', 'fluent-cart');
661 // }
662 // },
663 // static function ($attribute, $value) use ($data, $country, $states) {
664 // if ($country && $value) {
665 // $isValid = Arr::has($states, $value);
666 // if (!$isValid) {
667 // return __('Invalid state code 2.', 'fluent-cart');
668 // }
669 // }
670 // }
671 // ]
672 // ),
673 // $prefix . 'postcode' => array_merge(
674 // $defaultPostcodeRoles,
675 // $this->getZipCodeValidationRule($country)
676 // ),
677 // ];
678 }
679
680 }
681