Skip to content

Commit cd9036c

Browse files
Fix supported locales resolving
1 parent 2d16f56 commit cd9036c

File tree

7 files changed

+297
-5
lines changed

7 files changed

+297
-5
lines changed

src/DependencyInjection/Configuration.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@
1818

1919
final class Configuration implements ConfigurationInterface
2020
{
21+
private const ALL_PAYPAL_SUPPORTED_LOCALES = [
22+
'ar_EG', 'cs_CZ', 'da_DK', 'de_DE', 'el_GR', 'en_US', 'en_AU', 'en_GB',
23+
'en_IN', 'es_ES', 'es_XC', 'fi_FI', 'fr_FR', 'fr_CA', 'fr_XC', 'he_IL',
24+
'hu_HU', 'id_ID', 'it_IT', 'ja_JP', 'ko_KR', 'nl_NL', 'no_NO', 'pl_PL',
25+
'pt_BR', 'pt_PT', 'ru_RU', 'sk_SK', 'sv_SE', 'th_TH', 'zh_CN', 'zh_HK',
26+
'zh_TW', 'zh_XC',
27+
];
28+
2129
public function getConfigTreeBuilder(): TreeBuilder
2230
{
2331
$treeBuilder = new TreeBuilder('sylius_pay_pal_plugin');
@@ -34,6 +42,11 @@ public function getConfigTreeBuilder(): TreeBuilder
3442
->end()
3543
->end()
3644
->end()
45+
->arrayNode('supported_locales')
46+
->scalarPrototype()->end()
47+
->defaultValue(self::ALL_PAYPAL_SUPPORTED_LOCALES)
48+
->performNoDeepMerging()
49+
->end()
3750
->end()
3851
;
3952

src/DependencyInjection/SyliusPayPalExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public function load(array $configs, ContainerBuilder $container): void
3838

3939
$container->setParameter('sylius.paypal.logging.increased', (bool) $config['logging']['increased']);
4040
$container->setParameter('sylius_paypal.logging.increased', $container->getParameter('sylius.paypal.logging.increased'));
41+
$container->setParameter('sylius_paypal.supported_locales', $config['supported_locales']);
4142

4243
if ($config['sandbox']) {
4344
$container->setParameter('sylius.pay_pal.facilitator_url', 'https://paypal.sylius.com');

src/Processor/LocaleProcessor.php

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,44 @@
1313

1414
namespace Sylius\PayPalPlugin\Processor;
1515

16+
use Sylius\PayPalPlugin\Resolver\SupportedLocaleResolverInterface;
1617
use Symfony\Component\Intl\Locales;
1718

1819
final class LocaleProcessor implements LocaleProcessorInterface
1920
{
21+
private const ALLOWED_LOCALE_PATTERN = '/^[a-z]{2}(_[A-Z]{2})?$/';
22+
23+
public function __construct(
24+
private readonly ?SupportedLocaleResolverInterface $supportedLocaleResolver = null,
25+
) {
26+
if (null === $this->supportedLocaleResolver) {
27+
trigger_deprecation(
28+
'sylius/paypal-plugin',
29+
'1.7',
30+
sprintf(
31+
'Not passing an instance of "%s" is deprecated and will be prohibited in 3.0',
32+
SupportedLocaleResolverInterface::class,
33+
),
34+
);
35+
}
36+
}
37+
2038
public function process(string $locale): string
39+
{
40+
if (null !== $this->supportedLocaleResolver) {
41+
$locale = trim($locale);
42+
43+
if ($this->isValidLocale($locale)) {
44+
return $this->supportedLocaleResolver->resolve($locale);
45+
}
46+
47+
throw new \UnexpectedValueException(sprintf('Locale "%s" is not valid.', $locale));
48+
}
49+
50+
return $this->legacyProcess($locale);
51+
}
52+
53+
private function legacyProcess(string $locale): string
2154
{
2255
if ($this->isValidLocale($locale)) {
2356
return $locale;
@@ -29,21 +62,24 @@ public function process(string $locale): string
2962

3063
$locales = array_filter(Locales::getLocales(), function (string $targetLocale) use ($locale): bool {
3164
return
32-
strpos($targetLocale, $locale) === 0 &&
33-
strpos($targetLocale, '_') !== false &&
34-
strlen($targetLocale) === 5
65+
str_starts_with($targetLocale, $locale) &&
66+
strlen($targetLocale) === 5 &&
67+
$this->isValidLocale($targetLocale)
3568
;
3669
});
3770

3871
if ([] === $locales) {
3972
throw new \UnexpectedValueException(sprintf('Locale "%s" is not supported by PayPal.', $locale));
4073
}
4174

42-
return $locales[array_key_first($locales)];
75+
return array_shift($locales);
4376
}
4477

4578
private function isValidLocale(string $locale): bool
4679
{
47-
return strpos($locale, '_') !== false;
80+
return
81+
false === str_contains($locale, ' ') &&
82+
1 === preg_match(self::ALLOWED_LOCALE_PATTERN, $locale)
83+
;
4884
}
4985
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\PayPalPlugin\Resolver;
15+
16+
final class SupportedLocaleResolver implements SupportedLocaleResolverInterface
17+
{
18+
private readonly array $supportedLocales;
19+
20+
public function __construct(
21+
array $supportedLocales = [],
22+
) {
23+
$this->supportedLocales = array_unique($supportedLocales);
24+
}
25+
26+
public function resolve(string $locale): string
27+
{
28+
if (empty(trim($locale))) {
29+
throw new \UnexpectedValueException('Locale cannot be empty.');
30+
}
31+
32+
$length = strlen($locale);
33+
if (5 === $length) {
34+
if (in_array($locale, $this->supportedLocales, true)) {
35+
return $locale;
36+
}
37+
38+
throw new \UnexpectedValueException(
39+
sprintf('Locale "%s" is not supported by PayPal.', $locale),
40+
);
41+
}
42+
if (2 === $length) {
43+
return $this->findSupportedLocaleByLanguageCode($locale);
44+
}
45+
46+
throw new \UnexpectedValueException(
47+
sprintf('Locale "%s" is not supported by PayPal.', $locale),
48+
);
49+
}
50+
51+
private function findSupportedLocaleByLanguageCode(string $languageCode): string
52+
{
53+
foreach ($this->supportedLocales as $locale) {
54+
if (str_starts_with($locale, $languageCode)) {
55+
return $locale;
56+
}
57+
}
58+
59+
throw new \UnexpectedValueException(
60+
sprintf('Language "%s" could not be resolved into a locale supported by PayPal.', $languageCode),
61+
);
62+
}
63+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\PayPalPlugin\Resolver;
15+
16+
interface SupportedLocaleResolverInterface
17+
{
18+
/** @throw \UnexpectedValueException */
19+
public function resolve(string $locale): string;
20+
}

src/Resources/config/services.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,5 +356,13 @@
356356
<argument type="service" id="sylius.repository.payment_method" />
357357
</service>
358358
<service id="Sylius\PayPalPlugin\Resolver\PayPalPaymentMethodsResolverInterface" alias="sylius_paypal.resolver.paypal_payment_methods" />
359+
360+
<service
361+
id="sylius_paypal.resolver.supported_locale"
362+
class="Sylius\PayPalPlugin\Resolver\SupportedLocaleResolver"
363+
>
364+
<argument>%sylius_paypal.supported_locales%</argument>
365+
</service>
366+
<service id="Sylius\PayPalPlugin\Resolver\SupportedLocaleResolverInterface" alias="sylius_paypal.resolver.supported_locale" />
359367
</services>
360368
</container>
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Tests\Sylius\PayPalPlugin\Unit\Resolver;
15+
16+
use PHPUnit\Framework\TestCase;
17+
use Sylius\PayPalPlugin\Resolver\SupportedLocaleResolver;
18+
19+
final class SupportedLocaleResolverTest extends TestCase
20+
{
21+
/** @dataProvider validFiveCharacterLocaleProvider */
22+
public function test_returns_exact_match_for_five_character_locale(string $locale, array $supportedLocales): void
23+
{
24+
$resolver = new SupportedLocaleResolver($supportedLocales);
25+
26+
$result = $resolver->resolve($locale);
27+
28+
$this->assertSame($locale, $result);
29+
}
30+
31+
/** @return \Generator<string, array<string|array<string>>> */
32+
public static function validFiveCharacterLocaleProvider(): \Generator
33+
{
34+
yield 'en_US' => ['en_US', ['en_US', 'fr_FR', 'de_DE']];
35+
yield 'fr_FR' => ['fr_FR', ['en_US', 'fr_FR', 'de_DE']];
36+
yield 'de_DE' => ['de_DE', ['en_US', 'fr_FR', 'de_DE']];
37+
}
38+
39+
/** @dataProvider unsupportedFiveCharacterLocaleProvider */
40+
public function test_throws_for_unsupported_five_character_locale(string $locale, array $supportedLocales): void
41+
{
42+
$resolver = new SupportedLocaleResolver($supportedLocales);
43+
44+
$this->expectException(\UnexpectedValueException::class);
45+
$this->expectExceptionMessage(sprintf('Locale "%s" is not supported by PayPal.', $locale));
46+
47+
$resolver->resolve($locale);
48+
}
49+
50+
/** @return \Generator<string, array<string|array<string>>> */
51+
public static function unsupportedFiveCharacterLocaleProvider(): \Generator
52+
{
53+
yield 'es_ES not in list' => ['es_ES', ['en_US', 'fr_FR', 'de_DE']];
54+
yield 'it_IT not in list' => ['it_IT', ['en_US', 'fr_FR']];
55+
yield 'pt_PT not in list' => ['pt_PT', ['en_GB', 'en_US']];
56+
}
57+
58+
/** @dataProvider validLanguageCodeProvider */
59+
public function test_resolves_two_character_language_code_to_supported_locale(string $languageCode, array $supportedLocales, string $expectedLocale): void
60+
{
61+
$resolver = new SupportedLocaleResolver($supportedLocales);
62+
63+
$result = $resolver->resolve($languageCode);
64+
65+
$this->assertSame($expectedLocale, $result);
66+
}
67+
68+
/** @return \Generator<string, array<string|array<string>>> */
69+
public static function validLanguageCodeProvider(): \Generator
70+
{
71+
yield 'en finds en_US' => ['en', ['en_US', 'en_GB', 'fr_FR', 'de_DE'], 'en_US'];
72+
yield 'de finds de_DE' => ['de', ['de_DE', 'de_AT', 'fr_FR'], 'de_DE'];
73+
yield 'fr finds fr_FR' => ['fr', ['en_US', 'fr_FR', 'de_DE'], 'fr_FR'];
74+
}
75+
76+
/** @dataProvider unsupportedLanguageCodeProvider */
77+
public function test_throws_when_language_code_not_found(string $languageCode, array $supportedLocales): void
78+
{
79+
$resolver = new SupportedLocaleResolver($supportedLocales);
80+
81+
$this->expectException(\UnexpectedValueException::class);
82+
$this->expectExceptionMessage(sprintf('Language "%s" could not be resolved into a locale supported by PayPal.', $languageCode));
83+
84+
$resolver->resolve($languageCode);
85+
}
86+
87+
/** @return \Generator<string, array<string|array<string>>> */
88+
public static function unsupportedLanguageCodeProvider(): \Generator
89+
{
90+
yield 'es not in list' => ['es', ['en_US', 'fr_FR', 'de_DE']];
91+
yield 'it not in list' => ['it', ['en_US', 'fr_FR']];
92+
yield 'pt not in list' => ['pt', ['en_GB', 'en_US']];
93+
}
94+
95+
/** @dataProvider invalidLocaleProvider */
96+
public function test_throws_for_invalid_locale_length(string $locale, array $supportedLocales): void
97+
{
98+
$resolver = new SupportedLocaleResolver($supportedLocales);
99+
100+
$this->expectException(\UnexpectedValueException::class);
101+
$this->expectExceptionMessage(sprintf('Locale "%s" is not supported by PayPal.', $locale));
102+
103+
$resolver->resolve($locale);
104+
}
105+
106+
/** @return \Generator<string, array<string|array<string>>> */
107+
public static function invalidLocaleProvider(): \Generator
108+
{
109+
yield 'three character locale' => ['eng', ['en_US', 'fr_FR']];
110+
yield 'six character locale' => ['en_US_X', ['en_US', 'fr_FR']];
111+
yield 'four character locale' => ['enUS', ['en_US', 'fr_FR']];
112+
}
113+
114+
public function test_throws_for_empty_locale(): void
115+
{
116+
$resolver = new SupportedLocaleResolver(['en_US', 'fr_FR']);
117+
118+
$this->expectException(\UnexpectedValueException::class);
119+
$this->expectExceptionMessage('Locale cannot be empty.');
120+
121+
$resolver->resolve('');
122+
}
123+
124+
public function test_constructor_removes_duplicate_locales(): void
125+
{
126+
$supportedLocales = ['en_US', 'fr_FR', 'en_GB', 'de_DE', 'fr_FR'];
127+
$resolver = new SupportedLocaleResolver($supportedLocales);
128+
129+
$result = $resolver->resolve('en');
130+
$this->assertSame('en_US', $result);
131+
}
132+
133+
/** @dataProvider emptyLocaleProvider */
134+
public function test_throws_with_empty_locale(string $locale): void
135+
{
136+
$supportedLocales = ['en_US', 'fr_FR', 'en_US', 'de_DE', 'fr_FR'];
137+
$resolver = new SupportedLocaleResolver($supportedLocales);
138+
139+
$this->expectException(\UnexpectedValueException::class);
140+
$this->expectExceptionMessage('Locale cannot be empty.');
141+
142+
$resolver->resolve($locale);
143+
}
144+
145+
/** @return \Generator<string, array<array<string>>> */
146+
public static function emptyLocaleProvider(): \Generator
147+
{
148+
yield 'empty' => [''];
149+
yield 'space' => [' '];
150+
}
151+
}

0 commit comments

Comments
 (0)