[BUGFIX] Resolve slashed values in PageTypeDecorator correctly 12/58512/15
authorOliver Hader <oliver@typo3.org>
Sun, 30 Sep 2018 22:52:13 +0000 (00:52 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Mon, 1 Oct 2018 18:20:37 +0000 (20:20 +0200)
* slashed values are not resolved correctly concerning their
  dynamic '/' prefix in route paths
* adjusting route path and parameters might cause side effects
  when dealing with multiple decorating enhancers
* test cases for resolving and generating URIs using new
  PageTypeDecorator enhancer are added

Resolves: #86506
Releases: master
Change-Id: I3757933c47ea0f06d88bb27a635c2077e8f673b1
Reviewed-on: https://review.typo3.org/58512
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/core/Classes/Routing/Enhancer/AbstractEnhancer.php
typo3/sysext/core/Classes/Routing/Enhancer/DecoratingEnhancerInterface.php
typo3/sysext/core/Classes/Routing/Enhancer/PageTypeDecorator.php
typo3/sysext/core/Classes/Routing/PageRouter.php
typo3/sysext/core/Classes/Routing/PageUriMatcher.php
typo3/sysext/core/Documentation/Changelog/master/Feature-86160-PageTypeEnhancerForMappingTypeParameter.rst
typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerLinkGeneratorTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/EnhancerSiteRequestTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkHandlingController.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkRequest.typoscript

index b400ffe..f342479 100644 (file)
@@ -84,17 +84,17 @@ abstract class AbstractEnhancer implements EnhancerInterface
      */
     protected function resolveType(Route $route, array &$remainingQueryParameters): string
     {
+        $type = 0;
         $decoratedParameters = $route->getOption('_decoratedParameters');
-        if (!isset($decoratedParameters['type'])) {
-            return '0';
+        if (isset($decoratedParameters['type'])) {
+            $type = $decoratedParameters['type'];
+            unset($decoratedParameters['type']);
+            $remainingQueryParameters = array_replace_recursive(
+                $remainingQueryParameters,
+                $decoratedParameters
+            );
         }
-        $type = (string)$decoratedParameters['type'];
-        unset($decoratedParameters['type']);
-        $remainingQueryParameters = array_replace_recursive(
-            $remainingQueryParameters,
-            $decoratedParameters
-        );
-        return $type;
+        return (string)$type;
     }
 
     /**
index 3574c79..bece1f4 100644 (file)
@@ -24,22 +24,34 @@ use TYPO3\CMS\Core\Routing\RouteCollection;
 interface DecoratingEnhancerInterface extends EnhancerInterface
 {
     /**
-     * Decorates route collection and modifies route parameters and the
-     * URL path to be processed during URL resolving. Executed before invoking
-     * routing enhancers.
+     * Gets pattern that can be used to redecorate (undecorate)
+     * a potential previously decorated route path.
+     *
+     * Example:
+     * + route path: 'first/second.html'
+     * + redecoration pattern: '(?:\.html|\.json)$'
+     * -> 'first/second' might be the redecorated route path after
+     *    applying the redecoration pattern to preg_match/preg_replace
+     *
+     * @return string regular expression pattern
+     */
+    public function getRoutePathRedecorationPattern(): string;
+
+    /**
+     * Decorates route collection to be processed during URL resolving.
+     * Executed before invoking routing enhancers.
      *
      * @param RouteCollection $collection
-     * @param array $parameters reference to reconstituted parameters
-     * @param string $routePath reference to URL path
+     * @param string $routePath URL path
      */
-    public function decorateForMatching(RouteCollection $collection, array &$parameters, string &$routePath): void;
+    public function decorateForMatching(RouteCollection $collection, string $routePath): void;
 
     /**
-     * Decorates route collection and modifies route parameters during URL
-     * URL generation. Executed before invoking routing enhancers.
+     * Decorates route collection during URL URL generation.
+     * Executed before invoking routing enhancers.
      *
      * @param RouteCollection $collection
-     * @param array $parameters reference to query parameters
+     * @param array $parameters query parameters
      */
-    public function decorateForGeneration(RouteCollection $collection, array &$parameters): void;
+    public function decorateForGeneration(RouteCollection $collection, array $parameters): void;
 }
index 1d539fc..63cbc10 100644 (file)
@@ -29,13 +29,14 @@ use TYPO3\CMS\Core\Routing\RouteCollection;
  *   PageTypeSuffix:
  *     type: PageType
  *     default: ''
+ *     index: 'index'
  *     map:
  *       '.html': 1
  *       'menu.json': 13
  */
 class PageTypeDecorator extends AbstractEnhancer implements DecoratingEnhancerInterface
 {
-    protected const PREFIXES = ['.', '-', '_'];
+    protected const ROUTE_PATH_DELIMITERS = ['.', '-', '_'];
 
     /**
      * @var array
@@ -48,6 +49,11 @@ class PageTypeDecorator extends AbstractEnhancer implements DecoratingEnhancerIn
     protected $default;
 
     /**
+     * @var string
+     */
+    protected $index;
+
+    /**
      * @var array
      */
     protected $map;
@@ -58,54 +64,79 @@ class PageTypeDecorator extends AbstractEnhancer implements DecoratingEnhancerIn
     public function __construct(array $configuration)
     {
         $default = $configuration['default'] ?? '';
+        $index = $configuration['index'] ?? 'index';
         $map = $configuration['map'] ?? null;
 
         if (!is_string($default)) {
             throw new \InvalidArgumentException('default must be string', 1538327508);
         }
+        if (!is_string($index)) {
+            throw new \InvalidArgumentException('index must be string', 1538327509);
+        }
         if (!is_array($map)) {
-            throw new \InvalidArgumentException('map must be array', 1538327509);
+            throw new \InvalidArgumentException('map must be array', 1538327510);
         }
 
         $this->configuration = $configuration;
         $this->default = $default;
+        $this->index = $index;
         $this->map = array_map('strval', $map);
     }
 
     /**
+     * @return string
+     */
+    public function getRoutePathRedecorationPattern(): string
+    {
+        return $this->buildRegularExpressionPattern(false);
+    }
+
+    /**
      * {@inheritdoc}
      */
-    public function decorateForMatching(RouteCollection $collection, array &$parameters, string &$routePath): void
+    public function decorateForMatching(RouteCollection $collection, string $routePath): void
     {
+        $decoratedRoutePath = null;
+        $decoratedParameters = ['type' => 0];
+
         $pattern = $this->buildRegularExpressionPattern();
-        if (!preg_match('#' . $pattern . '#', $routePath, $matches, PREG_UNMATCHED_AS_NULL)) {
-            $parameters['type'] = 0;
-            return;
-        }
+        if (preg_match('#(?P<decoration>(?:' . $pattern . '))#', $routePath, $matches, PREG_UNMATCHED_AS_NULL)) {
+            if (!isset($matches['decoration'])) {
+                throw new \UnexpectedValueException(
+                    'Unexpected null value at end of URL',
+                    1538335671
+                );
+            }
 
-        $value = $matches['slashedItems'] ?? $matches['regularItems'] ?? null;
-        if (!is_string($value)) {
-            throw new \UnexpectedValueException(
-                'Unexpected null value at end of URL',
-                1538335671
-            );
+            $routePathValue = $matches['decoration'];
+            $parameterValue = $matches['indexItems'] ?? $matches['slashedItems'] ?? $matches['regularItems'];
+            $routePathValuePattern = $this->quoteForRegularExpressionPattern($routePathValue) . '$';
+            $decoratedRoutePath = preg_replace('#' . $routePathValuePattern . '#', '', $routePath);
+            $decoratedParameters = ['type' => $this->map[$parameterValue] ?? 0];
         }
 
-        $parameters['type'] = $this->map[$value] ?? 0;
-        $valuePattern = $this->quoteForRegularExpressionPattern($value) . '$';
-        $routePath = preg_replace('#' . $valuePattern . '#', '', $routePath);
+        foreach ($collection->all() as $route) {
+            if ($decoratedRoutePath !== null) {
+                $route->setOption(
+                    '_decoratedRoutePath',
+                    '/' . trim($decoratedRoutePath, '/')
+                );
+            }
+            $route->setOption('_decoratedParameters', $decoratedParameters);
+        }
     }
 
     /**
      * {@inheritdoc}
      */
-    public function decorateForGeneration(RouteCollection $collection, array &$parameters): void
+    public function decorateForGeneration(RouteCollection $collection, array $parameters): void
     {
         $type = isset($parameters['type']) ? (string)$parameters['type'] : null;
         $value = $this->resolveValue($type);
-        unset($parameters['type']);
 
-        if ($value !== '' && !in_array($value{0}, static::PREFIXES)) {
+        $considerIndex = $value !== ''
+            && in_array($value{0}, static::ROUTE_PATH_DELIMITERS);
+        if ($value !== '' && !in_array($value{0}, static::ROUTE_PATH_DELIMITERS)) {
             $value = '/' . $value;
         }
 
@@ -114,8 +145,12 @@ class PageTypeDecorator extends AbstractEnhancer implements DecoratingEnhancerIn
          * @var Route $existingRoute
          */
         foreach ($collection->all() as $routeName => $existingRoute) {
-            $existingRoute->setPath(rtrim($existingRoute->getPath(), '/') . $value);
-            $deflatedParameters = $existingRoute->getOption('deflatedParameters');
+            $existingRoutePath = rtrim($existingRoute->getPath(), '/');
+            if ($considerIndex && $existingRoutePath === '') {
+                $existingRoutePath = $this->index;
+            }
+            $existingRoute->setPath($existingRoutePath . $value);
+            $deflatedParameters = $existingRoute->getOption('deflatedParameters') ?? $parameters;
             if (isset($deflatedParameters['type'])) {
                 unset($deflatedParameters['type']);
                 $existingRoute->setOption(
@@ -143,9 +178,11 @@ class PageTypeDecorator extends AbstractEnhancer implements DecoratingEnhancerIn
 
     /**
      * Builds a regexp out of the map.
+     *
+     * @param bool $useNames
      * @return string
      */
-    protected function buildRegularExpressionPattern(): string
+    protected function buildRegularExpressionPattern(bool $useNames = true): string
     {
         $items = array_keys($this->map);
         $slashedItems = array_filter($items, [$this, 'needsSlashPrefix']);
@@ -156,10 +193,17 @@ class PageTypeDecorator extends AbstractEnhancer implements DecoratingEnhancerIn
 
         $patterns = [];
         if (!empty($slashedItems)) {
-            $patterns[] = '/(?P<slashedItems>' . implode('|', $slashedItems) . ')';
+            $name = $useNames ? '?P<slashedItems>' : '';
+            $patterns[] = '(?:^|/)(' . $name . implode('|', $slashedItems) . ')';
+        }
+        if (!empty($regularItems) && !empty($this->index)) {
+            $name = $useNames ? '?P<indexItems>' : '';
+            $indexPattern = $this->quoteForRegularExpressionPattern($this->index);
+            $patterns[] = '(' . $name . $indexPattern . '(?:' . implode('|', $regularItems) . '))';
         }
         if (!empty($regularItems)) {
-            $patterns[] = '(?P<regularItems>' . implode('|', $regularItems) . ')';
+            $name = $useNames ? '?P<regularItems>' : '';
+            $patterns[] = '(' . $name . implode('|', $regularItems) . ')';
         }
         return '(?:' . implode('|', $patterns) . ')$';
     }
@@ -185,7 +229,7 @@ class PageTypeDecorator extends AbstractEnhancer implements DecoratingEnhancerIn
     {
         return !in_array(
             $value{0} ?? '',
-            static::PREFIXES,
+            static::ROUTE_PATH_DELIMITERS,
             true
         );
     }
index 92dfd6e..2801912 100644 (file)
@@ -121,7 +121,6 @@ class PageRouter implements RouterInterface
             throw new RouteNotFoundException('No page candidates found for path "' . $urlPath . '"', 1538389999);
         }
 
-        $decoratedParameters = [];
         $fullCollection = new RouteCollection();
         foreach ($pageCandidates ?? [] as $page) {
             $pageIdForDefaultLanguage = (int)($page['l10n_parent'] ?: $page['uid']);
@@ -137,7 +136,7 @@ class PageRouter implements RouterInterface
             $enhancers = $this->getEnhancersForPage($pageIdForDefaultLanguage, $language);
             foreach ($enhancers as $enhancer) {
                 if ($enhancer instanceof DecoratingEnhancerInterface) {
-                    $enhancer->decorateForMatching($pageCollection, $decoratedParameters, $urlPath);
+                    $enhancer->decorateForMatching($pageCollection, $urlPath);
                 }
             }
             foreach ($enhancers as $enhancer) {
@@ -155,7 +154,6 @@ class PageRouter implements RouterInterface
             $result = $matcher->match('/' . trim($urlPath, '/'));
             /** @var Route $matchedRoute */
             $matchedRoute = $fullCollection->get($result['_route']);
-            $matchedRoute->setOption('_decoratedParameters', $decoratedParameters);
             return $this->buildPageArguments($matchedRoute, $result, $request->getQueryParams());
         } catch (ResourceNotFoundException $e) {
             // Do nothing
@@ -356,7 +354,6 @@ class PageRouter implements RouterInterface
                 continue;
             }
             $enhancerType = $enhancerConfiguration['type'] ?? '';
-            /** @var EnhancerInterface $enhancer */
             $enhancer = $this->enhancerFactory->create($enhancerType, $enhancerConfiguration);
             if (!empty($enhancerConfiguration['aspects'] ?? null)) {
                 $aspects = $this->aspectFactory->createAspects(
@@ -371,6 +368,48 @@ class PageRouter implements RouterInterface
     }
 
     /**
+     * Resolves decorating enhancers without having aspects assigned. These
+     * instances are used to pre-process URL path and MUST NOT be used for
+     * actually resolving or generating URL parameters.
+     *
+     * @return DecoratingEnhancerInterface[]
+     */
+    protected function getDecoratingEnhancers(): array
+    {
+        $enhancers = [];
+        foreach ($this->site->getConfiguration()['routeEnhancers'] ?? [] as $enhancerConfiguration) {
+            $enhancerType = $enhancerConfiguration['type'] ?? '';
+            $enhancer = $this->enhancerFactory->create($enhancerType, $enhancerConfiguration);
+            if ($enhancer instanceof DecoratingEnhancerInterface) {
+                $enhancers[] = $enhancer;
+            }
+        }
+        return $enhancers;
+    }
+
+    /**
+     * Gets all patterns that can be used to redecorate (undecorate) a
+     * potential previously decorated route path.
+     *
+     * @return string regular expression pattern capable of redecorating
+     */
+    protected function getRoutePathRedecorationPattern(): string
+    {
+        $decoratingEnhancers = $this->getDecoratingEnhancers();
+        if (empty($decoratingEnhancers)) {
+            return '';
+        }
+        $redecorationPatterns = array_map(
+            function (DecoratingEnhancerInterface $decorationEnhancers) {
+                $pattern = $decorationEnhancers->getRoutePathRedecorationPattern();
+                return '(?:' . $pattern . ')';
+            },
+            $decoratingEnhancers
+        );
+        return '(?P<decoration>' . implode('|', $redecorationPatterns) . ')';
+    }
+
+    /**
      * Returns possible URL parts for a string like /home/about-us/offices/ or /home/about-us/offices.json
      * to return.
      *
@@ -387,19 +426,19 @@ class PageRouter implements RouterInterface
      */
     protected function getCandidateSlugsFromRoutePath(string $routePath): array
     {
+        $redecorationPattern = $this->getRoutePathRedecorationPattern();
+        if (!empty($redecorationPattern) && preg_match('#' . $redecorationPattern . '#', $routePath, $matches)) {
+            $decoration = $matches['decoration'];
+            $decorationPattern = preg_quote($decoration, '#');
+            $routePath = preg_replace('#' . $decorationPattern . '$#', '', $routePath);
+        }
+
         $candidatePathParts = [];
         $pathParts = GeneralUtility::trimExplode('/', $routePath, true);
         if (empty($pathParts)) {
             return ['/'];
         }
-        // Check if the last part contains a ".", then split it
-        // @todo fix me based on enhancer configuration
-        $lastPart = array_pop($pathParts);
-        if (strpos($lastPart, '.') !== false) {
-            $pathParts = array_merge($pathParts, explode('.', $lastPart));
-        } else {
-            $pathParts[] = $lastPart;
-        }
+
         while (!empty($pathParts)) {
             $prefix = '/' . implode('/', $pathParts);
             $candidatePathParts[] = $prefix . '/';
@@ -479,17 +518,17 @@ class PageRouter implements RouterInterface
      */
     protected function resolveType(Route $route, array &$remainingQueryParameters): string
     {
+        $type = 0;
         $decoratedParameters = $route->getOption('_decoratedParameters');
-        if (!isset($decoratedParameters['type'])) {
-            return '0';
+        if (isset($decoratedParameters['type'])) {
+            $type = $decoratedParameters['type'];
+            unset($decoratedParameters['type']);
+            $remainingQueryParameters = array_replace_recursive(
+                $remainingQueryParameters,
+                $decoratedParameters
+            );
         }
-        $type = (string)$decoratedParameters['type'];
-        unset($decoratedParameters['type']);
-        $remainingQueryParameters = array_replace_recursive(
-            $remainingQueryParameters,
-            $decoratedParameters
-        );
-        return $type;
+        return (string)$type;
     }
 
     /**
index f564810..797d8a5 100644 (file)
@@ -74,6 +74,7 @@ class PageUriMatcher
     protected function matchCollection(string $urlPath, RouteCollection $routes): ?array
     {
         foreach ($routes as $name => $route) {
+            $urlPath = $this->getDecoratedRoutePath($route) ?? $urlPath;
             $compiledRoute = $route->compile();
 
             // check the static prefix of the URL first. Only use the more expensive preg_match when it matches
@@ -96,6 +97,22 @@ class PageUriMatcher
     }
 
     /**
+     * Resolves an optional route specific decorated route path that has been
+     * assigned by DecoratingEnhancerInterface instances.
+     *
+     * @param Route $route
+     * @return string|null
+     */
+    protected function getDecoratedRoutePath(Route $route): ?string
+    {
+        if (!$route->hasOption('_decoratedRoutePath')) {
+            return null;
+        }
+        $urlPath = $route->getOption('_decoratedRoutePath');
+        return rawurldecode($urlPath);
+    }
+
+    /**
      * Returns an array of values to use as request attributes.
      *
      * As this method requires the Route object, it is not available
index c31de52..95a3512 100644 (file)
@@ -46,21 +46,36 @@ Now configure the Route Enhancer in your site's `config.yaml` file like this:
          type: PageType
          default: ''
          map:
-         'rss.feed': 13
-         '.json': 26
+            'rss.feed': 13
+            '.json': 26
 
-It is also possible to set `default` to e.g. ".html" to add a ".html" suffix to all default pages.
 
 The `map` allows to add a filename or a file ending and map this to a `page.typeNum` value.
 
+It is also possible to set `default` to e.g. ".html" to add a ".html" suffix to all default pages.
+
+.. code-block:: yaml
+
+   routeEnhancers:
+      PageTypeSuffix:
+         type: PageType
+         default: '.json'
+         index: 'index'
+         map:
+            'rss.feed': 13
+            '.json': 26
+
+The `index` property is used when generating links on root-level page, thus, instead of e.g. having
+`/en/.json` thus would then result in `/en/index.json`.
+
 Impact
 ======
 
-The TYPO3 Frontend-internal `&type` parameter can now also be part of a speaking URL with a simple line
-of configuration.
+The TYPO3 Frontend-internal `&type` parameter can now also be part of a speaking URL with a simple
+line of configuration.
 
-Please note that the implementation is a Decorator Enhancer, which means that the PageTypeEnhancer is only
-there for adding suffixes to an existing route / variant, but not to substitute something within the middle
-of a speaking URL segment.
+Please note that the implementation is a Decorator Enhancer, which means that the PageTypeEnhancer
+is only there for adding suffixes to an existing route / variant, but not to substitute something
+within the middle of a speaking URL segment.
 
 .. index:: Frontend
index cbeb0cd..dd96f1a 100644 (file)
@@ -107,7 +107,7 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
      * @param array $languages
      * @param array $enhancers
      * @param string $variableName
-     * @param string $templateSuffix
+     * @param array $templateOptions
      * @return array
      */
     protected function createDataSet(
@@ -115,7 +115,7 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
         array $languages,
         array $enhancers,
         string $variableName = 'value',
-        string $templateSuffix = ''
+        array $templateOptions = []
     ): array {
         $dataSet = [];
         foreach ($enhancers as $enhancer) {
@@ -131,9 +131,11 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
                 ];
             }
         }
+        $templatePrefix = isset($templateOptions['prefix']) ? $templateOptions['prefix'] : '';
+        $templateSuffix = isset($templateOptions['suffix']) ? $templateOptions['suffix'] : '';
         return $this->keysFromTemplate(
             $dataSet,
-            'enhancer:%1$s, lang:%3$d' . $templateSuffix,
+            $templatePrefix . 'enhancer:%1$s, lang:%3$d' . $templateSuffix,
             function (array $items) {
                 array_splice(
                     $items,
@@ -146,12 +148,19 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
         );
     }
 
+    /**
+     * @param array $options
+     * @return array
+     */
     protected function getEnhancers(array $options = []): array
     {
-        $options = array_merge(['name' => 'enhance', 'value' => 100], $options);
+        $options = array_merge(
+            ['name' => 'enhance', 'value' => 100, 'additionalParameters' => ''],
+            $options
+        );
         return [
             [
-                'parameters' => sprintf('&value=%s', $options['value']),
+                'parameters' => sprintf('&value=%s%s', $options['value'], $options['additionalParameters']),
                 'enhancer' => [
                     'type' => 'Simple',
                     'routePath' => sprintf('/%s/{value}', $options['name']),
@@ -159,7 +168,7 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
                 ],
             ],
             [
-                'parameters' => sprintf('&testing[value]=%s', $options['value']),
+                'parameters' => sprintf('&testing[value]=%s%s', $options['value'], $options['additionalParameters']),
                 'enhancer' => [
                     'type' => 'Plugin',
                     'routePath' => sprintf('/%s/{value}', $options['name']),
@@ -168,7 +177,11 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
                 ],
             ],
             [
-                'parameters' => sprintf('&tx_testing_link[value]=%s&tx_testing_link[controller]=Link&tx_testing_link[action]=index', $options['value']),
+                'parameters' => sprintf(
+                    '&tx_testing_link[value]=%s&tx_testing_link[controller]=Link&tx_testing_link[action]=index%s',
+                    $options['value'],
+                    $options['additionalParameters']
+                ),
                 'enhancer' => [
                     'type' => 'Extbase',
                     'routes' => [
@@ -188,8 +201,28 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
     /**
      * @return array
      */
-    public function localeModifierDataProvider(): array
+    protected function createPageTypeDecorator(): array
+    {
+        return [
+            'type' => 'PageType',
+            'default' => '.html',
+            'index' => 'index',
+            'map' => [
+                '.html' =>  0,
+                'menu.json' =>  10,
+            ]
+        ];
+    }
+
+    /**
+     * @param string|array|null $options
+     * @return array
+     */
+    public function localeModifierDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'LocaleModifier',
             'default' => 'enhance',
@@ -202,15 +235,19 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
         ];
 
         $languages = [
-            '0' => 'https://acme.us/welcome/enhance/100?cHash=',
-            '1' => 'https://acme.fr/bienvenue/augmenter/100?cHash=',
+            '0' => sprintf('https://acme.us/welcome/enhance/100%s?cHash=', $options['pathSuffix'] ?? ''),
+            '1' => sprintf('https://acme.fr/bienvenue/augmenter/100%s?cHash=', $options['pathSuffix'] ?? ''),
         ];
 
         return $this->createDataSet(
             $aspect,
             $languages,
-            $this->getEnhancers(['name' => '{enhance_name}']),
-            'enhance_name'
+            $this->getEnhancers([
+                'name' => '{enhance_name}',
+                'additionalParameters' => $options['additionalParameters'] ?? ''
+            ]),
+            'enhance_name',
+            ['prefix' => 'localeModifier/']
         );
     }
 
@@ -247,10 +284,14 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
     }
 
     /**
+     * @param string|array|null $options
      * @return array
      */
-    public function persistedAliasMapperDataProvider(): array
+    public function persistedAliasMapperDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'PersistedAliasMapper',
             'tableName' => 'pages',
@@ -259,14 +300,19 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
         ];
 
         $languages = [
-            '0' => 'https://acme.us/welcome/enhance/welcome',
-            '1' => 'https://acme.fr/bienvenue/enhance/bienvenue',
+            '0' => sprintf('https://acme.us/welcome/enhance/welcome%s', $options['pathSuffix'] ?? ''),
+            '1' => sprintf('https://acme.fr/bienvenue/enhance/bienvenue%s', $options['pathSuffix'] ?? ''),
         ];
 
         return $this->createDataSet(
             $aspect,
             $languages,
-            $this->getEnhancers(['value' => 1100])
+            $this->getEnhancers([
+                'value' => 1100,
+                'additionalParameters' => $options['additionalParameters'] ?? ''
+            ]),
+            'value',
+            ['prefix' => 'persistedAliasMapper/']
         );
     }
 
@@ -303,10 +349,14 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
     }
 
     /**
+     * @param string|array|null $options
      * @return array
      */
-    public function persistedPatternMapperDataProvider(): array
+    public function persistedPatternMapperDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'PersistedPatternMapper',
             'tableName' => 'pages',
@@ -315,14 +365,19 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
         ];
 
         $languages = [
-            '0' => 'https://acme.us/welcome/enhance/hello-and-welcome-1100',
-            '1' => 'https://acme.fr/bienvenue/enhance/salut-et-bienvenue-1100',
+            '0' => sprintf('https://acme.us/welcome/enhance/hello-and-welcome-1100%s', $options['pathSuffix'] ?? ''),
+            '1' => sprintf('https://acme.fr/bienvenue/enhance/salut-et-bienvenue-1100%s', $options['pathSuffix'] ?? ''),
         ];
 
         return $this->createDataSet(
             $aspect,
             $languages,
-            $this->getEnhancers(['value' => 1100])
+            $this->getEnhancers([
+                'value' => 1100,
+                'additionalParameters' => $options['additionalParameters'] ?? ''
+            ]),
+            'value',
+            ['prefix' => 'persistedPatternMapper/']
         );
     }
 
@@ -359,10 +414,14 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
     }
 
     /**
+     * @param string|array|null $options
      * @return array
      */
-    public function staticValueMapperDataProvider(): array
+    public function staticValueMapperDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'StaticValueMapper',
             'map' => [
@@ -379,11 +438,19 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
         ];
 
         $languages = [
-            '0' => 'https://acme.us/welcome/enhance/hundred',
-            '1' => 'https://acme.fr/bienvenue/enhance/cent',
+            '0' => sprintf('https://acme.us/welcome/enhance/hundred%s', $options['pathSuffix'] ?? ''),
+            '1' => sprintf('https://acme.fr/bienvenue/enhance/cent%s', $options['pathSuffix'] ?? ''),
         ];
 
-        return $this->createDataSet($aspect, $languages, $this->getEnhancers());
+        return $this->createDataSet(
+            $aspect,
+            $languages,
+            $this->getEnhancers([
+                'additionalParameters' => $options['additionalParameters'] ?? ''
+            ]),
+            'value',
+            ['prefix' => 'staticValueMapper/']
+        );
     }
 
     /**
@@ -419,10 +486,14 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
     }
 
     /**
+     * @param string|array|null $options
      * @return array
      */
-    public function staticRangeMapperDataProvider(): array
+    public function staticRangeMapperDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'StaticRangeMapper',
             'start' => '1',
@@ -432,8 +503,8 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
         $dataSet = [];
         foreach (range(10, 100, 30) as $value) {
             $languages = [
-                '0' => sprintf('https://acme.us/welcome/enhance/%s', $value),
-                '1' => sprintf('https://acme.fr/bienvenue/enhance/%s', $value),
+                '0' => sprintf('https://acme.us/welcome/enhance/%s%s', $value, $options['pathSuffix'] ?? ''),
+                '1' => sprintf('https://acme.fr/bienvenue/enhance/%s%s', $value, $options['pathSuffix'] ?? ''),
             ];
 
             $dataSet = array_merge(
@@ -441,9 +512,15 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
                 $this->createDataSet(
                     $aspect,
                     $languages,
-                    $this->getEnhancers(['value' => $value]),
+                    $this->getEnhancers([
+                        'value' => $value,
+                        'additionalParameters' => $options['additionalParameters'] ?? ''
+                    ]),
                     'value',
-                    sprintf(', value:%d', $value)
+                    [
+                        'prefix' => 'staticRangeMapper/',
+                        'suffix' => sprintf(', value:%d', $value),
+                    ]
                 )
             );
         }
@@ -481,4 +558,87 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
 
         static::assertStringStartsWith($expectation, (string)$response->getBody());
     }
+
+    /**
+     * Combines all previous data providers for mappable aspects into one large
+     * data set that is permuted for several page type decorator instructions.
+     *
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $instructions = [
+            ['pathSuffix' => '.html', 'type' => null],
+            ['pathSuffix' => '.html', 'type' => 0],
+            ['pathSuffix' => '/menu.json', 'type' => 10],
+        ];
+
+        $dataSet = [];
+        foreach ($instructions as $instruction) {
+            $templateSuffix = sprintf(
+                ' [%s=>%s]',
+                $instruction['pathSuffix'],
+                $instruction['type'] ?? 'null'
+            );
+            $dataProviderOptions = [
+                'pathSuffix' => $instruction['pathSuffix'],
+                'additionalParameters' => $instruction['type'] !== null
+                    ? '&type=' . $instruction['type']
+                    : ''
+            ];
+            $dataSetCandidates = array_merge(
+                $this->localeModifierDataProvider($dataProviderOptions),
+                $this->persistedAliasMapperDataProvider($dataProviderOptions),
+                $this->persistedPatternMapperDataProvider($dataProviderOptions),
+                $this->staticValueMapperDataProvider($dataProviderOptions),
+                $this->staticRangeMapperDataProvider($dataProviderOptions)
+            );
+            $dataSetCandidatesKeys = array_map(
+                function (string $dataSetCandidatesKey) use ($templateSuffix) {
+                    return $dataSetCandidatesKey . $templateSuffix;
+                },
+                array_keys($dataSetCandidates)
+            );
+            $dataSet = array_merge(
+                $dataSet,
+                array_combine($dataSetCandidatesKeys, $dataSetCandidates)
+            );
+        }
+        return $dataSet;
+    }
+
+    /**
+     * @param array $enhancer
+     * @param string $additionalParameters
+     * @param int $targetLanguageId
+     * @param string $expectation
+     *
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(array $enhancer, string $additionalParameters, int $targetLanguageId, string $expectation)
+    {
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => [
+                'Enhancer' => $enhancer,
+                'PageType' => $this->createPageTypeDecorator()
+            ]
+        ]);
+
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest('https://acme.us/'))
+                ->withPageId(1100)
+                ->withInstructions([
+                    $this->createTypoLinkUrlInstruction([
+                        'parameter' => 1100,
+                        'language' => $targetLanguageId,
+                        'additionalParams' => $additionalParameters,
+                        'forceAbsoluteUrl' => 1,
+                    ])
+                ]),
+            $this->internalRequestContext
+        );
+
+        static::assertStringStartsWith($expectation, (string)$response->getBody());
+    }
 }
index 71f657f..04067ce 100644 (file)
@@ -107,7 +107,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
      * @param array $enhancerLanguageUris
      * @param array $enhancers
      * @param string $variableName
-     * @param string $templateSuffix
+     * @param array $templateOptions
      * @return array
      */
     protected function createDataSet(
@@ -115,7 +115,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
         array $enhancerLanguageUris,
         array $enhancers,
         string $variableName = 'value',
-        string $templateSuffix = ''
+        array $templateOptions = []
     ): array {
         $dataSet = [];
         foreach ($enhancers as $enhancer) {
@@ -140,9 +140,11 @@ class EnhancerSiteRequestTest extends AbstractTestCase
                 ];
             }
         }
+        $templatePrefix = isset($templateOptions['prefix']) ? $templateOptions['prefix'] : '';
+        $templateSuffix = isset($templateOptions['suffix']) ? $templateOptions['suffix'] : '';
         return $this->keysFromTemplate(
             $dataSet,
-            'enhancer:%1$s, lang:%3$d' . $templateSuffix,
+            $templatePrefix . 'enhancer:%1$s, lang:%3$d' . $templateSuffix,
             function (array $items) {
                 array_splice(
                     $items,
@@ -226,8 +228,28 @@ class EnhancerSiteRequestTest extends AbstractTestCase
     /**
      * @return array
      */
-    public function localeModifierDataProvider(): array
+    protected function createPageTypeDecorator(): array
     {
+        return [
+            'type' => 'PageType',
+            'default' => '.html',
+            'index' => 'index',
+            'map' => [
+                '.html' =>  0,
+                'menu.json' =>  10,
+            ]
+        ];
+    }
+
+    /**
+     * @param string|array|null $options
+     * @return array
+     */
+    public function localeModifierDataProvider($options = null): array
+    {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'LocaleModifier',
             'default' => 'enhance',
@@ -241,24 +263,35 @@ class EnhancerSiteRequestTest extends AbstractTestCase
 
         $enhancerLanguageUris = [
             'Simple' => [
-                '0' => 'https://acme.us/welcome/enhance/100?cHash=46227b4ce096dc78a4e71463326c9020',
-                '1' => 'https://acme.fr/bienvenue/augmenter/100?cHash=46227b4ce096dc78a4e71463326c9020',
+                '0' => 'https://acme.us/welcome/enhance/100%s?cHash=46227b4ce096dc78a4e71463326c9020',
+                '1' => 'https://acme.fr/bienvenue/augmenter/100%s?cHash=46227b4ce096dc78a4e71463326c9020',
             ],
             'Plugin' => [
-                '0' => 'https://acme.us/welcome/enhance/100?cHash=e24d3d2d5503baba670d827c3b9470c8',
-                '1' => 'https://acme.fr/bienvenue/augmenter/100?cHash=e24d3d2d5503baba670d827c3b9470c8',
+                '0' => 'https://acme.us/welcome/enhance/100%s?cHash=e24d3d2d5503baba670d827c3b9470c8',
+                '1' => 'https://acme.fr/bienvenue/augmenter/100%s?cHash=e24d3d2d5503baba670d827c3b9470c8',
             ],
             'Extbase' => [
-                '0' => 'https://acme.us/welcome/enhance/100?cHash=eef21771ab3c3dac3514b4479eedd5ff',
-                '1' => 'https://acme.fr/bienvenue/augmenter/100?cHash=eef21771ab3c3dac3514b4479eedd5ff',
+                '0' => 'https://acme.us/welcome/enhance/100%s?cHash=eef21771ab3c3dac3514b4479eedd5ff',
+                '1' => 'https://acme.fr/bienvenue/augmenter/100%s?cHash=eef21771ab3c3dac3514b4479eedd5ff',
             ]
         ];
 
+        $pathSuffix = $options['pathSuffix'] ?? '';
+        foreach ($enhancerLanguageUris as &$enhancerUris) {
+            $enhancerUris = array_map(
+                function (string $enhancerUri) use ($pathSuffix) {
+                    return sprintf($enhancerUri, $pathSuffix);
+                },
+                $enhancerUris
+            );
+        }
+
         return $this->createDataSet(
             $aspect,
             $enhancerLanguageUris,
             $this->getEnhancers(['name' => '{enhance_name}']),
-            'enhance_name'
+            'enhance_name',
+            ['prefix' => 'localeModifier/']
         );
     }
 
@@ -266,7 +299,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
      * @param array $enhancer
      * @param string $targetUri
      * @param int $expectedLanguageId
-     * @param string $expectation
+     * @param array $expectation
      *
      * @test
      * @dataProvider localeModifierDataProvider
@@ -282,10 +315,14 @@ class EnhancerSiteRequestTest extends AbstractTestCase
     }
 
     /**
+     * @param string|array|null $options
      * @return array
      */
-    public function persistedAliasMapperDataProvider(): array
+    public function persistedAliasMapperDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'PersistedAliasMapper',
             'tableName' => 'pages',
@@ -296,15 +333,17 @@ class EnhancerSiteRequestTest extends AbstractTestCase
         $enhancerLanguageUris = $this->populateToKeys(
             ['Simple', 'Plugin', 'Extbase'],
             [
-                '0' => 'https://acme.us/welcome/enhance/welcome',
-                '1' => 'https://acme.fr/bienvenue/enhance/bienvenue',
+                '0' => sprintf('https://acme.us/welcome/enhance/welcome%s', $options['pathSuffix'] ?? ''),
+                '1' => sprintf('https://acme.fr/bienvenue/enhance/bienvenue%s', $options['pathSuffix'] ?? ''),
             ]
         );
 
         return $this->createDataSet(
             $aspect,
             $enhancerLanguageUris,
-            $this->getEnhancers(['value' => 1100], true)
+            $this->getEnhancers(['value' => 1100], true),
+            'value',
+            ['prefix' => 'persistedAliasMapper/']
         );
     }
 
@@ -312,7 +351,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
      * @param array $enhancer
      * @param string $targetUri
      * @param int $expectedLanguageId
-     * @param string $expectation
+     * @param array $expectation
      *
      * @test
      * @dataProvider persistedAliasMapperDataProvider
@@ -328,10 +367,14 @@ class EnhancerSiteRequestTest extends AbstractTestCase
     }
 
     /**
+     * @param string|array|null $options
      * @return array
      */
-    public function persistedPatternMapperDataProvider(): array
+    public function persistedPatternMapperDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'PersistedPatternMapper',
             'tableName' => 'pages',
@@ -342,15 +385,17 @@ class EnhancerSiteRequestTest extends AbstractTestCase
         $enhancerLanguageUris = $this->populateToKeys(
             ['Simple', 'Plugin', 'Extbase'],
             [
-                '0' => 'https://acme.us/welcome/enhance/hello-and-welcome-1100',
-                '1' => 'https://acme.fr/bienvenue/enhance/salut-et-bienvenue-1100',
+                '0' => sprintf('https://acme.us/welcome/enhance/hello-and-welcome-1100%s', $options['pathSuffix'] ?? ''),
+                '1' => sprintf('https://acme.fr/bienvenue/enhance/salut-et-bienvenue-1100%s', $options['pathSuffix'] ?? ''),
             ]
         );
 
         return $this->createDataSet(
             $aspect,
             $enhancerLanguageUris,
-            $this->getEnhancers(['value' => 1100], true)
+            $this->getEnhancers(['value' => 1100], true),
+            'value',
+            ['prefix' => 'persistedPatternMapper/']
         );
     }
 
@@ -358,7 +403,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
      * @param array $enhancer
      * @param string $targetUri
      * @param int $expectedLanguageId
-     * @param string $expectation
+     * @param array $expectation
      *
      * @test
      * @dataProvider persistedPatternMapperDataProvider
@@ -374,10 +419,14 @@ class EnhancerSiteRequestTest extends AbstractTestCase
     }
 
     /**
+     * @param string|array|null $options
      * @return array
      */
-    public function staticValueMapperDataProvider(): array
+    public function staticValueMapperDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'StaticValueMapper',
             'map' => [
@@ -396,15 +445,17 @@ class EnhancerSiteRequestTest extends AbstractTestCase
         $enhancerLanguageUris = $this->populateToKeys(
             ['Simple', 'Plugin', 'Extbase'],
             [
-                '0' => 'https://acme.us/welcome/enhance/hundred',
-                '1' => 'https://acme.fr/bienvenue/enhance/cent',
+                '0' => sprintf('https://acme.us/welcome/enhance/hundred%s', $options['pathSuffix'] ?? ''),
+                '1' => sprintf('https://acme.fr/bienvenue/enhance/cent%s', $options['pathSuffix'] ?? ''),
             ]
         );
 
         return $this->createDataSet(
             $aspect,
             $enhancerLanguageUris,
-            $this->getEnhancers([], true)
+            $this->getEnhancers([], true),
+            'value',
+            ['prefix' => 'staticValueMapper/']
         );
     }
 
@@ -412,7 +463,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
      * @param array $enhancer
      * @param string $targetUri
      * @param int $expectedLanguageId
-     * @param string $expectation
+     * @param array $expectation
      *
      * @test
      * @dataProvider staticValueMapperDataProvider
@@ -428,10 +479,14 @@ class EnhancerSiteRequestTest extends AbstractTestCase
     }
 
     /**
+     * @param string|array|null $options
      * @return array
      */
-    public function staticRangeMapperDataProvider(): array
+    public function staticRangeMapperDataProvider($options = null): array
     {
+        if (!is_array($options)) {
+            $options = [];
+        }
         $aspect = [
             'type' => 'StaticRangeMapper',
             'start' => '1',
@@ -443,8 +498,8 @@ class EnhancerSiteRequestTest extends AbstractTestCase
             $enhancerLanguageUris = $this->populateToKeys(
                 ['Simple', 'Plugin', 'Extbase'],
                 [
-                    '0' => sprintf('https://acme.us/welcome/enhance/%s', $value),
-                    '1' => sprintf('https://acme.fr/bienvenue/enhance/%s', $value),
+                    '0' => sprintf('https://acme.us/welcome/enhance/%s%s', $value, $options['pathSuffix'] ?? ''),
+                    '1' => sprintf('https://acme.fr/bienvenue/enhance/%s%s', $value, $options['pathSuffix'] ?? ''),
                 ]
             );
 
@@ -455,7 +510,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
                     $enhancerLanguageUris,
                     $this->getEnhancers(['value' => $value], true),
                     'value',
-                    sprintf(', value:%d', $value)
+                    ['prefix' => 'staticRangeMapper/', 'suffix' => sprintf(', value:%d', $value)]
                 )
             );
         }
@@ -466,7 +521,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
      * @param array $enhancer
      * @param string $targetUri
      * @param int $expectedLanguageId
-     * @param string $expectation
+     * @param array $expectation
      *
      * @test
      * @dataProvider staticRangeMapperDataProvider
@@ -482,6 +537,94 @@ class EnhancerSiteRequestTest extends AbstractTestCase
     }
 
     /**
+     * @return array
+     */
+    public function pageTypeDecoratorIsAppliedDataProvider(): array
+    {
+        $instructions = [
+            ['pathSuffix' => '.html', 'type' => null],
+            ['pathSuffix' => '.html', 'type' => 0],
+            ['pathSuffix' => '/menu.json', 'type' => 10],
+        ];
+
+        $dataSet = [];
+        foreach ($instructions as $instruction) {
+            $templateSuffix = sprintf(
+                ' [%s=>%s]',
+                $instruction['pathSuffix'],
+                $instruction['type'] ?? 'null'
+            );
+            $expectedPageType = (string)($instruction['type'] ?? 0);
+            $dataProviderOptions = [
+                'pathSuffix' => $instruction['pathSuffix'],
+            ];
+            $dataSetCandidates = array_merge(
+                $this->localeModifierDataProvider($dataProviderOptions),
+                $this->persistedAliasMapperDataProvider($dataProviderOptions),
+                $this->persistedPatternMapperDataProvider($dataProviderOptions),
+                $this->staticValueMapperDataProvider($dataProviderOptions),
+                $this->staticRangeMapperDataProvider($dataProviderOptions)
+            );
+            // add expected pageType to data set candidates
+            $dataSetCandidates = array_map(
+                function (array $dataSetCandidate) use ($expectedPageType) {
+                    $dataSetCandidate[3]['pageType'] = $expectedPageType;
+                    return $dataSetCandidate;
+                },
+                $dataSetCandidates
+            );
+            $dataSetCandidatesKeys = array_map(
+                function (string $dataSetCandidatesKey) use ($templateSuffix) {
+                    return $dataSetCandidatesKey . $templateSuffix;
+                },
+                array_keys($dataSetCandidates)
+            );
+            $dataSet = array_merge(
+                $dataSet,
+                array_combine($dataSetCandidatesKeys, $dataSetCandidates)
+            );
+        }
+        return $dataSet;
+    }
+
+    /**
+     * @param array $enhancer
+     * @param string $targetUri
+     * @param int $expectedLanguageId
+     * @param array $expectation
+     *
+     * @test
+     * @dataProvider pageTypeDecoratorIsAppliedDataProvider
+     */
+    public function pageTypeDecoratorIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
+    {
+        $this->mergeSiteConfiguration('acme-com', [
+            'routeEnhancers' => [
+                'Enhancer' => $enhancer,
+                'PageType' => $this->createPageTypeDecorator()
+            ]
+        ]);
+
+        $allParameters = array_replace_recursive(
+            $expectation['dynamicArguments'],
+            $expectation['staticArguments']
+        );
+        $expectation['pageId'] = 1100;
+        $expectation['languageId'] = $expectedLanguageId;
+        $expectation['requestQueryParams'] = $allParameters;
+        $expectation['_GET'] = $allParameters;
+
+        $response = $this->executeFrontendRequest(
+            new InternalRequest($targetUri),
+            $this->internalRequestContext,
+            true
+        );
+
+        $pageArguments = json_decode((string)$response->getBody(), true);
+        static::assertEquals($expectation, $pageArguments);
+    }
+
+    /**
      * @param array $enhancer
      * @param string $targetUri
      * @param int $expectedLanguageId
@@ -498,6 +641,7 @@ class EnhancerSiteRequestTest extends AbstractTestCase
             $expectation['staticArguments']
         );
         $expectation['pageId'] = 1100;
+        $expectation['pageType'] = '0';
         $expectation['languageId'] = $expectedLanguageId;
         $expectation['requestQueryParams'] = $allParameters;
         $expectation['_GET'] = $allParameters;
index 14ac057..380fbfa 100644 (file)
@@ -58,6 +58,7 @@ class LinkHandlingController
         $language = $request->getAttribute('language');
         return json_encode([
             'pageId' => $pageArguments->getPageId(),
+            'pageType' => $pageArguments->getPageType(),
             'languageId' => $language->getLanguageId(),
             'dynamicArguments' => $pageArguments->getDynamicArguments(),
             'staticArguments' => $pageArguments->getStaticArguments(),
index 20f11b4..e30bdc8 100644 (file)
@@ -17,3 +17,8 @@ page {
   10 = USER
   10.userFunc = TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Fixtures\LinkHandlingController->dumpPageArgumentsAction
 }
+
+menu < page
+menu {
+  typeNum = 10
+}