Skip to content

Commit

Permalink
configurable redirector adapter (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
benwalch authored Oct 9, 2023
1 parent 9c337ee commit fac7a85
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 81 deletions.
4 changes: 3 additions & 1 deletion UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

### New Features
- [BC BREAK] Config node `mode` has been removed and will be handled internally which simplifies i18n usability
- Configurable cookie expire flag
- [BC BREAK] Config node `cookie` has been removed. Please use `i18n.registry.redirector.cookie.config.cookie` instead.
See [Redirector Adapter](docs/51_RedirectorAdapter.md) for further reference
- Fully configurable redirector adapters

***

Expand Down
20 changes: 19 additions & 1 deletion config/pimcore/config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
doctrine_migrations:
migrations_paths:
'I18nBundle\Migrations': '@I18nBundle/src/Migrations'
'I18nBundle\Migrations': '@I18nBundle/src/Migrations'

i18n:
registry:
redirector:
cookie:
config:
cookie:
path: /
secure: false
http_only: true
same_site: lax
expire: '+1 year'
geo:
config:
rules:
- { ignore_country: false, strict_country: true, strict_language: false }
- { ignore_country: false, strict_country: false, strict_language: false }
- { ignore_country: true, strict_country: false, strict_language: true }
2 changes: 0 additions & 2 deletions config/services/helper.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ services:
autoconfigure: true
public: false

I18nBundle\Helper\CookieHelper: ~

I18nBundle\Helper\AdminLocaleHelper: ~

I18nBundle\Helper\AdminMessageRendererHelper: ~
Expand Down
57 changes: 56 additions & 1 deletion docs/51_RedirectorAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,33 @@ redirector gets applied.

### Cookie Redirector
> Priority: `300`
If enabled, visitor gets redirected to the last selected locale
If enabled, visitor gets redirected to the last selected locale

Configuration:
```
config:
cookie:
path:
domain:
secure:
http_only:
same_site:
expire:
```

### GEO Redirector
> Priority: `200`
If enabled, visitor gets redirected based on IP and browser language

Configuration
```
config:
rules:
- { ignore_country: false, strict_country: true, strict_language: false }
- { ignore_country: false, strict_country: false, strict_language: false }
...
```

### Fallback Redirector
> Priority: `100`
If enabled, visitor gets redirected based on the `default_locale` setting defined in i18n settings (available in each zone)
Expand Down Expand Up @@ -52,6 +73,22 @@ App\Services\I18nBundle\RedirectorAdapter\Website:
- { name: i18n.adapter.redirector, alias: website, priority: 110 }
```
### 2. Enable
you can also provide config for your redirector
```yaml
# config/config.yaml
i18n:
registry:
rediretor:
website:
enabled: true
config:
my_config_node: 'value'
# etc ...

```

### 3. Create a class

Create a class, extend it from `AbstractRedirector`.
Expand All @@ -64,6 +101,7 @@ namespace App\Services\I18nBundle\RedirectorAdapter;
use I18nBundle\Adapter\Redirector\AbstractRedirector;
use I18nBundle\Adapter\Redirector\RedirectorBag;
use I18nBundle\Model\ZoneSiteInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class Website extends AbstractRedirector
{
Expand All @@ -73,6 +111,9 @@ class Website extends AbstractRedirector
if ($this->lastRedirectorWasSuccessful($redirectorBag) === true) {
return;
}

// get custom config
$myConfigValue = $this->config['my_config_node'];

// get the last decision bag
$lastDecisionBag = $redirectorBag->getLastRedirectorDecision();
Expand Down Expand Up @@ -144,5 +185,19 @@ class Website extends AbstractRedirector
$this->setDecision(['valid' => false]);
}
}

protected function getConfigResolver(): ?OptionsResolver
{
$optionsResolver = new OptionsResolver();

// preare your options resolver
$optionsResolver->setRequired('my_config_node');
$optionsResolver->setAllowedTypes('my_config_node', 'string');
// etc ..

return $optionsResolver;

}

}
```
29 changes: 26 additions & 3 deletions src/Adapter/Redirector/AbstractRedirector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
abstract class AbstractRedirector implements RedirectorInterface
{
protected bool $enabled = true;
protected array $config = [];
protected ?string $name;
protected array $decision = [];

Expand All @@ -20,6 +21,23 @@ public function setEnabled(bool $enabled): void
$this->enabled = $enabled;
}

public function getConfig(): array
{
return $this->config;
}

public function setConfig(array $config): void
{
$configResolver = $this->getConfigResolver();
if (null === $configResolver) {
if (!empty($config)) {
throw new \Exception(sprintf('redirector adapter "%s" has a config, but no config resolver was provided.', $this->getName()));
}
} else {
$this->config = $configResolver->resolve($config);
}
}

public function getName(): string
{
return $this->name;
Expand All @@ -32,12 +50,12 @@ public function setName($name): void

public function setDecision(array $decision): void
{
$this->decision = $this->getResolver()->resolve($decision);
$this->decision = $this->getDecisionResolver()->resolve($decision);
}

public function getDecision(): array
{
return $this->getResolver()->resolve($this->decision);
return $this->getDecisionResolver()->resolve($this->decision);
}

public function lastRedirectorWasSuccessful(RedirectorBag $redirectorBag): bool
Expand All @@ -52,7 +70,12 @@ public function lastRedirectorWasSuccessful(RedirectorBag $redirectorBag): bool
return false;
}

private function getResolver(): OptionsResolver
protected function getConfigResolver(): ?OptionsResolver
{
return null;
}

private function getDecisionResolver(): OptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
Expand Down
39 changes: 32 additions & 7 deletions src/Adapter/Redirector/CookieRedirector.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
namespace I18nBundle\Adapter\Redirector;

use I18nBundle\Helper\CookieHelper;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CookieRedirector extends AbstractRedirector
{
protected CookieHelper $cookieHelper;

public function __construct(CookieHelper $cookieHelper)
{
$this->cookieHelper = $cookieHelper;
}

public function makeDecision(RedirectorBag $redirectorBag): void
{
$cookieHelper = new CookieHelper($this->config['cookie']);

if ($this->lastRedirectorWasSuccessful($redirectorBag) === true) {
return;
}
Expand All @@ -26,7 +24,7 @@ public function makeDecision(RedirectorBag $redirectorBag): void
$language = null;

$request = $redirectorBag->getRequest();
$redirectCookie = $this->cookieHelper->get($request);
$redirectCookie = $cookieHelper->get($request);

//if no cookie available the validation fails.
if (is_array($redirectCookie) && !empty($redirectCookie['url'])) {
Expand All @@ -45,4 +43,31 @@ public function makeDecision(RedirectorBag $redirectorBag): void
'url' => $url
]);
}

protected function getConfigResolver(): OptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setRequired('cookie');
$resolver->setDefault('cookie', function(OptionsResolver $cookieResolver) {
$cookieResolver
->setRequired(['path', 'domain', 'secure', 'http_only', 'same_site'])
->setDefaults([
'path' => '/',
'domain' => null,
'secure' => false,
'http_only' => true,
'same_site' => Cookie::SAMESITE_LAX,
'expire' => '+1 year'
])
->setAllowedTypes('path', 'string')
->setAllowedTypes('domain', ['string', 'null'])
->setAllowedTypes('secure', 'bool')
->setAllowedTypes('http_only', 'bool')
->setAllowedTypes('same_site', 'string')
->setAllowedTypes('expire', ['integer', 'string'])
->setAllowedValues('same_site', [Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE]);
});

return $resolver;
}
}
30 changes: 22 additions & 8 deletions src/Adapter/Redirector/GeoRedirector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use I18nBundle\Helper\UserHelper;
use I18nBundle\Model\ZoneSiteInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class GeoRedirector extends AbstractRedirector
{
Expand Down Expand Up @@ -47,18 +48,14 @@ public function makeDecision(RedirectorBag $redirectorBag): void
];

$prioritisedListQuery = [];
$prioritisedList = [
['ignoreCountry' => false, 'countryStrictMode' => true, 'languageStrictMode' => false],
['ignoreCountry' => false, 'countryStrictMode' => false, 'languageStrictMode' => false],
['ignoreCountry' => true, 'countryStrictMode' => false, 'languageStrictMode' => true]
];
$prioritisedList = $this->config['rules'];

foreach ($prioritisedList as $index => $list) {
foreach ($userLanguagesIso as $priority => $userLocale) {

$country = $list['ignoreCountry'] ? null : $userCountryIso;
$countryStrictMode = $list['countryStrictMode'];
$languageStrictMode = $list['languageStrictMode'];
$country = $list['ignore_country'] ? null : $userCountryIso;
$countryStrictMode = $list['strict_country'];
$languageStrictMode = $list['strict_language'];

if (null !== $zoneSite = $this->findZoneSite($zoneSites, $userLocale, $country, $countryStrictMode, $languageStrictMode)) {
$prioritisedListQuery[] = [
Expand Down Expand Up @@ -144,4 +141,21 @@ protected function findZoneSite(

return $indexId !== false ? $zoneSites[$indexId] : null;
}

protected function getConfigResolver(): OptionsResolver
{
$resolver = new OptionsResolver();
$resolver
->setRequired('rules')
->setDefault('rules', function(OptionsResolver $rulesResolver) {
$rulesResolver
->setPrototype(true)
->setRequired(['ignore_country', 'strict_country', 'strict_language'])
->setAllowedTypes('ignore_country', 'bool')
->setAllowedTypes('strict_country', 'bool')
->setAllowedTypes('strict_language', 'bool');
});

return $resolver;
}
}
6 changes: 6 additions & 0 deletions src/Adapter/Redirector/RedirectorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace I18nBundle\Adapter\Redirector;

use Symfony\Component\OptionsResolver\OptionsResolver;

interface RedirectorInterface
{
public function isEnabled(): bool;
Expand All @@ -16,6 +18,10 @@ public function setDecision(array $decision): void;

public function getDecision(): array;

public function setConfig(array $config): void;

public function getConfig(): array;

public function lastRedirectorWasSuccessful(RedirectorBag $redirectorBag): bool;

public function makeDecision(RedirectorBag $redirectorBag): void;
Expand Down
12 changes: 10 additions & 2 deletions src/DependencyInjection/Compiler/RedirectorAdapterPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ public function process(ContainerBuilder $container): void
{
$services = [];
$definition = $container->getDefinition(RedirectorRegistry::class);
$registryAvailability = $container->getParameter('i18n.registry_availability');
$registry = $container->getParameter('i18n.registry');

$redirectorRegistry = $registry['redirector'];

foreach ($container->findTaggedServiceIds('i18n.adapter.redirector', true) as $serviceId => $attributes) {
$priority = $attributes[0]['priority'] ?? 0;
Expand All @@ -33,11 +35,17 @@ public function process(ContainerBuilder $container): void

foreach ($services as $service) {
$serviceAlias = $service['alias'];
$available = isset($registryAvailability['redirector'][$serviceAlias]) ? $registryAvailability['redirector'][$serviceAlias]['enabled'] : true;
$available = isset($redirectorRegistry[$serviceAlias]) ? $redirectorRegistry[$serviceAlias]['enabled'] : true;

if (!$available) {
continue;
}

$definition->addMethodCall('register', [$service['reference'], $serviceAlias]);

$service['definition']->addMethodCall('setName', [$serviceAlias]);
$service['definition']->addMethodCall('setEnabled', [$available]);
$service['definition']->addMethodCall('setConfig', [$redirectorRegistry[$serviceAlias]['config'] ?? []]);
}
}
}
Loading

0 comments on commit fac7a85

Please sign in to comment.