From f2f79d955f97820dc9b572f26d68ada3ce713ed6 Mon Sep 17 00:00:00 2001
From: Friedemann Altrock <hallo@faltrock.de>
Date: Wed, 23 Aug 2023 17:48:13 +0200
Subject: [PATCH] [BUGFIX] Fix fluid template paths to respect TypoScript order
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Only add implicit default paths (Resources/Private/X) when they
are not explicitly configured via TypoScript, as they may have
been configured as an overwrite for another base path.

Resolves: #81099
Releases: main, 12.4, 11.5
Change-Id: I0fe4cb690aa253d5d9941fcfbe0a9c9063298547
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/80835
Tested-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
---
 .../fluid/Classes/View/TemplatePaths.php      |  6 ++-
 .../Tests/Unit/View/TemplatePathsTest.php     | 48 +++++++++++++++++++
 2 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/typo3/sysext/fluid/Classes/View/TemplatePaths.php b/typo3/sysext/fluid/Classes/View/TemplatePaths.php
index fa4572790239..721b784fd80d 100644
--- a/typo3/sysext/fluid/Classes/View/TemplatePaths.php
+++ b/typo3/sysext/fluid/Classes/View/TemplatePaths.php
@@ -109,7 +109,11 @@ class TemplatePaths extends \TYPO3Fluid\Fluid\View\TemplatePaths
 
         foreach ($paths as $name => $defaultPaths) {
             if (!empty($configuredPaths[$name])) {
-                $paths[$name] = array_merge($defaultPaths, ArrayUtility::sortArrayWithIntegerKeys((array)$configuredPaths[$name]));
+                $configured = ArrayUtility::sortArrayWithIntegerKeys((array)$configuredPaths[$name]);
+                // calculate implicit default paths which have not been explicitly configured
+                $implicitDefaultPaths = array_diff($defaultPaths, $configured);
+                // prepend implicit default paths (which have not been found in configured paths), as fallbacks
+                $paths[$name] = array_merge($implicitDefaultPaths, $configured);
             }
         }
 
diff --git a/typo3/sysext/fluid/Tests/Unit/View/TemplatePathsTest.php b/typo3/sysext/fluid/Tests/Unit/View/TemplatePathsTest.php
index f6db52136214..f02d1ee5ae9a 100644
--- a/typo3/sysext/fluid/Tests/Unit/View/TemplatePathsTest.php
+++ b/typo3/sysext/fluid/Tests/Unit/View/TemplatePathsTest.php
@@ -266,4 +266,52 @@ class TemplatePathsTest extends UnitTestCase
             ],
         ], $result);
     }
+
+    /**
+     * @test
+     */
+    public function getContextSpecificViewConfigurationRespectsTypoScriptConfiguredPaths(): void
+    {
+        $configurationManager = $this->createMock(ConfigurationManagerInterface::class);
+        $configurationManager->expects(self::once())->method('getConfiguration')->willReturn([
+            'plugin.' => [
+                'tx_test.' => [
+                    'view.' => [
+                        'templateRootPaths.' => [
+                            '0' => 'base/Templates/',
+                            '10' => 'test/Templates/',
+                        ],
+                        'partialRootPaths.' => [
+                            '0' => 'base/Partials/',
+                            '10' => 'test/Partials/',
+                        ],
+                        'layoutRootPaths.' => [
+                            '0' => 'base/Layouts/',
+                            '10' => 'test/Layouts/',
+                        ],
+                    ],
+                ],
+            ],
+        ]);
+        $subject = $this->getAccessibleMock(TemplatePaths::class, ['getConfigurationManager', 'getExtensionPrivateResourcesPath', 'isBackendMode', 'isFrontendMode']);
+        $subject->expects(self::once())->method('getExtensionPrivateResourcesPath')->with('test')->willReturn('test/');
+        $subject->expects(self::once())->method('getConfigurationManager')->willReturn($configurationManager);
+        $subject->expects(self::once())->method('isBackendMode')->willReturn(false);
+        $subject->expects(self::once())->method('isFrontendMode')->willReturn(true);
+        $result = $subject->_call('getContextSpecificViewConfiguration', 'test');
+        self::assertSame([
+            'templateRootPaths' => [
+                'base/Templates/',
+                'test/Templates/',
+            ],
+            'partialRootPaths' => [
+                'base/Partials/',
+                'test/Partials/',
+            ],
+            'layoutRootPaths' => [
+                'base/Layouts/',
+                'test/Layouts/',
+            ],
+        ], $result);
+    }
 }
-- 
GitLab