diff --git a/README.md b/README.md
index f1bad8e..06a9850 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
![i18n](https://user-images.githubusercontent.com/700119/27761666-f3ed6746-5e60-11e7-955a-3030453c68ff.jpg)
## Scheme
-![i18n scheme](https://user-images.githubusercontent.com/700119/140646548-fcf6433d-1fa0-4323-98e2-9cc49550e5ee.png)
+![i18n scheme](https://user-images.githubusercontent.com/700119/141124503-59576527-e5b1-47b3-a38e-d06e51555bde.png)
## Introduction
Pimcore already comes with some great features to build internationalized websites.
@@ -58,15 +58,17 @@ When using this bundle, you should:
- If you're using the country detection, you need a valid maxmind geo ip [data provider](docs/10_GeoControl.md)
## Further Information
+- [I18n Overview Page](./docs/1_I18n.md): Learn all about the i18n principals.
- [Geo IP/Control](docs/10_GeoControl.md): Enable GeoIP Data Provider.
- [Zone Definitions](docs/20_Zones.md): Learn more about i18n zone definitions and how to manage them.
- - [Custom I18n Context Look-Up](docs/21_CustomI18nContextLookUp.md)] (🔥 New!)
+ - [Custom I18n Context Look-Up](docs/21_I18nContext.md)] (🔥 New!)
- [Href-Lang](docs/25_HrefLang.md): Find out more about the href-lang tag generator.
- [Language Configuration](docs/26_Languages.md): Configure languages.
- [Country Configuration](docs/27_Countries.md): Configure countries.
-- Dynamic Routing
- - [Static Routes](docs/28_StaticRoutes.md): Configure translatable static routes and implement href-lang tags.
- - [Symfony Route](docs/29_SymfonyRoutes.md): Configure translatable symfony routes and implement href-lang tags.
+- Route and Alternate Links Generation
+ - [Document Routes](docs/90_DocumentRoutes.md): Build document routes
+ - [Static Routes](docs/91_StaticRoutes.md): Build translatable static routes and implement href-lang tags.
+ - [Symfony Route](docs/92_SymfonyRoutes.md): Build translatable symfony routes and implement href-lang tags.
- [Front Page Mapping](docs/30_FrontPageMapping.md): Learn how to map a custom front page.
- [Localized Error Documents](docs/40_LocaleErrorDocument.md): Learn how to create localized error documents.
- [Custom Locale Adapter](docs/50_CustomLocaleAdapter.md): Learn how to create a custom locale adapter.
diff --git a/UPGRADE.md b/UPGRADE.md
index 9c50e82..333ee22 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -34,7 +34,8 @@ i18n:
### Global Changes
- `$staticRoute->assemble()` is **not** supported anymore, you always need to call `$router->generate()`:
- Every PIMCORE LinkGenerator needs to implement the `I18nLinkGeneratorInterface`
- - You need to pass the `_18n => [ type = RouteItemInterface::TYPE, routeParameters => [] ]` block via `$router->generate()`
+ - You need to pass the `_18n => [ type = RouteItemInterface::TYPE, routeParameters => [] ]` block via `$router->generate()` (Or use `RouteParameterBuilder` for parameter building)
+- `url()`, `path()`, `pimcore_url()` twig helper are not supported, use `i18n_entity_route()`, `i18n_static_route()` and `i18n_symfony_route()` instead
- Context Adapter and Manager have been removed (All corresponding information are available via `I18nContextInterface` directly)
- PHP8 return type declarations added: you may have to adjust your extensions accordingly
- `LocaleProviderInterface` changes:
@@ -79,7 +80,7 @@ i18n:
### Additional new Features
- Check Akamai CDN header [@florian25686](https://github.com/dachcom-digital/pimcore-i18n/pull/76/files)
-- Allow different I18nContext look-ups [#70](https://github.com/dachcom-digital/pimcore-i18n/issues/70), read more about it [here](./docs/21_CustomI18nContextLookUp.md)
+- Allow different I18nContext look-ups [#70](https://github.com/dachcom-digital/pimcore-i18n/issues/70), read more about it [here](./docs/21_I18nContext.md)
- Allow symfony routes [#65](https://github.com/dachcom-digital/pimcore-i18n/issues/65)
***
diff --git a/docs/1_I18n.md b/docs/1_I18n.md
index bcea396..de7b046 100644
--- a/docs/1_I18n.md
+++ b/docs/1_I18n.md
@@ -1,5 +1,5 @@
# I18n Workflow
-![i18n scheme](https://user-images.githubusercontent.com/700119/140646548-fcf6433d-1fa0-4323-98e2-9cc49550e5ee.png)
+![i18n scheme](https://user-images.githubusercontent.com/700119/141124503-59576527-e5b1-47b3-a38e-d06e51555bde.png)
## I18n Context
I18n will generate a `I18nContextInterface` object at **every request** which is accessible via the resolver
@@ -136,126 +136,32 @@ my_symfony_route:
matching_route_key: '(%i18n.route.translations.mySymfonyRouteKey%)' ## returns (meine-symfony-route|my-symfony-route)
```
-## Command Line
-Generating absolute URLs in CL is easy! Let's create a static route (which will dispatch your LinkGenerator):
+## Twig|PHP Route Generation
+- [Current Request Routes for Documents](./90_DocumentRoutes.md#generating-routes-in-current-request)
+- [Current Request Routes for Static Routes](./91_StaticRoutes.md#generating-routes-in-current-request)
+- [Current Request Routes for Symfony Routes](./92_SymfonyRoutes.md#generating-routes-in-current-request)
-```php
-$routeParameters = [
- '_i18n' => [
- 'type' => RouteItemInterface::STATIC_ROUTE,
- 'entity' => $object,
- 'routeParameters' => [
- '_locale' => 'en'
- ],
- 'context' => [
- ## if you're having sites, you need to define zones
- ## if you're having zones, you MUST pass the site context parameter
- ## that's the rule!
- 'site' => Site::getByDomain('test-domain1.test')
- ]
- ]
-];
-
-echo $this->router->generate('', $routeParameters, UrlGeneratorInterface::ABSOLUTE_URL);
-```
-
-## Twig Helper
-Generating a complete headless context object via twig:
-
-```twig
-{# SECTION A: simple i18n route generation #}
-
-{# simple url() #}
-{{ url('my_symfony_route', {_i18n: { type: 'symfony', routeParameters: {_locale: 'de'}, context: { site: pimcore_site_by_domain('test-domain1.test') }}}) }}
-{# simple path() without configured zones #}
-{{ path('my_symfony_route', {_i18n: { type: 'symfony', routeParameters: {_locale: 'de'} }}) }}
-
-
-{# SECTION B: complete i18n context generation #}
-
-{% set object_context = i18n_create_context_by_symfony_route('my_symfony_route', {_locale: 'en'}) %}
-{{ dump(object_context) }}
-{{ dump(object_context.linkedLanguages) }}
-
-{#
- if you're having sites, you need to define zones
- if you're having zones, you MUST pass the site context parameter
- that's the rule!
-#}
-{% set route_site = pimcore_site_by_domain('test-domain1.test') %}
-{% set zone_aware_object_context = i18n_create_context_by_symfony_route('my_symfony_route', {_locale: 'en'}, route_site) %}
-{{ dump(zone_aware_object_context) }}
-{{ dump(zone_aware_object_context.linkedLanguages) }}
-```
+## Command Line Route Generation
+Generating absolute URLs in CLI is easy!
+- [CLI Routes for Documents](./90_DocumentRoutes.md#generating-routes-in-cli)
+- [CLI Routes for Static Routes](./91_StaticRoutes.md#generating-routes-in-cli)
+- [CLI Routes for Symfony Routes](./92_SymfonyRoutes.md#generating-routes-in-cli)
-## PHP Helper
-Generating a complete headless context object via php api. Let's dump some examples:
+## Non-I18n Routes
+I18n always requires the `_i18n` parameter node in `urlGenerator->generate`.
+If this node is not present, the default route generation will be triggered.
```php
+dump($router->generate('my_non_i18n_aware_symfony_route', ['_locale' => 'en'], UrlGeneratorInterface::ABSOLUTE_URL));
+```
-// SECTION A: complete i18n context generation
-
-// $i18nContextManager = [DI] I18nBundle\Manager\I18nContextManager
-$routeItemParameters = [
- 'type' => RouteItemInterface::SYMFONY_ROUTE,
- 'routeParameters' => [
- '_locale' => 'de'
- // ... and all your special route params which i18n are not aware off
- ],
- 'routeName' => 'my_static_route',
- 'context' => [
- 'site' => Site::getByDomain('test-domain1.test')
- ]
-];
-
-// if you want to use the `$i18nContext.getLinkedLanguages()`
-// you need to pass the second boolean argument as true to boot the path generator adapter
-// this is disabled by default because it is not required when generating default routes
-$i18nContext = $i18nContextManager->buildContextByParameters($routeItemParameters, true);
-
-// SECTION B: simple i18n route generation
-
-// $router = [DI] Symfony\Component\Routing\RouterInterface
-dump('static route (without entity) and site:' . $router->generate('my_static_route', [
- '_i18n' => [
- 'type' => RouteItemInterface::STATIC_ROUTE,
- 'routeName' => 'my_static_route'
- 'routeParameters' => [
- '_locale' => 'de'
- // ... and all your special route params which i18n are not aware off
- ],
- 'context' => [
- ## if you're having sites, you need to define zones
- ## if you're having zones, you MUST pass the site context parameter
- ## that's the rule!
- 'site' => Site::getByDomain('test-domain1.test')
- ]
- ]
-], UrlGeneratorInterface::ABSOLUTE_URL));
-
-dump('static route (with entity): ' . $router->generate('', [
- '_i18n' => [
- 'type' => RouteItemInterface::STATIC_ROUTE,
- 'entity' => $object,
- 'routeParameters' => [
- '_locale' => 'de'
- // ... and all your special route params which i18n are not aware off
- ]
- ]
-], UrlGeneratorInterface::ABSOLUTE_URL));
-
-dump('symfony route: ' . $router->generate('my_symfony_route', [
- '_i18n' => [
- 'type' => RouteItemInterface::SYMFONY_ROUTE,
- 'routeParameters' => [
- '_locale' => 'de'
- // ... and all your special route params which i18n are not aware off
- ]
- ]
-], UrlGeneratorInterface::ABSOLUTE_URL));
+## Context
+I18n allows you to easily fetch the current context via a given request object.
+Read more about it [here](21_I18nContext.md#current-context).
-dump('non i18n symfony route: ' . $router->generate('my_non_i18n_aware_symfony_route', ['_locale' => 'en'], UrlGeneratorInterface::ABSOLUTE_URL));
-```
+## Custom Context Boot
+Generating a complete headless context object via twig or php api.
+Read more about it [here](21_I18nContext.md#custom-context-look-up).
## Code Examples
Please check out the [code examples](./60_CodeExamples.md) doc section to learn more about accessing zone information.
\ No newline at end of file
diff --git a/docs/21_CustomI18nContextLookUp.md b/docs/21_I18nContext.md
similarity index 55%
rename from docs/21_CustomI18nContextLookUp.md
rename to docs/21_I18nContext.md
index beffc98..b069857 100644
--- a/docs/21_CustomI18nContextLookUp.md
+++ b/docs/21_I18nContext.md
@@ -1,28 +1,68 @@
+# Current Context
+
+Fetch the current context to list linked languages for example:
+
+```twig
+
i18n Current Context
+{% set i18n_current_context = i18n_current_context() %}
+{{ dump(i18n_current_context) }}
+{{ dump(i18n_current_context.linkedLanguages) }}
+
+ {% for link in i18n_current_context.linkedLanguages %}
+ {{ link.url }}
+ {% endfor %}
+
+```
+
+**PHP**
+
+```php
+getContext($requestStack->getMainRequest());
+ $linkedLanguages = $i18nContext->getLinkedLanguages();
+ }
+}
+```
+
# Custom Context Look-Up
-In some cases, you want to retrieve all available links for a specific document or object (Generating links via commandline for example).
+
+In some cases, you want to retrieve all available links for a specific document or object.
+
+## Boot Context in TWIG
```twig
I18n Context Look-Up (document)
{% set document_context = i18n_create_context_by_entity(pimcore_document(16), { _locale: 'en' }) %}
{{ dump(document_context) }}
{{ dump(document_context.linkedLanguages) }}
-
+
+
I18n Context Look-Up (object [will call correspondig link generator])
{% set object_context = i18n_create_context_by_entity(pimcore_object(16), { _locale: 'en' }) %}
{{ dump(object_context) }}
{{ dump(object_context.linkedLanguages) }}
-
+
+
I18n Context Look-Up (symfony route)
{% set object_context = i18n_create_context_by_symfony_route('my_symfony_route, { _locale: 'en' } }) %}
{{ dump(object_context) }}
{{ dump(object_context.linkedLanguages) }}
-
+
+
I18n Context Look-Up (static route)
{% set object_context = i18n_create_context_by_static_route('my_static_route', { _locale: 'fr', object_id: 16 } }) %}
{{ dump(object_context) }}
{{ dump(object_context.linkedLanguages) }}
-## If you're using zones, you NEED to pass the site as context!
+{# If you're using zones, you NEED to pass the site as context! #}
I18n Context Look-Up with Zones (document)
{% set zone_aware_document_context = i18n_create_context_by_entity(pimcore_document(16), { _locale: 'en' }, pimcore_site_by_domain('my-site.test')) %}
@@ -30,10 +70,13 @@ In some cases, you want to retrieve all available links for a specific document
{{ dump(zone_aware_document_context.linkedLanguages) }}
```
+## Boot Context in PHP
+
```php
i18nContextManager = $i18nContextManager;
}
- public function build(Pimcore\Document $document, array $routeParameter)
+ public function build(Pimcore\DataObject $object, array $routeParameter)
{
+ ## I. no zones defined
$parameters = [
'routeParameters' => $routeParameter,
- 'entity' => $document
+ 'entity' => $object
];
- $i18nContext = $this->i18nContextManager->buildContextByParameters($parameters, true);
-
- ## If you're using zones, you NEED to pass the site as context!
+ // third argument needs to be true to fully boot context (initialize linked zone sites)
+ $i18nContext = $this->i18nContextManager->buildContextByParameters(RouteItemInterface::STATIC_ROUTE, $parameters, true);
- $zoneAwareParameters = [
+ ## II. zones available
+ ## You MUST pass the site as context!
+ $zoneAwareParameters = [
'routeParameters' => $routeParameter,
- 'entity' => $document,
+ 'entity' => $object,
'context' => [
'site' => $site
]
];
- $zoneAwareI18nContext = $this->i18nContextManager->buildContextByParameters($zoneAwareParameters, true);
+ $zoneAwareI18nContext = $this->i18nContextManager->buildContextByParameters(RouteItemInterface::STATIC_ROUTE, $zoneAwareParameters, true);
dump($zoneAwareI18nContext);
}
diff --git a/docs/60_CodeExamples.md b/docs/60_CodeExamples.md
index e9de24b..1cf2422 100644
--- a/docs/60_CodeExamples.md
+++ b/docs/60_CodeExamples.md
@@ -20,66 +20,40 @@
| getLanguageNameByIsoCode | helper to get language name by iso code |
| getCountryNameByIsoCode | helper to get country name by iso code |
-## Zone
-The zone represents an instance of `I18nZoneInterface` which comes with some helper methods:
-
-| Name | Description |
-|------|-------------|
-| getId | given zone id (null, if it no zones have been configured |
-| getName | given zone name (null, if no zones have been configured |
-| getDomains | all available domains for given zone. |
-| getMode | returns `language` or `country` |
-| getTranslations | array, translations for dynamic routes |
-| isActiveZone | check if zone is active one |
-| getLocaleUrlMapping | For example: `de-ch`. Mostly used to build [static routes](./28_StaticRoutes.md#naming-convention-in-country-context) |
-| getGlobalInfo | international state |
-| getSites | get all corresponding sites |
-
-**Twig**
+### Fetch Context Data in Twig
```twig
{#
- be careful, i18n_context is allowed to return null!
+ be careful, i18n_current_context is allowed to return null!
#}
-{% set current_locale = i18n_context().localeDefinition.locale %}
-{% set current_language_iso = i18n_context().localeDefinition.languageIso %}
-{% set current_country_iso = i18n_context().localeDefinition.countryIso %}
+{% set current_locale = i18n_current_context().localeDefinition.locale %}
+{% set current_language_iso = i18n_current_context().localeDefinition.languageIso %}
+{% set current_country_iso = i18n_current_context().localeDefinition.countryIso %}
-{{ dump(i18n_context().mode) }}
-{{ dump(i18n_context().localeDefinition.locale) }}
-{{ dump(i18n_context().linkedLanguages) }}
-{{ dump(i18n_context().activeLanguages) }}
-{{ dump(i18n_context().activeCountries) }}
-{{ dump(i18n_context().languageNameByIsoCode(current_language_iso, current_locale)) }}
-{{ dump(i18n_context().countryNameByIsoCode(current_country_iso, current_locale)) }}
+{{ dump(i18n_current_context().localeDefinition.locale) }}
+{{ dump(i18n_current_context().linkedLanguages) }}
+{{ dump(i18n_current_context().activeLanguages) }}
+{{ dump(i18n_current_context().activeCountries) }}
+{{ dump(i18n_current_context().languageNameByIsoCode(current_language_iso, current_locale)) }}
+{{ dump(i18n_current_context().countryNameByIsoCode(current_country_iso, current_locale)) }}
-{{ dump(i18n_context().zoneActiveLocales()) }}
-{{ dump(i18n_context().localeInfo('de', 'id')) }}
+{{ dump(i18n_current_context().zoneActiveLocales()) }}
+{{ dump(i18n_current_context().localeInfo('de', 'id')) }}
```
-**PHP**
+### Fetch Context Data in PHP
```php
requestStack = $requestStack;
- $this->i18nContextResolver = $i18nContextResolver;
- }
-
- public function getInformation()
- {
- $i18nContext = $this->i18nContextResolver->getContext($this->requestStack->getMainRequest());
+ $i18nContext = $i18nContextResolver->getContext($requestStack->getMainRequest());
- $mode = $i18nContext->getZone()->getMode();
$LinkedLanguages = $i18nContext->getLinkedLanguages();
// get current locale
@@ -104,6 +78,57 @@ class ExampleService
}
```
+## Zone
+The zone represents an instance of `I18nZoneInterface` which comes with some helper methods:
+
+| Name | Description |
+|------|-------------|
+| getId | given zone id (null, if it no zones have been configured |
+| getName | given zone name (null, if no zones have been configured |
+| getDomains | all available domains for given zone. |
+| getMode | returns `language` or `country` |
+| getTranslations | array, translations for dynamic routes |
+| isActiveZone | check if zone is active one |
+| getLocaleUrlMapping | For example: `de-ch`. Mostly used to build [static routes](./28_StaticRoutes.md#naming-convention-in-country-context) |
+| getGlobalInfo | international state |
+| getSites | get all corresponding sites |
+
+
+### Fetch Zone Data in Twig
+```twig
+{#
+ be careful, i18n_current_context is allowed to return null!
+#}
+
+{{ dump(i18n_current_context().zone.mode) }}
+{{ dump(i18n_current_context().zone.sites) }}
+
+```
+
+### Fetch Zone Data in PHP
+```php
+getContext($requestStack->getMainRequest());
+
+ $zone = $i18nContext->getZone();
+
+ // get zone mode (language or country
+ $mode = $zone->getMode();
+
+ // list all zone sites
+ $zoneSites = $zone->getSites(true);
+ }
+}
+```
+
### Zone Current Site Information
To retrieve data from an active site, you may want to use the `$i18nContext->getCurrentZoneSite()` method.
Since the current site gets defined via the current locale, be sure that locale is always available.
@@ -132,47 +157,39 @@ The current site represents an instance of `ZoneSiteInterface` which comes with
| getSubSites | array |
| hasSubSites | bool |
-**Twig**
+### Fetch Zone Site Data in Twig
```twig
{# get current context info #}
-{{ dump(i18n_context().currentZoneSite.url) }}
-{{ dump(i18n_context().currentZoneSite.localeUrlMapping) }}
+{{ dump(i18n_current_context().currentZoneSite.url) }}
+{{ dump(i18n_current_context().currentZoneSite.localeUrlMapping) }}
```
-**PHP**
+### Fetch Zone Site Data in Twig
```php
requestStack = $requestStack;
- $this->i18nContextResolver = $i18nContextResolver;
- }
-
- public function getInformation()
- {
- $i18nContext = $this->i18nContextResolver->getContext($this->requestStack->getMainRequest());
+ $i18nContext = $i18nContextResolver->getContext($requestStack->getMainRequest());
$currentContextInfo = $i18nContext->getCurrentZoneSite()->getUrl();
$currentContextInfo = $i18nContext->getCurrentZoneSite()->getLocaleUrlMapping();
}
}
```
+
## Navigation Examples
### Language Drop-Down
```twig
-{% set i18n_context = i18n_context() %}
-{% if i18n_context is not null %}
- {% set languages = i18n_context.activeLanguages %}
+{% set current_context = i18n_current_context() %}
+{% if current_context is not null %}
+ {% set languages = current_context.activeLanguages %}
{% if languages is iterable %}
@@ -187,9 +204,9 @@ class ExampleService
### Country Selection
```twig
-{% set i18n_context = i18n_context() %}
-{% if i18n_context is not null %}
- {% set countries = i18n_context.activeCountries %}
+{% set current_context = i18n_current_context() %}
+{% if current_context is not null %}
+ {% set countries = current_context.activeCountries %}
{% if countries is iterable %}
{% for country in countries %}
@@ -211,10 +228,10 @@ class ExampleService
### Complex Country / Language Selection based on Current Zone
```twig
- {% set i18n_context = i18n_context() %}
- {% if i18n_context is not null %}
- {% if i18n_context.zone.mode == 'country' %}
- {% set countries = i18n_context.activeCountries %}
+ {% set current_context = i18n_current_context() %}
+ {% if current_context is not null %}
+ {% if current_context.zone.mode == 'country' %}
+ {% set countries = current_context.activeCountries %}
{% if countries is iterable %}
{% for country in countries %}
@@ -228,8 +245,8 @@ class ExampleService
{% endfor %}
{% endif %}
- {% elseif i18n_context.zone.mode == 'language' %}
- {% set languages = i18n_context.activeLanguages %}
+ {% elseif current_context.zone.mode == 'language' %}
+ {% set languages = current_context.activeLanguages %}
{% if languages is iterable %}
{% for language in languages %}
diff --git a/docs/90_DocumentRoutes.md b/docs/90_DocumentRoutes.md
new file mode 100644
index 0000000..29eca15
--- /dev/null
+++ b/docs/90_DocumentRoutes.md
@@ -0,0 +1,54 @@
+# Document Routes
+
+## Generating Routes in current request
+To create document paths/urls in **current request** via Twig or PHP API
+you may want to use the `_i18n` parameter builder:
+
+### Twig
+```twig
+{# relative #}
+{{ dump( i18n_entity_route(pimcore_document(46), {}, false) ) }}
+
+{# absolute #}
+{{ dump( i18n_entity_route(pimcore_document(46), {}, true) ) }}
+```
+
+### PHP
+```php
+use I18nBundle\Builder\RouteParameterBuilder;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+public function myAction(Request $request)
+{
+ $parameters = RouteParameterBuilder::buildForEntityWithRequest(
+ \Pimcore\Model\Document::getById(20),
+ [],
+ $request
+ );
+
+ return $this->urlGenerator->generate('', $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
+}
+```
+
+## Generating Routes in CLI
+To create document paths/urls in **headless** context:
+
+### PHP
+```php
+use I18nBundle\Builder\RouteParameterBuilder;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+protected function execute(InputInterface $input, OutputInterface $output): int
+{
+ $parameters = RouteParameterBuilder::buildForEntity(
+ \Pimcore\Model\Document::getById(20),
+ [],
+ []
+ );
+
+ return $this->urlGenerator->generate('', $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
+}
+```
\ No newline at end of file
diff --git a/docs/28_StaticRoutes.md b/docs/91_StaticRoutes.md
similarity index 74%
rename from docs/28_StaticRoutes.md
rename to docs/91_StaticRoutes.md
index 2e8388b..6d3945c 100644
--- a/docs/28_StaticRoutes.md
+++ b/docs/91_StaticRoutes.md
@@ -1,6 +1,81 @@
# Static Routes
The I18nBundle will help you to create translatable static routes and will also help you to generate valid alternate links.
+## Generating Routes in current request
+To create static route paths/urls in **current request** via Twig or PHP API
+you may want to use the `_i18n` parameter builder:
+
+### Twig
+```twig
+{# relative [by static route name] #}
+{{ dump( i18n_static_route('my_static_route', { news: 'my-attribute' }, true) ) }}
+
+{# absolute [by static route name] #}
+{{ dump( i18n_static_route('my_static_route', { news: 'my-attribute' }, true) ) }}
+
+{# relative [object with link generator] #}
+{{ dump( i18n_entity_route(pimcore_object(20), {}, false) ) }}
+
+{# absolute [object with link generator] #}
+{{ dump( i18n_entity_route(pimcore_object(20), {}, true) ) }}
+```
+
+### PHP
+```php
+use I18nBundle\Builder\RouteParameterBuilder;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+public function myAction(Request $request)
+{
+ $parameters = RouteParameterBuilder::buildForEntityWithRequest(
+ \Pimcore\Model\DataObject::getById(20),
+ [],
+ $request
+ );
+
+ $linkGeneratorStaticRoute = $this->urlGenerator->generate('', $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
+
+ $parameters = RouteParameterBuilder::buildForStaticRouteWithRequest(
+ ['news' => 'my-attribute'],
+ $request
+ );
+
+ $instantStaticRoute = $this->urlGenerator->generate('my_static_route', $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
+}
+```
+
+## Generating Routes in CLI
+To create static route paths/urls in **headless** context:
+
+### PHP
+```php
+use I18nBundle\Builder\RouteParameterBuilder;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+protected function execute(InputInterface $input, OutputInterface $output): int
+{
+ $parameters = RouteParameterBuilder::buildForEntity(
+ \Pimcore\Model\DataObject::getById(20),
+ ['_locale' => 'en'],
+ ['site' => Site::getByDomain('test-domain1.test')]
+ );
+
+ $linkGeneratorStaticRoute = $this->urlGenerator->generate('', $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
+
+ $parameters = RouteParameterBuilder::buildForStaticRoute(
+ ['_locale' => 'en', 'news' => 'my-attribute'],
+ ['site' => Site::getByDomain('test-domain1.test')]
+ );
+
+ $instantStaticRoute = $this->urlGenerator->generate('my_static_route', $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
+
+ return 0;
+}
+```
+
## Translatable Fragments
Creating translatable static routes are quite a challenge in pimcore.
We'll show you how to create them based on our [Pimcore News Bundle](https://github.com/dachcom-digital/pimcore-news).
@@ -75,8 +150,8 @@ That was easy! Now we need to check the static routes again - because the patter
Pimcore allows [optional placeholders](https://pimcore.com/docs/5.0.x/MVC/Routing_and_URLs/Custom_Routes.html#page_Building_URLs_based_on_Custom_Routes) so instead of `%_locale` just add `{%_locale}` to your reverse element.
If no locale has been found in your request url the fragment now gets excluded.
-## href-lang Generator
-Now let's create an event listener to generate valid alternate links for our news entries:
+## Alternate Links Generator
+Then need to register an alternate listener and its corresponding service.
```html
@@ -85,7 +160,6 @@ Now let's create an event listener to generate valid alternate links for our new
```
-First you need to register a service:
```yaml
App\EventListener\I18nRoutesAlternateListener:
autowire: true
@@ -93,7 +167,6 @@ App\EventListener\I18nRoutesAlternateListener:
- { name: kernel.event_subscriber }
```
-Now implement the event listener itself:
```php
**Note:** Of course it's still possible to use iso code formatted url structures if you really want to do that. :)
-
-## Creating Static Routes in Twig
-Nothing special here. Just create your url like you know it from the twig standard and pass your parameters via the `_18n` flag.
-I18n will transform your locale fragment, if necessary:
-
-```twig
-{{ url('your_static_route', { _i18n: { routeParameters: { _locale: app.request.locale, param1: param1 } } }) }}
-```
\ No newline at end of file
+> **Note:** Of course it's still possible to use iso code formatted url structures if you really want to do that. :)
\ No newline at end of file
diff --git a/docs/29_SymfonyRoutes.md b/docs/92_SymfonyRoutes.md
similarity index 54%
rename from docs/29_SymfonyRoutes.md
rename to docs/92_SymfonyRoutes.md
index 618f8d7..92eefe4 100644
--- a/docs/29_SymfonyRoutes.md
+++ b/docs/92_SymfonyRoutes.md
@@ -23,7 +23,62 @@ i18n_symfony_route:
matching_route_key: '(%i18n.route.translations.mySymfonyRouteKey%)' ## returns (meine-symfony-route|my-symfony-route)
```
-Then need to register an alternate listener:
+## Generating Routes in current request
+To create symfony paths/urls in **current request** via Twig or PHP API
+you may want to use the `_i18n` parameter builder:
+
+### Twig
+```twig
+{# relative #}
+{{ dump( i18n_symfony_route('i18n_symfony_route', {foo: bar}, false) ) }}
+
+{# absolute #}
+{{ dump( i18n_symfony_route('i18n_symfony_route', {foo: bar}, true) ) }}
+```
+
+### PHP
+```php
+use I18nBundle\Builder\RouteParameterBuilder;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+public function myAction(Request $request)
+{
+ $parameters = RouteParameterBuilder::buildForSymfonyRouteWithRequest(
+ ['foo' => 'bar'],
+ $request
+ );
+
+ $symfonyRoute = $this->urlGenerator->generate('i18n_symfony_route', $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
+}
+```
+
+## Generating Routes in CLI
+To create symfony paths/urls in **headless** context:
+
+### PHP
+```php
+use I18nBundle\Builder\RouteParameterBuilder;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+protected function execute(InputInterface $input, OutputInterface $output): int
+{
+ $parameters = RouteParameterBuilder::buildForSymfonyRoute(
+ ['foo' => 'bar'],
+ ['site' => Site::getByDomain('test-domain1.test')]
+ );
+
+ $symfonyRoute = $this->urlGenerator->generate('i18n_symfony_route', $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
+
+ return 0;
+}
+```
+
+## Alternate Links
+Then need to register an alternate listener and its corresponding service.
+
```yaml
App\EventListener\I18nRoutesAlternateListener:
autowire: true
@@ -31,7 +86,6 @@ App\EventListener\I18nRoutesAlternateListener:
- { name: kernel.event_subscriber }
```
-Now implement the event listener itself:
```php
router = $router;
+ }
+
public function configureOptions(OptionsResolver $options): void
{
$options
@@ -31,9 +40,9 @@ public function getUrls(I18nContextInterface $i18nContext, bool $onlyShowRootLan
}
if ($i18nContext->getZone()->getMode() === 'language') {
- $urls = $this->documentUrlsFromLanguage($i18nContext->getZone(), $document, $onlyShowRootLanguages);
+ $urls = $this->documentUrlsFromLanguage($i18nContext, $document, $onlyShowRootLanguages);
} else {
- $urls = $this->documentUrlsFromCountry($i18nContext->getZone(), $document, $onlyShowRootLanguages);
+ $urls = $this->documentUrlsFromCountry($i18nContext, $document, $onlyShowRootLanguages);
}
$this->cachedUrls[$document->getId()] = $urls;
@@ -41,15 +50,11 @@ public function getUrls(I18nContextInterface $i18nContext, bool $onlyShowRootLan
return $urls;
}
- private function documentUrlsFromLanguage(ZoneInterface $zone, PimcoreDocument $document, bool $onlyShowRootLanguages = false): array
+ private function documentUrlsFromLanguage(I18nContextInterface $i18nContext, PimcoreDocument $document, bool $onlyShowRootLanguages = false): array
{
$routes = [];
-
- try {
- $zoneSites = $zone->getSites(true);
- } catch (\Exception $e) {
- return [];
- }
+ $zoneSites = $i18nContext->getZone()->getSites(true);
+ $routeItem = $i18nContext->getRouteItem();
$rootDocumentIndexId = array_search($document->getId(), array_map(static function (ZoneSiteInterface $site) {
return $site->getRootId();
@@ -70,7 +75,6 @@ private function documentUrlsFromLanguage(ZoneInterface $zone, PimcoreDocument $
'locale' => $zoneSite->getLocale(),
'hrefLang' => $zoneSite->getHrefLang(),
'localeUrlMapping' => $zoneSite->getLocaleUrlMapping(),
- 'key' => $document->getKey(),
'url' => $zoneSite->getUrl()
];
}
@@ -100,25 +104,13 @@ private function documentUrlsFromLanguage(ZoneInterface $zone, PimcoreDocument $
continue;
}
- if ($this->hasPrettyUrl($document) === true) {
- $relativePath = $document->getPrettyUrl();
- $url = System::joinPath([$zoneSite->getSiteRequestContext()->getDomainUrl(), $relativePath]);
- } else {
- // map paths
- $documentPath = $document->getRealPath() . $document->getKey();
- $relativePath = preg_replace('/^' . preg_quote($zoneSite->getFullPath(), '/') . '/', '', $documentPath);
- $url = System::joinPath([$zoneSite->getUrl(), $relativePath]);
- }
-
$routes[] = [
'languageIso' => $zoneSite->getLanguageIso(),
'countryIso' => null,
'locale' => $zoneSite->getLocale(),
'hrefLang' => $zoneSite->getHrefLang(),
'localeUrlMapping' => $zoneSite->getLocaleUrlMapping(),
- 'key' => $document->getKey(),
- 'relativePath' => $relativePath,
- 'url' => $url
+ 'url' => $this->generateLink($routeItem, $document)
];
}
}
@@ -126,22 +118,17 @@ private function documentUrlsFromLanguage(ZoneInterface $zone, PimcoreDocument $
return $routes;
}
- private function documentUrlsFromCountry(ZoneInterface $zone, PimcoreDocument $document, bool $onlyShowRootLanguages = false): array
+ private function documentUrlsFromCountry(I18nContextInterface $i18nContext, PimcoreDocument $document, bool $onlyShowRootLanguages = false): array
{
$routes = [];
-
- try {
- $zoneSites = $zone->getSites(true);
- } catch (\Exception $e) {
- return [];
- }
+ $zoneSites = $i18nContext->getZone()->getSites(true);
+ $routeItem = $i18nContext->getRouteItem();
$rootDocumentIndexId = array_search($document->getId(), array_map(static function (ZoneSiteInterface $site) {
return $site->getRootId();
}, $zoneSites), true);
if ($onlyShowRootLanguages === true || $rootDocumentIndexId !== false) {
-
foreach ($zoneSites as $zoneSite) {
if (!empty($zoneSite->getCountryIso())) {
$routes[] = [
@@ -159,7 +146,9 @@ private function documentUrlsFromCountry(ZoneInterface $zone, PimcoreDocument $d
return $routes;
}
- $hardLinkZoneSitesToCheck = [];
+ $virtualProxyZoneSites = [];
+ $virtualProxyZoneSiteDocuments = [];
+
$service = new PimcoreDocument\Service();
$translations = $service->getTranslations($document);
@@ -188,7 +177,7 @@ private function documentUrlsFromCountry(ZoneInterface $zone, PimcoreDocument $d
// if page info is type of "hardlink", we need to add them to a second check
if (!isset($translations[$pageInfoLocale])) {
if ($zoneSite->getType() === 'hardlink') {
- $hardLinkZoneSitesToCheck[] = $zoneSite;
+ $virtualProxyZoneSites[] = $zoneSite;
}
continue;
}
@@ -204,71 +193,63 @@ private function documentUrlsFromCountry(ZoneInterface $zone, PimcoreDocument $d
continue;
}
- $hasPrettyUrl = false;
- if ($this->hasPrettyUrl($document) === true) {
- $hasPrettyUrl = true;
- $relativePath = $document->getPrettyUrl();
- $url = System::joinPath([$zoneSite->getSiteRequestContext()->getDomainUrl(), $relativePath]);
- } else {
- //map paths
- $documentPath = $document->getRealPath() . $document->getKey();
- $relativePath = preg_replace('/^' . preg_quote($zoneSite->getFullPath(), '/') . '/', '', $documentPath);
- $url = System::joinPath([$zoneSite->getUrl(), $relativePath]);
- }
-
$routes[] = [
'languageIso' => $zoneSite->getLanguageIso(),
'countryIso' => $zoneSite->getCountryIso(),
'locale' => $zoneSite->getLocale(),
'hrefLang' => $zoneSite->getHrefLang(),
'localeUrlMapping' => $zoneSite->getLocaleUrlMapping(),
- 'key' => $document->getKey(),
- 'relativePath' => $relativePath,
- 'hasPrettyUrl' => $hasPrettyUrl,
- 'url' => $url
+ 'url' => $this->generateLink($routeItem, $document)
];
+
+ $virtualProxyZoneSiteDocuments[] = $document;
}
- if (count($hardLinkZoneSitesToCheck) === 0) {
+ if (count($virtualProxyZoneSites) === 0) {
return $routes;
}
- foreach ($hardLinkZoneSitesToCheck as $hardLinkZoneSiteWrapper) {
+ foreach ($virtualProxyZoneSites as $virtualProxyZoneSite) {
- $sameLanguageContext = array_search($hardLinkZoneSiteWrapper->getLanguageIso(), array_column($routes, 'languageIso'), true);
- if ($sameLanguageContext === false || !isset($routes[$sameLanguageContext])) {
+ $sameLanguageContextIndex = array_search($virtualProxyZoneSite->getLanguageIso(), array_column($routes, 'languageIso'), true);
+ if ($sameLanguageContextIndex === false) {
continue;
}
- $languageContext = $routes[$sameLanguageContext];
- $posGlobalPath = System::joinPath([$hardLinkZoneSiteWrapper->getFullPath(), $languageContext['relativePath']]);
-
- // case 1: only add hardlinks check if document has no pretty url => we can't guess pretty urls by magic.
- // case 2: always continue: could be disabled or isn't linked via translations.
- if ($languageContext['hasPrettyUrl'] === true || PimcoreDocument\Service::pathExists($posGlobalPath)) {
+ try {
+ $virtualProxyUrl = $this->generateLink($routeItem, $virtualProxyZoneSiteDocuments[$sameLanguageContextIndex], $virtualProxyZoneSite);
+ } catch (VirtualProxyPathException $e) {
continue;
}
$routes[] = [
- 'languageIso' => $hardLinkZoneSiteWrapper->getLanguageIso(),
- 'countryIso' => $hardLinkZoneSiteWrapper->getCountryIso(),
- 'locale' => $hardLinkZoneSiteWrapper->getLocale(),
- 'hrefLang' => $hardLinkZoneSiteWrapper->getHrefLang(),
- 'localeUrlMapping' => $hardLinkZoneSiteWrapper->getLocaleUrlMapping(),
- 'key' => $languageContext['key'],
- 'url' => System::joinPath([$hardLinkZoneSiteWrapper->getUrl(), $languageContext['relativePath']])
+ 'languageIso' => $virtualProxyZoneSite->getLanguageIso(),
+ 'countryIso' => $virtualProxyZoneSite->getCountryIso(),
+ 'locale' => $virtualProxyZoneSite->getLocale(),
+ 'hrefLang' => $virtualProxyZoneSite->getHrefLang(),
+ 'localeUrlMapping' => $virtualProxyZoneSite->getLocaleUrlMapping(),
+ 'url' => $virtualProxyUrl
];
}
return $routes;
}
- private function hasPrettyUrl(PimcoreDocument $document): bool
+ protected function generateLink(RouteItemInterface $routeItem, PimcoreDocument $document, ?ZoneSiteInterface $virtualProxyZoneSite = null): string
{
- if ($document instanceof PimcoreDocument\Page) {
- return !empty($document->getPrettyUrl()) && $document->getPrettyUrl() !== '';
- }
-
- return false;
+ $routeParameters = [
+ Definitions::ATTRIBUTE_I18N_ROUTE_IDENTIFIER => [
+ 'type' => RouteItemInterface::DOCUMENT_ROUTE,
+ 'entity' => $document,
+ 'routeName' => '',
+ 'routeParameters' => [],
+ 'routeAttributes' => $routeItem->getRouteAttributes(),
+ 'context' => $virtualProxyZoneSite instanceof ZoneSiteInterface
+ ? ['virtualProxyZoneSite' => $virtualProxyZoneSite]
+ : []
+ ]
+ ];
+
+ return $this->router->generate('', $routeParameters, UrlGeneratorInterface::ABSOLUTE_URL);
}
}
diff --git a/src/I18nBundle/Adapter/PathGenerator/DynamicRoute.php b/src/I18nBundle/Adapter/PathGenerator/DynamicRoute.php
index 77a406b..f8b342b 100644
--- a/src/I18nBundle/Adapter/PathGenerator/DynamicRoute.php
+++ b/src/I18nBundle/Adapter/PathGenerator/DynamicRoute.php
@@ -20,7 +20,6 @@ protected function buildAlternateRoutesStack(I18nContextInterface $i18nContext,
$alternateRouteItems = [];
$routes = [];
- //create custom list for event ($i18nList) - do not include all the zone config stuff.
foreach ($i18nContext->getZone()->getSites(true) as $zoneSite) {
if (!empty($zoneSite->getLanguageIso())) {
$alternateRouteItems[] = $this->alternateRouteItemTransformer->transform(
diff --git a/src/I18nBundle/Builder/RouteItemBuilder.php b/src/I18nBundle/Builder/RouteItemBuilder.php
index 9d01c34..267a5d3 100644
--- a/src/I18nBundle/Builder/RouteItemBuilder.php
+++ b/src/I18nBundle/Builder/RouteItemBuilder.php
@@ -247,15 +247,4 @@ protected function assertValidSymfonyRoute(RouteItemInterface $routeItem): void
$routeItem->getRouteAttributesBag()->set('_i18n_translation_keys', $i18nDefaults['translation_keys']);
}
}
-
- protected function assertRouteContext(RouteItemInterface $routeItem, ZoneSiteInterface $site): void
- {
- $routeItem->getRouteContextBag()->add([
- 'host' => $site->getSiteRequestContext()->getHost(),
- 'scheme' => $site->getSiteRequestContext()->getScheme(),
- 'httpPort' => $site->getSiteRequestContext()->getHttpPort(),
- 'httpsPort' => $site->getSiteRequestContext()->getHttpsPort(),
- ]);
- }
-
}
diff --git a/src/I18nBundle/Builder/RouteParameterBuilder.php b/src/I18nBundle/Builder/RouteParameterBuilder.php
new file mode 100644
index 0000000..79d675d
--- /dev/null
+++ b/src/I18nBundle/Builder/RouteParameterBuilder.php
@@ -0,0 +1,100 @@
+ $routeParameter,
+ 'context' => $context
+ ];
+
+ if ($element !== null) {
+ if ($element instanceof Document) {
+ $routeType = RouteItemInterface::DOCUMENT_ROUTE;
+ } elseif ($element instanceof AbstractObject) {
+ $routeType = RouteItemInterface::STATIC_ROUTE;
+ } else {
+ throw new \Exception('Cannot build route parameters for entity "%"', get_class($element));
+ }
+
+ $params['entity'] = $element;
+ }
+
+ if ($routeType === null) {
+ throw new \Exception('Cannot build route parameters because of unknown rout type');
+ }
+
+ $params['type'] = $routeType;
+
+ if (!$request instanceof Request) {
+ return [Definitions::ATTRIBUTE_I18N_ROUTE_IDENTIFIER => $params];
+ }
+
+ if ($routeType === RouteItemInterface::DOCUMENT_ROUTE) {
+ return [Definitions::ATTRIBUTE_I18N_ROUTE_IDENTIFIER => $params];
+ }
+
+ if ($request->attributes->has(SiteResolver::ATTRIBUTE_SITE)) {
+ $params['context']['site'] = $request->attributes->get(SiteResolver::ATTRIBUTE_SITE);
+ } elseif (Tool::isFrontendRequestByAdmin($request) && $request->attributes->has(DynamicRouter::CONTENT_KEY)) {
+ $params['context']['site'] = Frontend::getSiteForDocument($request->attributes->get(DynamicRouter::CONTENT_KEY));
+ }
+
+ if ($request->attributes->has('_locale')) {
+ $params['routeParameters']['_locale'] = $request->getLocale();
+ }
+
+ return [Definitions::ATTRIBUTE_I18N_ROUTE_IDENTIFIER => $params];
+ }
+
+}
diff --git a/src/I18nBundle/Builder/ZoneSitesBuilder.php b/src/I18nBundle/Builder/ZoneSitesBuilder.php
index 7f86245..39810dc 100644
--- a/src/I18nBundle/Builder/ZoneSitesBuilder.php
+++ b/src/I18nBundle/Builder/ZoneSitesBuilder.php
@@ -4,12 +4,14 @@
use I18nBundle\Configuration\Configuration;
use I18nBundle\Definitions;
+use I18nBundle\Model\RouteItem\RouteItemInterface;
use I18nBundle\Model\ZoneSite;
use I18nBundle\Model\ZoneSiteInterface;
use I18nBundle\Model\ZoneInterface;
use I18nBundle\Model\SiteRequestContext;
use Pimcore\Db\Connection;
use Pimcore\Model\Document;
+use Pimcore\Model\Site;
class ZoneSitesBuilder
{
@@ -27,25 +29,34 @@ public function __construct(
$this->configuration = $configuration;
}
- public function buildZoneSites(ZoneInterface $zone, bool $isFrontendRequestByAdmin = false): array
+ public function buildZoneSites(ZoneInterface $zone, RouteItemInterface $routeItem, bool $fullBootstrap = false, bool $isFrontendRequestByAdmin = false): array
{
$zoneSites = [];
- $availableSites = $this->fetchAvailableSites();
+ $routeItemLocale = $routeItem->getLocaleFragment();
+
+ if ($fullBootstrap === false) {
+ // we don't have a zone id, so no site context is needed!
+ if ($zone->getId() === null) {
+ $virtualZoneSite = $this->buildVirtualZoneSite();
+ $zoneSites[] = $this->createZoneSite($zone, $isFrontendRequestByAdmin, $virtualZoneSite['mainDomain'], $virtualZoneSite['rootId'], $routeItemLocale, $fullBootstrap);
+ } else {
+ /** @var Site $site */
+ $site = $routeItem->getRouteContextBag()->get('site');
+ $zoneSites[] = $this->createZoneSite($zone, $isFrontendRequestByAdmin, $site->getMainDomain(), $site->getRootId(), $routeItemLocale, $fullBootstrap);
+ }
- //it's a simple page, no sites: create a default one
- if (count($availableSites) === 0) {
+ return $zoneSites;
+ }
- $hostUrl = !empty($this->generalDomain) && $this->generalDomain !== 'localhost' ? $this->generalDomain : \Pimcore\Tool::getHostUrl();
- $realHostUrl = parse_url($hostUrl, PHP_URL_HOST);
+ $availableSites = $this->fetchAvailableSites();
- $availableSites[] = [
- 'mainDomain' => $realHostUrl,
- 'rootId' => 1
- ];
+ //it's a simple page, no sites: create a virtual one
+ if (count($availableSites) === 0) {
+ $availableSites[] = $this->buildVirtualZoneSite();
}
foreach ($availableSites as $site) {
- $zoneSite = $this->createZoneSite($zone, $isFrontendRequestByAdmin, $site['mainDomain'], $site['rootId']);
+ $zoneSite = $this->createZoneSite($zone, $isFrontendRequestByAdmin, $site['mainDomain'], $site['rootId'], $routeItemLocale, $fullBootstrap);
if ($zoneSite instanceof ZoneSiteInterface) {
$zoneSites[] = $zoneSite;
}
@@ -58,23 +69,26 @@ protected function createZoneSite(
ZoneInterface $zone,
bool $isFrontendRequestByAdmin,
string $mainDomain,
- int $rootId
+ int $rootId,
+ ?string $routeItemLocale,
+ bool $fullBootstrap
): ?ZoneSiteInterface {
+ $subPages = [];
$domainDoc = Document::getById($rootId);
if (!$domainDoc instanceof Document) {
return null;
}
- $currentZoneDomainConfiguration = null;
+ $zoneDomainConfiguration = null;
$valid = $zone->getId() === null;
if ($zone->getId() !== null && !empty($zone->getDomains())) {
- foreach ($zone->getDomains() as $currentZoneDomain) {
- $currentZoneDomainHost = is_array($currentZoneDomain) ? $currentZoneDomain[0] : $currentZoneDomain;
+ foreach ($zone->getDomains() as $zoneDomain) {
+ $currentZoneDomainHost = is_array($zoneDomain) ? $zoneDomain[0] : $zoneDomain;
if ($mainDomain === $currentZoneDomainHost) {
- $currentZoneDomainConfiguration = $currentZoneDomain;
+ $zoneDomainConfiguration = $zoneDomain;
$valid = true;
break;
@@ -82,170 +96,199 @@ protected function createZoneSite(
}
}
- $siteRequestContext = $this->generateSiteRequestContext($mainDomain, $currentZoneDomainConfiguration);
-
$isPublishedMode = $domainDoc->isPublished() === true || $isFrontendRequestByAdmin;
if ($valid === false || $isPublishedMode === false) {
return null;
}
- $isRootDomain = false;
- $subPages = [];
-
- $docLocale = $domainDoc->getProperty('language');
+ $hrefLang = '';
+ $docRealLanguageIso = '';
$docCountryIso = null;
+ $docLocale = $domainDoc->getProperty('language');
+ $siteRequestContext = $this->generateSiteRequestContext($mainDomain, $zoneDomainConfiguration);
- if ($zone->getMode() === 'country' && !empty($docLocale)) {
- $docCountryIso = Definitions::INTERNATIONAL_COUNTRY_NAMESPACE;
- }
+ if (!empty($docLocale)) {
- if (str_contains($docLocale, '_')) {
- $parts = explode('_', $docLocale);
- if (isset($parts[1]) && !empty($parts[1])) {
- $docCountryIso = $parts[1];
+ if ($zone->getMode() === 'country') {
+ $docCountryIso = Definitions::INTERNATIONAL_COUNTRY_NAMESPACE;
}
- }
- //domain has language, it's the root.
- if (!empty($docLocale)) {
- $isRootDomain = true;
- if (!in_array($docLocale, array_column($zone->getActiveLocales(), 'locale'), true)) {
- return null;
+ if (str_contains($docLocale, '_')) {
+ $parts = explode('_', $docLocale);
+ if (isset($parts[1]) && !empty($parts[1])) {
+ $docCountryIso = $parts[1];
+ }
+ }
+
+ $realLang = explode('_', $docLocale);
+ $docRealLanguageIso = $realLang[0];
+ $hrefLang = strtolower($docRealLanguageIso);
+ if (!empty($docCountryIso) && $docCountryIso !== Definitions::INTERNATIONAL_COUNTRY_NAMESPACE) {
+ $hrefLang .= '-' . strtolower($docCountryIso);
}
- } else {
- $children = $domainDoc->getChildren(true);
+ }
- foreach ($children as $child) {
+ // domain has locale property, so there are no subpages
+ $isRootDomain = !empty($docLocale);
- if (!in_array($child->getType(), ['page', 'hardlink', 'link'], true)) {
- continue;
- }
+ // if it's root domain, check if it's allowed for active locales
+ if ($isRootDomain === true && !in_array($docLocale, array_column($zone->getActiveLocales(), 'locale'), true)) {
+ return null;
+ }
+
+ // do not render sub pages if current domain is root domain
+ $subPages = $isRootDomain === true ? [] : $this->createSubSites($domainDoc, $zone, $siteRequestContext, $isFrontendRequestByAdmin, $routeItemLocale, $fullBootstrap);
+
+ return new ZoneSite(
+ $siteRequestContext,
+ $rootId,
+ $isRootDomain,
+ $docLocale === $routeItemLocale,
+ $docLocale,
+ $docCountryIso,
+ $docRealLanguageIso,
+ $hrefLang,
+ null,
+ $siteRequestContext->getDomainUrl(),
+ $siteRequestContext->getDomainUrl(),
+ $domainDoc->getRealFullPath(),
+ $domainDoc->getRealFullPath(),
+ $domainDoc->getType(),
+ $subPages
+ );
+ }
+
+ protected function createSubSites(
+ Document $domainDoc,
+ ZoneInterface $zone,
+ SiteRequestContext $siteRequestContext,
+ bool $isFrontendRequestByAdmin,
+ ?string $routeItemLocale,
+ bool $fullBootstrap
+ ): array {
- $urlKey = $child->getKey();
- $docUrl = $urlKey;
- $validPath = true;
- $loopDetector = [];
+ $subPages = [];
+ $children = $domainDoc->getChildren(true);
- //detect real doc url: if page is a link, move to target until we found a real document.
- if ($child->getType() === 'link') {
- /** @var Document\Link $linkChild */
- $linkChild = $child;
- while ($linkChild instanceof Document\Link) {
- if (in_array($linkChild->getPath(), $loopDetector, true)) {
- $validPath = false;
+ $processedChildLocales = [];
- break;
- }
+ foreach ($children as $child) {
- if ($linkChild->getLinktype() !== 'internal') {
- $validPath = false;
+ $validPath = true;
+ $loopDetector = [];
+ $childCountryIso = null;
+ $urlKey = $child->getKey();
+ $docUrl = $urlKey;
+ $childDocLocale = $child->getProperty('language');
- break;
- }
+ if (!in_array($child->getType(), ['page', 'hardlink', 'link'], true)) {
+ continue;
+ }
- if ($linkChild->getInternalType() !== 'document') {
- $validPath = false;
+ // we're only booting a specific locale. skip other subpage rendering if specific has been found!
+ if ($fullBootstrap === false && in_array($routeItemLocale, $processedChildLocales, true)) {
+ return $subPages;
+ }
- break;
- }
+ // we're only booting a specific locale. skip other subpage but parse only requested one!
+ if ($fullBootstrap === false && $routeItemLocale !== $childDocLocale) {
+ continue;
+ }
- $loopDetector[] = $linkChild->getPath();
- $linkChild = Document::getById($linkChild->getInternal());
+ //detect real doc url: if page is a link, move to target until we found a real document.
+ if ($child->getType() === 'link') {
+ /** @var Document\Link $linkChild */
+ $linkChild = $child;
+ while ($linkChild instanceof Document\Link) {
+ if (in_array($linkChild->getPath(), $loopDetector, true)) {
+ $validPath = false;
- if (!$linkChild instanceof Document) {
- $validPath = false;
+ break;
+ }
- break;
- }
+ if ($linkChild->getLinktype() !== 'internal') {
+ $validPath = false;
- $isPublishedMode = $linkChild->isPublished() === true || $isFrontendRequestByAdmin;
- if ($isPublishedMode === false) {
- $validPath = false;
+ break;
+ }
- break;
- }
+ if ($linkChild->getInternalType() !== 'document') {
+ $validPath = false;
- // we can't use getFullPath since i18n will transform the path since it could be a "out-of-context" link.
- $docUrl = ltrim($linkChild->getPath(), DIRECTORY_SEPARATOR) . $linkChild->getKey();
+ break;
}
- }
- $isPublishedMode = $child->isPublished() === true || $isFrontendRequestByAdmin;
- if ($validPath === false || $isPublishedMode === false) {
- continue;
- }
+ $loopDetector[] = $linkChild->getPath();
+ $linkChild = Document::getById($linkChild->getInternal());
- $childDocLocale = $child->getProperty('language');
- $childCountryIso = null;
+ if (!$linkChild instanceof Document) {
+ $validPath = false;
- if ($zone->getMode() === 'country') {
- $childCountryIso = Definitions::INTERNATIONAL_COUNTRY_NAMESPACE;
- }
+ break;
+ }
+
+ $isPublishedMode = $linkChild->isPublished() === true || $isFrontendRequestByAdmin;
+ if ($isPublishedMode === false) {
+ $validPath = false;
- if (str_contains($childDocLocale, '_')) {
- $parts = explode('_', $childDocLocale);
- if (isset($parts[1]) && !empty($parts[1])) {
- $childCountryIso = $parts[1];
+ break;
}
- }
- if (empty($childDocLocale) || !in_array($childDocLocale, array_column($zone->getActiveLocales(), 'locale'), true)) {
- continue;
+ // we can't use getFullPath since i18n will transform the path since it could be a "out-of-context" link.
+ $docUrl = ltrim($linkChild->getPath(), DIRECTORY_SEPARATOR) . $linkChild->getKey();
}
+ }
+
+ $isPublishedMode = $child->isPublished() === true || $isFrontendRequestByAdmin;
+ if ($validPath === false || $isPublishedMode === false) {
+ continue;
+ }
- $domainUrlWithKey = rtrim($siteRequestContext->getDomainUrl() . DIRECTORY_SEPARATOR . $urlKey, DIRECTORY_SEPARATOR);
- $homeDomainUrlWithKey = rtrim($siteRequestContext->getDomainUrl() . DIRECTORY_SEPARATOR . $docUrl, DIRECTORY_SEPARATOR);
+ if ($zone->getMode() === 'country') {
+ $childCountryIso = Definitions::INTERNATIONAL_COUNTRY_NAMESPACE;
+ }
- $realLang = explode('_', $childDocLocale);
- $hrefLang = strtolower($realLang[0]);
- if (!empty($childCountryIso) && $childCountryIso !== Definitions::INTERNATIONAL_COUNTRY_NAMESPACE) {
- $hrefLang .= '-' . strtolower($childCountryIso);
+ if (str_contains($childDocLocale, '_')) {
+ $parts = explode('_', $childDocLocale);
+ if (isset($parts[1]) && !empty($parts[1])) {
+ $childCountryIso = $parts[1];
}
+ }
- $subPages[] = new ZoneSite(
- $siteRequestContext,
- $child->getId(),
- false,
- $childDocLocale,
- $childCountryIso,
- $realLang[0],
- $hrefLang,
- $urlKey,
- $domainUrlWithKey,
- $homeDomainUrlWithKey,
- $child->getRealFullPath(),
- $child->getType()
- );
+ if (empty($childDocLocale) || !in_array($childDocLocale, array_column($zone->getActiveLocales(), 'locale'), true)) {
+ continue;
}
- }
- $hrefLang = '';
- $docRealLanguageIso = '';
+ $processedChildLocales[] = $childDocLocale;
- if (!empty($docLocale)) {
- $realLang = explode('_', $docLocale);
- $docRealLanguageIso = $realLang[0];
- $hrefLang = strtolower($docRealLanguageIso);
- if (!empty($docCountryIso) && $docCountryIso !== Definitions::INTERNATIONAL_COUNTRY_NAMESPACE) {
- $hrefLang .= '-' . strtolower($docCountryIso);
+ $domainUrlWithKey = rtrim($siteRequestContext->getDomainUrl() . DIRECTORY_SEPARATOR . $urlKey, DIRECTORY_SEPARATOR);
+ $homeDomainUrlWithKey = rtrim($siteRequestContext->getDomainUrl() . DIRECTORY_SEPARATOR . $docUrl, DIRECTORY_SEPARATOR);
+
+ $realLang = explode('_', $childDocLocale);
+ $hrefLang = strtolower($realLang[0]);
+ if (!empty($childCountryIso) && $childCountryIso !== Definitions::INTERNATIONAL_COUNTRY_NAMESPACE) {
+ $hrefLang .= '-' . strtolower($childCountryIso);
}
+
+ $subPages[] = new ZoneSite(
+ $siteRequestContext,
+ $child->getId(),
+ false,
+ $routeItemLocale === $childDocLocale,
+ $childDocLocale,
+ $childCountryIso,
+ $realLang[0],
+ $hrefLang,
+ $urlKey,
+ $domainUrlWithKey,
+ $homeDomainUrlWithKey,
+ $domainDoc->getRealFullPath(),
+ $child->getRealFullPath(),
+ $child->getType()
+ );
}
- return new ZoneSite(
- $siteRequestContext,
- $rootId,
- $isRootDomain,
- $docLocale,
- $docCountryIso,
- $docRealLanguageIso,
- $hrefLang,
- null,
- $siteRequestContext->getDomainUrl(),
- $siteRequestContext->getDomainUrl(),
- $domainDoc->getRealFullPath(),
- $domainDoc->getType(),
- $subPages
- );
+ return $subPages;
}
protected function generateSiteRequestContext(string $domain, mixed $domainConfiguration): SiteRequestContext
@@ -288,4 +331,16 @@ protected function fetchAvailableSites(): array
{
return $this->db->fetchAllAssociative('SELECT `mainDomain`, `rootId` FROM sites');
}
+
+ protected function buildVirtualZoneSite(): array
+ {
+ $hostUrl = !empty($this->generalDomain) && $this->generalDomain !== 'localhost' ? $this->generalDomain : \Pimcore\Tool::getHostUrl();
+ $realHostUrl = parse_url($hostUrl, PHP_URL_HOST);
+
+ return [
+ 'mainDomain' => $realHostUrl ?? '',
+ 'rootId' => 1
+ ];
+ }
+
}
diff --git a/src/I18nBundle/Context/I18nContext.php b/src/I18nBundle/Context/I18nContext.php
index efda36f..25c83c2 100644
--- a/src/I18nBundle/Context/I18nContext.php
+++ b/src/I18nBundle/Context/I18nContext.php
@@ -19,6 +19,7 @@ class I18nContext implements I18nContextInterface
protected ZoneInterface $zone;
protected LocaleDefinitionInterface $localeDefinition;
protected ?PathGeneratorInterface $pathGenerator;
+ protected ?ZoneSiteInterface $currentZoneSite = null;
/**
* @throws ZoneSiteNotFoundException
@@ -33,8 +34,7 @@ public function __construct(
$this->zone = $zone;
$this->pathGenerator = $pathGenerator;
$this->localeDefinition = $localeDefinition;
-
- $this->assertRouteContext();
+ $this->currentZoneSite = $this->determinateCurrentZoneSite();
}
public function getRouteItem(): RouteItemInterface
@@ -54,36 +54,7 @@ public function getLocaleDefinition(): LocaleDefinitionInterface
public function getCurrentZoneSite(): ZoneSiteInterface
{
- $sites = $this->zone->getSites(true);
- $locale = $this->localeDefinition->getLocale();
- $zoneIdentifier = $this->zone->getId() ?? 0;
-
- if (empty($locale)) {
- throw new ZoneSiteNotFoundException(
- sprintf(
- 'Cannot determinate current site with empty locale in zone %d',
- $zoneIdentifier
- )
- );
- }
-
- $availableZoneSiteLocales = array_map(static function (ZoneSiteInterface $site) {
- return $site->getLocale();
- }, $sites);
-
- $treeIndex = array_search($locale, $availableZoneSiteLocales, true);
-
- if ($treeIndex === false) {
- throw new ZoneSiteNotFoundException(
- sprintf(
- 'No zone site for locale "%s" found. Available zone (Id: %d) site locales: %s',
- $locale,
- $zoneIdentifier,
- implode(', ', $availableZoneSiteLocales))
- );
- }
-
- return $sites[$treeIndex];
+ return $this->currentZoneSite;
}
public function getCurrentLocale(): ?string
@@ -297,6 +268,43 @@ protected function getLocaleData(string $locale, string $field, string $keyIdent
return null;
}
+ protected function determinateCurrentZoneSite(): ?ZoneSiteInterface
+ {
+ $sites = $this->zone->getSites(true);
+ $locale = $this->localeDefinition->getLocale();
+ $zoneIdentifier = $this->zone->getId() ?? 0;
+
+ if (empty($locale)) {
+ return null;
+ }
+
+ $activeSites = array_values(array_filter($sites, static function (ZoneSiteInterface $site) {
+ return $site->isActive() === true;
+ }));
+
+ if (count($activeSites) === 0) {
+ throw new ZoneSiteNotFoundException(sprintf(
+ 'No zone site for locale "%s" found. Available zone (Id %d) site locales: %s',
+ $locale,
+ $zoneIdentifier,
+ implode(', ', array_map(static function (ZoneSiteInterface $site) {
+ return $site->getLocale();
+ }, $sites))
+ ));
+ } elseif (count($activeSites) > 1) {
+ throw new ZoneSiteNotFoundException(sprintf(
+ 'Ambiguous locale definition for zone (Id %d) sites detected ("%s" was requested, multiple paths [%s] matched).',
+ $zoneIdentifier,
+ $locale,
+ implode(', ', array_map(static function (ZoneSiteInterface $site) {
+ return $site->getFullPath();
+ }, $activeSites))
+ ));
+ }
+
+ return $activeSites[0];
+ }
+
/**
* Get languages for Country.
* Only checks if root document in given country iso is accessible.
@@ -333,25 +341,6 @@ protected function getActiveLanguagesForCountry(?string $countryIso = null): arr
return $languages;
}
- /**
- * @throws ZoneSiteNotFoundException
- */
- protected function assertRouteContext(): void
- {
- if (!$this->localeDefinition->hasLocale()) {
- return;
- }
-
- $currentZoneSite = $this->getCurrentZoneSite();
-
- $this->routeItem->getRouteContextBag()->add([
- 'host' => $currentZoneSite->getSiteRequestContext()->getHost(),
- 'scheme' => $currentZoneSite->getSiteRequestContext()->getScheme(),
- 'httpPort' => $currentZoneSite->getSiteRequestContext()->getHttpPort(),
- 'httpsPort' => $currentZoneSite->getSiteRequestContext()->getHttpsPort(),
- ]);
- }
-
protected function mapLanguageInfo(string $locale, string $href): array
{
$iso = explode('_', $locale);
diff --git a/src/I18nBundle/EventListener/Frontend/HeadLinkListener.php b/src/I18nBundle/EventListener/Frontend/HeadLinkListener.php
index eacb6e5..2c498c0 100644
--- a/src/I18nBundle/EventListener/Frontend/HeadLinkListener.php
+++ b/src/I18nBundle/EventListener/Frontend/HeadLinkListener.php
@@ -45,7 +45,6 @@ public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
- // just add metadata on master request
if (!$event->isMainRequest()) {
return;
}
diff --git a/src/I18nBundle/EventListener/Frontend/HeadMetaListener.php b/src/I18nBundle/EventListener/Frontend/HeadMetaListener.php
index a8b8f3a..afa553c 100644
--- a/src/I18nBundle/EventListener/Frontend/HeadMetaListener.php
+++ b/src/I18nBundle/EventListener/Frontend/HeadMetaListener.php
@@ -38,7 +38,6 @@ public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
- // just add meta data on master request
if (!$event->isMainRequest()) {
return;
}
diff --git a/src/I18nBundle/EventListener/I18nStartupListener.php b/src/I18nBundle/EventListener/I18nStartupListener.php
index 1d06cfa..f7b0907 100644
--- a/src/I18nBundle/EventListener/I18nStartupListener.php
+++ b/src/I18nBundle/EventListener/I18nStartupListener.php
@@ -72,6 +72,10 @@ public function onKernelRequest(RequestEvent $event): void
return;
}
+ if ($document instanceof Document\Link) {
+ return;
+ }
+
try {
$this->initializeI18nContext($request, $document);
} catch (\Throwable $e) {
diff --git a/src/I18nBundle/Exception/VirtualProxyPathException.php b/src/I18nBundle/Exception/VirtualProxyPathException.php
new file mode 100644
index 0000000..dc953f2
--- /dev/null
+++ b/src/I18nBundle/Exception/VirtualProxyPathException.php
@@ -0,0 +1,11 @@
+routeItemBuilder->buildRouteItemByParameters($type, $i18nRouteParameters);
- $zone = $this->setupZone($routeItem, false);
- $pathGenerator = $this->setupPathGenerator($routeItem, $bootPathGenerator);
+ $zone = $this->setupZone($routeItem, false, $fullBootstrap);
+ $pathGenerator = $this->setupPathGenerator($routeItem, $fullBootstrap);
$localeDefinition = $this->buildLocaleDefinition($routeItem);
return new I18nContext($routeItem, $zone, $localeDefinition, $pathGenerator);
@@ -71,20 +67,20 @@ public function buildContextByParameters(array $i18nRouteParameters, bool $bootP
* @throws ZoneSiteNotFoundException
* @throws RouteItemException
*/
- public function buildContextByRequest(Request $baseRequest, ?Document $baseDocument, bool $bootPathGenerator = false): I18nContextInterface
+ public function buildContextByRequest(Request $baseRequest, ?Document $baseDocument, bool $fullBootstrap = false): I18nContextInterface
{
$routeItem = $this->routeItemBuilder->buildRouteItemByRequest($baseRequest, $baseDocument);
- $zone = $this->setupZone($routeItem, $this->requestHelper->isFrontendRequestByAdmin($baseRequest));
- $pathGenerator = $this->setupPathGenerator($routeItem, $bootPathGenerator);
+ $zone = $this->setupZone($routeItem, $this->requestHelper->isFrontendRequestByAdmin($baseRequest), $fullBootstrap);
+ $pathGenerator = $this->setupPathGenerator($routeItem, $fullBootstrap);
$localeDefinition = $this->buildLocaleDefinition($routeItem);
return new I18nContext($routeItem, $zone, $localeDefinition, $pathGenerator);
}
- protected function setupPathGenerator(RouteItemInterface $routeItem, bool $bootPathGenerator = false): ?PathGeneratorInterface
+ protected function setupPathGenerator(RouteItemInterface $routeItem, bool $fullBootstrap = false): ?PathGeneratorInterface
{
- if ($bootPathGenerator === false) {
+ if ($fullBootstrap === false) {
return null;
}
@@ -98,7 +94,7 @@ protected function setupPathGenerator(RouteItemInterface $routeItem, bool $bootP
return $pathGenerator;
}
- protected function setupZone(RouteItemInterface $routeItem, bool $isFrontendRequestByAdmin = false): ZoneInterface
+ protected function setupZone(RouteItemInterface $routeItem, bool $isFrontendRequestByAdmin = false, bool $fullBootstrap = false): ZoneInterface
{
$zone = $this->zoneBuilder->buildZone($routeItem);
@@ -106,7 +102,7 @@ protected function setupZone(RouteItemInterface $routeItem, bool $isFrontendRequ
// since they are kind of internal!
if ($zone instanceof Zone) {
$zone->processProviderLocales($this->localeProviderRegistry->get($zone->getLocaleAdapterName()));
- $zone->setSites($this->zoneSitesBuilder->buildZoneSites($zone, $isFrontendRequestByAdmin));
+ $zone->setSites($this->zoneSitesBuilder->buildZoneSites($zone, $routeItem, $fullBootstrap, $isFrontendRequestByAdmin));
}
return $zone;
@@ -150,5 +146,4 @@ public function buildPathGenerator(?string $pathGeneratorIdentifier): PathGenera
return $this->pathGeneratorRegistry->get($pathGeneratorIdentifier);
}
-
}
diff --git a/src/I18nBundle/Model/RouteItem/BaseRouteItem.php b/src/I18nBundle/Model/RouteItem/BaseRouteItem.php
index ca411a9..494f082 100644
--- a/src/I18nBundle/Model/RouteItem/BaseRouteItem.php
+++ b/src/I18nBundle/Model/RouteItem/BaseRouteItem.php
@@ -22,7 +22,7 @@ public function __construct(string $type, bool $headless)
RouteItemInterface::SYMFONY_ROUTE,
RouteItemInterface::STATIC_ROUTE,
], true)) {
- throw new \Exception(sprintf('Invalid RouteItem type %s', $type));
+ throw new \Exception(sprintf('Invalid RouteItem type "%s"', $type));
}
$this->type = $type;
diff --git a/src/I18nBundle/Model/ZoneSite.php b/src/I18nBundle/Model/ZoneSite.php
index 0d7d00f..d4c0ddd 100644
--- a/src/I18nBundle/Model/ZoneSite.php
+++ b/src/I18nBundle/Model/ZoneSite.php
@@ -7,6 +7,7 @@ class ZoneSite implements ZoneSiteInterface
protected SiteRequestContext $siteRequestContext;
protected int $rootId;
protected bool $isRootDomain;
+ protected bool $isActive;
protected ?string $locale;
protected ?string $countryIso;
protected string $languageIso;
@@ -14,6 +15,7 @@ class ZoneSite implements ZoneSiteInterface
protected ?string $localeUrlMapping;
protected string $url;
protected ?string $homeUrl;
+ protected string $rootPath;
protected string $fullPath;
protected ?string $type;
protected array $subSites;
@@ -22,6 +24,7 @@ public function __construct(
SiteRequestContext $siteRequestContext,
int $rootId,
bool $isRootDomain,
+ bool $isActive,
?string $locale,
?string $countryIso,
string $languageIso,
@@ -29,6 +32,7 @@ public function __construct(
?string $localeUrlMapping,
string $url,
?string $homeUrl,
+ string $rootPath,
string $fullPath,
?string $type,
array $subSites = []
@@ -36,6 +40,7 @@ public function __construct(
$this->siteRequestContext = $siteRequestContext;
$this->rootId = $rootId;
$this->isRootDomain = $isRootDomain;
+ $this->isActive = $isActive;
$this->locale = $locale;
$this->countryIso = $countryIso;
$this->languageIso = $languageIso;
@@ -43,6 +48,7 @@ public function __construct(
$this->localeUrlMapping = $localeUrlMapping;
$this->url = $url;
$this->homeUrl = $homeUrl;
+ $this->rootPath = $rootPath;
$this->fullPath = $fullPath;
$this->type = $type;
$this->subSites = $subSites;
@@ -63,6 +69,11 @@ public function isRootDomain(): bool
return $this->isRootDomain;
}
+ public function isActive(): bool
+ {
+ return $this->isActive;
+ }
+
public function getLocale(): ?string
{
return $this->locale;
@@ -98,6 +109,11 @@ public function getHomeUrl(): ?string
return $this->homeUrl;
}
+ public function getRootPath(): string
+ {
+ return $this->rootPath;
+ }
+
public function getFullPath(): string
{
return $this->fullPath;
diff --git a/src/I18nBundle/Model/ZoneSiteInterface.php b/src/I18nBundle/Model/ZoneSiteInterface.php
index f99ac58..733c10e 100644
--- a/src/I18nBundle/Model/ZoneSiteInterface.php
+++ b/src/I18nBundle/Model/ZoneSiteInterface.php
@@ -10,6 +10,8 @@ public function getRootId(): int;
public function isRootDomain(): bool;
+ public function isActive(): bool;
+
public function getLocale(): ?string;
public function getCountryIso(): ?string;
@@ -24,6 +26,8 @@ public function getUrl(): string;
public function getHomeUrl(): ?string;
+ public function getRootPath(): string;
+
public function getFullPath(): string;
public function getType(): ?string;
diff --git a/src/I18nBundle/Modifier/RouteModifier.php b/src/I18nBundle/Modifier/RouteModifier.php
new file mode 100644
index 0000000..c09c321
--- /dev/null
+++ b/src/I18nBundle/Modifier/RouteModifier.php
@@ -0,0 +1,266 @@
+linkGeneratorRouteItemTransformer = $linkGeneratorRouteItemTransformer;
+ $this->i18nContextManager = $i18nContextManager;
+ }
+
+ public function generateI18nContext(string $name, $parameters = []): I18nContextInterface
+ {
+ $i18nParameters = $parameters[Definitions::ATTRIBUTE_I18N_ROUTE_IDENTIFIER];
+ $i18nType = $i18nParameters['type'] ?? '';
+ $i18nParameters = $this->validateRouteParameters($name, $i18nType, $i18nParameters);
+
+ unset($parameters[Definitions::ATTRIBUTE_I18N_ROUTE_IDENTIFIER]);
+
+ return $this->i18nContextManager->buildContextByParameters($i18nType, $i18nParameters, false);
+ }
+
+ public function modifyStaticRouteFragments(I18nContextInterface $i18nContext, string $originalPath)
+ {
+ $zone = $i18nContext->getZone();
+ $routeItem = $i18nContext->getRouteItem();
+ $locale = $routeItem->getLocaleFragment();
+
+ if (!$zone instanceof ZoneInterface) {
+ return $originalPath;
+ }
+
+ $path = preg_replace_callback(
+ '/@((?:(?![\/|?]).)*)/',
+ function ($matches) use ($zone, $locale) {
+ return $this->translateDynamicRouteKey($zone, $matches[1], $locale);
+ },
+ $originalPath
+ );
+
+ $path = $this->parseLocaleUrlMapping($zone, $path, $locale);
+
+ if (str_ends_with($path, '?')) {
+ $path = substr($path, 0, -1);
+ }
+
+ return $path;
+ }
+
+ public function modifySymfonyRouteParameterBag(I18nContextInterface $i18nContext): void
+ {
+ $zone = $i18nContext->getZone();
+ $locale = $i18nContext->getRouteItem()->getLocaleFragment();
+ $translationKeys = $i18nContext->getRouteItem()->getRouteAttributesBag()->get('_i18n_translation_keys', []);
+
+ foreach ($translationKeys as $routeKey => $translationKey) {
+
+ if ($i18nContext->getRouteItem()->getRouteParametersBag()->has($translationKey)) {
+ continue;
+ }
+
+ $i18nContext->getRouteItem()->getRouteParametersBag()->set($routeKey, $this->translateDynamicRouteKey($zone, $translationKey, $locale));
+ }
+ }
+
+ public function parseLocaleUrlMapping(ZoneInterface $zone, string $path, string $locale): string
+ {
+ //transform locale style to given url mapping - if existing
+ $urlMapping = $zone->getLocaleUrlMapping();
+
+ if (!array_key_exists($locale, $urlMapping)) {
+ return $path;
+ }
+
+ $urlFragments = parse_url($path);
+ $pathFragment = $urlFragments['path'] ?? '';
+ $fragments = array_values(array_filter(explode(DIRECTORY_SEPARATOR, $pathFragment)));
+
+ if ($fragments[0] === $locale) {
+ //replace first value in array!
+ $fragments[0] = $urlMapping[$locale];
+ $addSlash = str_starts_with($pathFragment, DIRECTORY_SEPARATOR);
+ $freshPath = System::joinPath($fragments, $addSlash);
+ $path = str_replace($pathFragment, $freshPath, $path);
+ }
+
+ return $path;
+ }
+
+ public function buildLinkGeneratorRouteItem(Concrete $routeItemEntity, I18nContextInterface $i18nContext): RouteItemInterface
+ {
+ $linkGenerator = $routeItemEntity->getClass()?->getLinkGenerator();
+ if (!$linkGenerator instanceof I18nLinkGeneratorInterface) {
+ throw new \Exception(
+ sprintf(
+ 'I18n link generator error: Your link generator "%s" needs to be an instance of %s.',
+ get_class($linkGenerator),
+ I18nLinkGeneratorInterface::class
+ )
+ );
+ }
+
+ $parsedLinkGeneratorRouteItem = $linkGenerator->generateRouteItem(
+ $routeItemEntity,
+ $this->linkGeneratorRouteItemTransformer->transform(
+ $i18nContext->getRouteItem(),
+ ['staticRouteName' => $linkGenerator->getStaticRouteName($routeItemEntity)]
+ )
+ );
+
+ return $this->linkGeneratorRouteItemTransformer->reverseTransform($parsedLinkGeneratorRouteItem);
+ }
+
+ public function buildDocumentPath(I18nContextInterface $i18nContext, int $referenceType): string
+ {
+ $routeItem = $i18nContext->getRouteItem();
+ $document = $routeItem->getEntity();
+ $contextBag = $routeItem->getRouteContextBag();
+ $zoneSite = $i18nContext->getCurrentZoneSite();
+
+ $documentPath = '';
+ $documentDebug = '--';
+
+ $prettyUrlSet = false;
+ if ($document instanceof Document\Page && !empty($document->getPrettyUrl())) {
+ $prettyUrlSet = true;
+ $documentPath = $document->getPrettyUrl();
+ $documentDebug = sprintf('id %d with pretty url "%s"', $document->getId(), $documentPath);
+ } elseif ($document instanceof Document) {
+ $documentPath = $document->getRealFullPath();
+ $documentDebug = sprintf('id %d with path "%s"', $document->getId(), $documentPath);
+ }
+
+ if ($documentPath === '') {
+ throw new RouteNotFoundException(
+ sprintf('cannot generate document route [%s]', $documentDebug)
+ );
+ }
+
+ if ($contextBag->has('virtualProxyZoneSite')) {
+
+ if ($prettyUrlSet) {
+ throw new VirtualProxyPathException(sprintf('Virtual path generation for document with pretty URLs ("%s") cannot be processed', $documentPath));
+ }
+
+ /** @var ZoneSiteInterface $zoneSite */
+ $zoneSite = $contextBag->get('virtualProxyZoneSite');
+ $relativePath = preg_replace('/^' . preg_quote($i18nContext->getCurrentZoneSite()->getFullPath(), '/') . '/', '', $documentPath);
+ $documentPath = System::joinPath([$zoneSite->getFullPath(), $relativePath]);
+
+ if (Document\Service::pathExists($documentPath)) {
+ throw new VirtualProxyPathException(sprintf(
+ 'Virtual proxy path "%s" for document "%s" should not exists but was found in zone path %s',
+ $documentPath,
+ $document->getRealPath(),
+ $zoneSite->getFullPath()
+ ));
+ }
+ }
+
+ if (!$prettyUrlSet) {
+ // strip site prefix from path
+ $documentPath = substr($documentPath, strlen($zoneSite->getRootPath()));
+ }
+
+ if ($referenceType !== UrlGeneratorInterface::ABSOLUTE_URL) {
+ return $documentPath;
+ }
+
+ $scheme = $zoneSite->getSiteRequestContext()->getScheme();
+ $host = $zoneSite->getSiteRequestContext()->getHost();
+ $httpPort = $zoneSite->getSiteRequestContext()->getHttpPort();
+ $httpsPort = $zoneSite->getSiteRequestContext()->getHttpsPort();
+
+ $port = '';
+ if ($scheme === 'http' && $httpPort !== 80) {
+ $port = ':' . $httpPort;
+ } elseif ($scheme === 'https' && $httpsPort !== 443) {
+ $port = ':' . $httpsPort;
+ }
+
+ if (!empty($documentPath)) {
+ $documentPath = '/' . ltrim($documentPath, '/');
+ }
+
+ return sprintf('%s://%s%s%s', $scheme, $host, $port, $documentPath);
+ }
+
+ protected function translateDynamicRouteKey(ZoneInterface $zone, string $key, string $locale): string
+ {
+ $zoneTranslations = $zone->getTranslations();
+ $zoneIdentifier = $zone->getId() ?? 0;
+
+ $exceptionMessage = null;
+ $routeKey = null;
+
+ if (empty($zoneTranslations)) {
+ $exceptionMessage = sprintf('No translations for zone [Id: %d] found', $zoneIdentifier);
+ } else {
+ $translationIndex = array_search($key, array_column($zoneTranslations, 'key'), true);
+ if ($translationIndex === false) {
+ $exceptionMessage = sprintf('No translation key for "%s" in zone [Id: %d] found', $key, $zoneIdentifier);
+ } else {
+ $translation = $zoneTranslations[$translationIndex]['values'];
+ if (!isset($translation[$locale])) {
+ $exceptionMessage = sprintf('No translation key for "%s" with locale "%s" in zone [Id: %d] found', $key, $locale, $zoneIdentifier);
+ } else {
+ $routeKey = $translation[$locale];
+ }
+ }
+ }
+
+ if ($routeKey !== null) {
+ return $routeKey;
+ }
+
+ if (\Pimcore\Tool::isFrontendRequestByAdmin()) {
+ return $key;
+ }
+
+ throw new MissingTranslationRouteSlugException($exceptionMessage);
+ }
+
+ protected function validateRouteParameters(string $name, string $routeType, array $parameters): array
+ {
+ unset($parameters['type']);
+
+ if (!empty($name)) {
+ $parameters['routeName'] = $name;
+ }
+
+ if ($name === '') {
+ $entity = $parameters['entity'] ?? null;
+ if ($routeType === RouteItemInterface::DOCUMENT_ROUTE && !$entity instanceof Document) {
+ throw new \Exception('I18n document route without route name requires a valid Document in "entity" parameter');
+ } elseif ($routeType === RouteItemInterface::STATIC_ROUTE && !$entity instanceof DataObject) {
+ throw new \Exception('I18n static route without route name requires a valid DataObject in "entity" parameter');
+ }
+ }
+
+ return $parameters;
+ }
+}
diff --git a/src/I18nBundle/Resources/config/services/route.yml b/src/I18nBundle/Resources/config/services/route.yml
index 2cc2230..433a403 100644
--- a/src/I18nBundle/Resources/config/services/route.yml
+++ b/src/I18nBundle/Resources/config/services/route.yml
@@ -2,3 +2,6 @@ services:
I18nBundle\Factory\RouteItemFactory:
autowire: true
+
+ I18nBundle\Modifier\RouteModifier:
+ autowire: true
diff --git a/src/I18nBundle/Resources/config/services/system.yml b/src/I18nBundle/Resources/config/services/system.yml
index 759a0a6..ef16661 100644
--- a/src/I18nBundle/Resources/config/services/system.yml
+++ b/src/I18nBundle/Resources/config/services/system.yml
@@ -5,16 +5,13 @@ services:
autoconfigure: true
public: false
- # tool: installer
I18nBundle\Tool\Install:
public: true
arguments:
$bundle: "@=service('kernel').getBundle('I18nBundle')"
- # configuration
I18nBundle\Configuration\Configuration: ~
- # session: configurator
I18nBundle\Session\SessionConfigurator:
tags:
- { name: pimcore.session.configurator }
diff --git a/src/I18nBundle/Routing/I18nRouter.php b/src/I18nBundle/Routing/I18nRouter.php
index ac5cded..a752b5e 100644
--- a/src/I18nBundle/Routing/I18nRouter.php
+++ b/src/I18nBundle/Routing/I18nRouter.php
@@ -2,16 +2,11 @@
namespace I18nBundle\Routing;
-use I18nBundle\Configuration\Configuration;
+use I18nBundle\Modifier\RouteModifier;
use I18nBundle\Context\I18nContextInterface;
use I18nBundle\Definitions;
-use I18nBundle\Exception\MissingTranslationRouteSlugException;
-use I18nBundle\LinkGenerator\I18nLinkGeneratorInterface;
-use I18nBundle\Manager\I18nContextManager;
use I18nBundle\Model\ZoneInterface;
use I18nBundle\Model\RouteItem\RouteItemInterface;
-use I18nBundle\Tool\System;
-use I18nBundle\Transformer\LinkGeneratorRouteItemTransformer;
use Pimcore\Model\DataObject\Concrete;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
@@ -24,24 +19,18 @@
class I18nRouter implements RouterInterface, RequestMatcherInterface, WarmableInterface
{
protected RouterInterface $router;
- protected LinkGeneratorRouteItemTransformer $linkGeneratorRouteItemTransformer;
- protected Configuration $configuration;
+ protected RouteModifier $routeModifier;
protected UrlGeneratorInterface $urlGenerator;
- protected I18nContextManager $i18nContextManager;
protected ?RequestContext $contextBackup = null;
public function __construct(
RouterInterface $router,
- LinkGeneratorRouteItemTransformer $linkGeneratorRouteItemTransformer,
- Configuration $configuration,
- UrlGeneratorInterface $urlGenerator,
- I18nContextManager $i18nContextManager
+ RouteModifier $routeModifier,
+ UrlGeneratorInterface $urlGenerator
) {
$this->router = $router;
- $this->linkGeneratorRouteItemTransformer = $linkGeneratorRouteItemTransformer;
- $this->configuration = $configuration;
+ $this->routeModifier = $routeModifier;
$this->urlGenerator = $urlGenerator;
- $this->i18nContextManager = $i18nContextManager;
}
/**
@@ -106,16 +95,10 @@ public function generate($name, $parameters = [], $referenceType = self::ABSOLUT
return $this->router->generate($name, $parameters, $referenceType);
}
- $i18nParameters = $parameters[Definitions::ATTRIBUTE_I18N_ROUTE_IDENTIFIER];
+ $i18nContext = $this->routeModifier->generateI18nContext($name, $parameters);
unset($parameters[Definitions::ATTRIBUTE_I18N_ROUTE_IDENTIFIER]);
- if (!empty($name)) {
- $i18nParameters['routeName'] = $name;
- }
-
- $i18nContext = $this->i18nContextManager->buildContextByParameters($i18nParameters, false);
-
if (!$i18nContext instanceof I18nContextInterface) {
return $this->router->generate($name, $parameters, $referenceType);
}
@@ -128,7 +111,11 @@ public function generate($name, $parameters = [], $referenceType = self::ABSOLUT
return $this->generateSymfonyRoute($i18nContext, $referenceType);
}
- throw new RouteNotFoundException(sprintf('None of the chained routers were able to generate route: %s', $name));
+ if ($i18nContext->getRouteItem()->getType() === RouteItemInterface::DOCUMENT_ROUTE) {
+ return $this->generateDocumentRoute($i18nContext, $referenceType);
+ }
+
+ throw new RouteNotFoundException(sprintf('None of the chained routers were able to generate i18n route: %s', $name));
}
protected function generateStaticRoute(I18nContextInterface $i18nContext, int $referenceType): string
@@ -137,46 +124,16 @@ protected function generateStaticRoute(I18nContextInterface $i18nContext, int $r
$routeItem = $i18nContext->getRouteItem();
if ($routeItemEntity instanceof Concrete) {
- $routeItem = $this->buildLinkGeneratorRouteItem($routeItemEntity, $i18nContext);
- }
-
- $this->buildRouteContext($i18nContext, $referenceType);
-
- $path = $this->urlGenerator->generate($routeItem->getRouteName(), $routeItem->getRouteParameters(), $referenceType);
-
- $this->restoreRouteContext();
-
- $zone = $i18nContext->getZone();
- $locale = $routeItem->getLocaleFragment();
-
- if (!$zone instanceof ZoneInterface) {
- return $path;
+ $routeItem = $this->routeModifier->buildLinkGeneratorRouteItem($routeItemEntity, $i18nContext);
}
+ $path = $this->generateContextAwarePath($i18nContext, $routeItem, $referenceType);
- $path = preg_replace_callback(
- '/@((?:(?![\/|?]).)*)/',
- function ($matches) use ($zone, $locale) {
- return $this->translateDynamicRouteKey($zone, $matches[1], $locale);
- },
- $path
- );
-
- $path = $this->parseLocaleUrlMapping($zone, $path, $locale);
-
- if (str_ends_with($path, '?')) {
- $path = substr($path, 0, -1);
- }
-
- return $path;
+ return $this->routeModifier->modifyStaticRouteFragments($i18nContext, $path);
}
protected function generateSymfonyRoute(I18nContextInterface $i18nContext, int $referenceType): string
{
- if (empty($i18nContext->getRouteItem()->hasLocaleFragment())) {
- return $this->urlGenerator->generate($i18nContext->getRouteItem()->getRouteName(), $i18nContext->getRouteItem()->getRouteParameters(), $referenceType);
- }
-
$locale = $i18nContext->getRouteItem()->getLocaleFragment();
$zone = $i18nContext->getZone();
@@ -184,113 +141,29 @@ protected function generateSymfonyRoute(I18nContextInterface $i18nContext, int $
return $this->urlGenerator->generate($i18nContext->getRouteItem()->getRouteName(), $i18nContext->getRouteItem()->getRouteParameters(), $referenceType);
}
- $translationKeys = $i18nContext->getRouteItem()->getRouteAttributesBag()->get('_i18n_translation_keys', []);
- $routeParametersBag = $i18nContext->getRouteItem()->getRouteParametersBag();
-
- foreach ($translationKeys as $routeKey => $translationKey) {
-
- if ($routeParametersBag->has($translationKey)) {
- continue;
- }
-
- $routeParametersBag->set($routeKey, $this->translateDynamicRouteKey($zone, $translationKey, $locale));
- }
-
- $this->buildRouteContext($i18nContext, $referenceType);
-
- $path = $this->urlGenerator->generate($i18nContext->getRouteItem()->getRouteName(), $routeParametersBag->all(), $referenceType);
+ $this->routeModifier->modifySymfonyRouteParameterBag($i18nContext);
- $this->restoreRouteContext();
+ $path = $this->generateContextAwarePath($i18nContext, $i18nContext->getRouteItem(), $referenceType);
- return $this->parseLocaleUrlMapping($zone, $path, $locale);
+ return $this->routeModifier->parseLocaleUrlMapping($zone, $path, $locale);
}
- protected function translateDynamicRouteKey(ZoneInterface $zone, string $key, string $locale): string
+ protected function generateDocumentRoute(I18nContextInterface $i18nContext, int $referenceType): string
{
- $zoneTranslations = $zone->getTranslations();
- $zoneIdentifier = $zone->getId() ?? 0;
-
- $exceptionMessage = null;
- $routeKey = null;
-
- if (empty($zoneTranslations)) {
- $exceptionMessage = sprintf('No translations for zone [Id: %d] found', $zoneIdentifier);
- } else {
-
- $translationIndex = array_search($key, array_column($zoneTranslations, 'key'), true);
-
- if ($translationIndex === false) {
- $exceptionMessage = sprintf('No translation key for "%s" in zone [Id: %d] found', $key, $zoneIdentifier);
- }
-
- $translation = $zoneTranslations[$translationIndex]['values'];
-
- if (!isset($translation[$locale])) {
- $exceptionMessage = sprintf('No translation key for "%s" with locale "%s" in zone [Id: %d] found', $key, $locale, $zoneIdentifier);
- }
-
- $routeKey = $translation[$locale];
- }
-
- if ($routeKey !== null) {
- return $routeKey;
- }
-
- if (\Pimcore\Tool::isFrontendRequestByAdmin()) {
- return $key;
- }
-
- throw new MissingTranslationRouteSlugException($exceptionMessage);
+ return $this->routeModifier->buildDocumentPath($i18nContext, $referenceType);
}
- protected function parseLocaleUrlMapping(ZoneInterface $zone, string $path, string $locale): string
+ protected function generateContextAwarePath(I18nContextInterface $i18nContext, RouteItemInterface $routeItem, int $referenceType): string
{
- //transform locale style to given url mapping - if existing
- $urlMapping = $zone->getLocaleUrlMapping();
-
- if (!array_key_exists($locale, $urlMapping)) {
- return $path;
- }
+ $this->buildRouteContext($i18nContext, $referenceType);
- $urlFragments = parse_url($path);
- $pathFragment = $urlFragments['path'] ?? '';
- $fragments = array_values(array_filter(explode(DIRECTORY_SEPARATOR, $pathFragment)));
+ $path = $this->urlGenerator->generate($routeItem->getRouteName(), $routeItem->getRouteParameters(), $referenceType);
- if ($fragments[0] === $locale) {
- //replace first value in array!
- $fragments[0] = $urlMapping[$locale];
- $addSlash = str_starts_with($pathFragment, DIRECTORY_SEPARATOR);
- $freshPath = System::joinPath($fragments, $addSlash);
- $path = str_replace($pathFragment, $freshPath, $path);
- }
+ $this->restoreRouteContext();
return $path;
}
- protected function buildLinkGeneratorRouteItem(Concrete $routeItemEntity, I18nContextInterface $i18nContext): RouteItemInterface
- {
- $linkGenerator = $routeItemEntity->getClass()?->getLinkGenerator();
- if (!$linkGenerator instanceof I18nLinkGeneratorInterface) {
- throw new \Exception(
- sprintf(
- 'I18n link generator error: Your link generator "%s" needs to be an instance of %s.',
- get_class($linkGenerator),
- I18nLinkGeneratorInterface::class
- )
- );
- }
-
- $parsedLinkGeneratorRouteItem = $linkGenerator->generateRouteItem(
- $routeItemEntity,
- $this->linkGeneratorRouteItemTransformer->transform(
- $i18nContext->getRouteItem(),
- ['staticRouteName' => $linkGenerator->getStaticRouteName($routeItemEntity)]
- )
- );
-
- return $this->linkGeneratorRouteItemTransformer->reverseTransform($parsedLinkGeneratorRouteItem);
- }
-
protected function buildRouteContext(I18nContextInterface $i18nContext, int $referenceType): void
{
$allowedKeys = [
@@ -307,9 +180,11 @@ protected function buildRouteContext(I18nContextInterface $i18nContext, int $ref
$this->contextBackup = clone $this->getContext();
foreach ($allowedKeys as $allowedKey) {
- if (!empty($i18nContext->getRouteItem()->getRouteContextBag()->get($allowedKey))) {
+ $contextGetter = sprintf('get%s', ucfirst($allowedKey));
+ $contextValue = $i18nContext->getCurrentZoneSite()->getSiteRequestContext()->$contextGetter($allowedKey);
+ if (!empty($contextValue)) {
$setter = sprintf('set%s', ucfirst($allowedKey));
- $this->getContext()->$setter($i18nContext->getRouteItem()->getRouteContextBag()->get($allowedKey));
+ $this->getContext()->$setter($contextValue);
}
}
}
diff --git a/src/I18nBundle/Twig/Extension/I18nExtension.php b/src/I18nBundle/Twig/Extension/I18nExtension.php
index 4a8532f..442a3fc 100644
--- a/src/I18nBundle/Twig/Extension/I18nExtension.php
+++ b/src/I18nBundle/Twig/Extension/I18nExtension.php
@@ -6,26 +6,31 @@
use I18nBundle\Http\I18nContextResolverInterface;
use I18nBundle\Manager\I18nContextManager;
use I18nBundle\Model\RouteItem\RouteItemInterface;
+use I18nBundle\Builder\RouteParameterBuilder;
use Pimcore\Model\DataObject\AbstractObject;
use Pimcore\Model\Document;
use Pimcore\Model\Element\ElementInterface;
use Pimcore\Model\Site;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class I18nExtension extends AbstractExtension
{
protected RequestStack $requestStack;
+ protected UrlGeneratorInterface $urlGenerator;
protected I18nContextResolverInterface $i18nContextResolver;
protected I18nContextManager $i18nContextManager;
public function __construct(
RequestStack $requestStack,
+ UrlGeneratorInterface $urlGenerator,
I18nContextResolverInterface $i18nContextResolver,
I18nContextManager $i18nContextManager
) {
$this->requestStack = $requestStack;
+ $this->urlGenerator = $urlGenerator;
$this->i18nContextResolver = $i18nContextResolver;
$this->i18nContextManager = $i18nContextManager;
}
@@ -33,10 +38,13 @@ public function __construct(
public function getFunctions(): array
{
return [
- new TwigFunction('i18n_context', [$this, 'getI18nContext']),
+ new TwigFunction('i18n_current_context', [$this, 'getI18nContext']),
new TwigFunction('i18n_create_context_by_entity', [$this, 'createI18nContextByEntity']),
new TwigFunction('i18n_create_context_by_static_route', [$this, 'createI18nContextByStaticRoute']),
new TwigFunction('i18n_create_context_by_symfony_route', [$this, 'createI18nContextBySymfonyRoute']),
+ new TwigFunction('i18n_entity_route', [$this, 'createI18nEntityRoute']),
+ new TwigFunction('i18n_static_route', [$this, 'createI18nStaticRoute']),
+ new TwigFunction('i18n_symfony_route', [$this, 'createI18nSymfonyRoute']),
];
}
@@ -56,20 +64,19 @@ public function createI18nContextByEntity(ElementInterface $entity, array $route
];
if ($entity instanceof Document) {
- $routeItemParameters['type'] = RouteItemInterface::DOCUMENT_ROUTE;
+ $type = RouteItemInterface::DOCUMENT_ROUTE;
} elseif ($entity instanceof AbstractObject) {
- $routeItemParameters['type'] = RouteItemInterface::STATIC_ROUTE;
+ $type = RouteItemInterface::STATIC_ROUTE;
} else {
throw new \Exception('Cannot build zone for entity "%"', get_class($entity));
}
- return $this->i18nContextManager->buildContextByParameters($routeItemParameters, true);
+ return $this->i18nContextManager->buildContextByParameters($type, $routeItemParameters, true);
}
public function createI18nContextByStaticRoute(string $route, array $routeParameter = [], ?Site $site = null): I18nContextInterface
{
$routeItemParameters = [
- 'type' => RouteItemInterface::STATIC_ROUTE,
'routeParameters' => $routeParameter,
'routeName' => $route,
'context' => [
@@ -77,13 +84,12 @@ public function createI18nContextByStaticRoute(string $route, array $routeParame
]
];
- return $this->i18nContextManager->buildContextByParameters($routeItemParameters, true);
+ return $this->i18nContextManager->buildContextByParameters(RouteItemInterface::STATIC_ROUTE, $routeItemParameters, true);
}
public function createI18nContextBySymfonyRoute(string $route, array $routeParameter = [], ?Site $site = null): I18nContextInterface
{
$routeItemParameters = [
- 'type' => RouteItemInterface::SYMFONY_ROUTE,
'routeParameters' => $routeParameter,
'routeName' => $route,
'context' => [
@@ -91,6 +97,44 @@ public function createI18nContextBySymfonyRoute(string $route, array $routeParam
]
];
- return $this->i18nContextManager->buildContextByParameters($routeItemParameters, true);
+ return $this->i18nContextManager->buildContextByParameters(RouteItemInterface::SYMFONY_ROUTE, $routeItemParameters, true);
}
+
+ public function createI18nEntityRoute(ElementInterface $entity, array $routeParameter = [], bool $absoluteUrl = false): string
+ {
+ $referenceType = $absoluteUrl ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH;
+
+ $routeItemParameters = RouteParameterBuilder::buildForEntityWithRequest(
+ $entity,
+ $routeParameter,
+ $this->requestStack->getCurrentRequest()
+ );
+
+ return $this->urlGenerator->generate('', $routeItemParameters, $referenceType);
+ }
+
+ public function createI18nStaticRoute(string $route, array $routeParameter = [], bool $absoluteUrl = false): string
+ {
+ $referenceType = $absoluteUrl ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH;
+
+ $routeItemParameters = RouteParameterBuilder::buildForStaticRouteWithRequest(
+ $routeParameter,
+ $this->requestStack->getCurrentRequest()
+ );
+
+ return $this->urlGenerator->generate($route, $routeItemParameters, $referenceType);
+ }
+
+ public function createI18nSymfonyRoute(string $route, array $routeParameter = [], bool $absoluteUrl = false): string
+ {
+ $referenceType = $absoluteUrl ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH;
+
+ $routeItemParameters = RouteParameterBuilder::buildForSymfonyRouteWithRequest(
+ $routeParameter,
+ $this->requestStack->getCurrentRequest()
+ );
+
+ return $this->urlGenerator->generate($route, $routeItemParameters, $referenceType);
+ }
+
}
diff --git a/tests/_etc/config/app/templates/language-selector.html.twig b/tests/_etc/config/app/templates/language-selector.html.twig
index 1297183..7485afb 100644
--- a/tests/_etc/config/app/templates/language-selector.html.twig
+++ b/tests/_etc/config/app/templates/language-selector.html.twig
@@ -8,9 +8,9 @@
- {% set i18n_context = i18n_context() %}
- {% if i18n_context is not null %}
- {% set languages = i18n_context.activeLanguages %}
+ {% set current_context = i18n_current_context() %}
+ {% if current_context is not null %}
+ {% set languages = current_context.activeLanguages %}
{% if languages is iterable %}
diff --git a/tests/functional.default.country/LocalizedErrorDocumentsCest.php b/tests/functional.default.country/LocalizedErrorDocumentsCest.php
index bc9af5c..1230c48 100644
--- a/tests/functional.default.country/LocalizedErrorDocumentsCest.php
+++ b/tests/functional.default.country/LocalizedErrorDocumentsCest.php
@@ -20,7 +20,10 @@ public function testDefaultErrorPage(FunctionalTester $I)
]
]);
+ // we need to unpublish the default page here
+ // since the default error page is placed on root level (defined in system.yaml)
$defaultErrorDocument = $I->haveAPageDocument('error', [], 'en');
+ $I->haveAUnPublishedDocument($defaultErrorDocument);
$document1 = $I->haveAPageDocument('en', [], 'en');
$document2 = $I->haveAPageDocument('de', [], 'de');
diff --git a/tests/functional.default.country/StaticRouteKeyTranslationCest.php b/tests/functional.default.country/StaticRouteKeyTranslationCest.php
index f4fbdab..1074fe8 100644
--- a/tests/functional.default.country/StaticRouteKeyTranslationCest.php
+++ b/tests/functional.default.country/StaticRouteKeyTranslationCest.php
@@ -62,7 +62,8 @@ public function testLocalizedStaticRouteZoneSiteException(FunctionalTester $I)
]);
$exception = ZoneSiteNotFoundException::class;
- $exceptionMessage = 'No zone site for locale "en_GB" found. Available zone (Id: 0) site locales: en';
+ // no available locales since static route build happens in without full bootstrap
+ $exceptionMessage = 'No zone site for locale "en_GB" found. Available zone (Id 0) site locales: ';
$I->seeException($exception, $exceptionMessage, function () use ($I, $staticRoute) {
$I->amOnStaticRoute($staticRoute->getName(), [