[TASK] Set locale for requests earlier in Frontend process 63/60863/5
authorBenni Mack <benni@typo3.org>
Mon, 3 Jun 2019 07:15:30 +0000 (09:15 +0200)
committerAndreas Fernandez <a.fernandez@scripting-base.de>
Wed, 5 Jun 2019 04:47:29 +0000 (06:47 +0200)
Due to non-site handling, setting the locale was built on top of
TypoScript settings. This was the reason, this was encapsulated
within "$TSFE->settingLocale()".

However, as the locale is now always available once SiteLanguage
has been resolved, this can be handled very early.

On top, the functionaltiy can be extracted from TSFE completely,
and be universally used within "Locales".

The method $TSFE->settingLocale() is now deprecated.

Resolves: #88473
Releases: master
Change-Id: I28c057ecc6d6ba37153a09812a61e5827cdb7bc5
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60863
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
typo3/sysext/core/Classes/Localization/Locales.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-88473-TypoScriptFrontendController-settingLocale.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Localization/LocalesTest.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php
typo3/sysext/frontend/Classes/Middleware/SiteResolver.php
typo3/sysext/frontend/Tests/Unit/Middleware/SiteResolverTest.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
typo3/sysext/redirects/Classes/Service/RedirectService.php

index 0c8c4c9..977248b 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Localization;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Log\LogManager;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -256,4 +258,41 @@ class Locales implements \TYPO3\CMS\Core\SingletonInterface
         }
         return $selectedLanguage;
     }
+
+    /**
+     * Setting locale based on a SiteLanguage's defined locale.
+     * Used for frontend rendering, previously set within TSFE->settingLocale
+     *
+     * @param SiteLanguage $siteLanguage
+     * @return bool whether the locale was found on the system (and could be set properly) or not
+     */
+    public static function setSystemLocaleFromSiteLanguage(SiteLanguage $siteLanguage): bool
+    {
+        $locale = $siteLanguage->getLocale();
+        // No locale was given, so return false;
+        if (!$locale) {
+            return false;
+        }
+        $availableLocales = GeneralUtility::trimExplode(',', $locale, true);
+        // If LC_NUMERIC is set e.g. to 'de_DE' PHP parses float values locale-aware resulting in strings with comma
+        // as decimal point which causes problems with value conversions - so we set all locale types except LC_NUMERIC
+        // @see https://bugs.php.net/bug.php?id=53711
+        $locale = setlocale(LC_COLLATE, ...$availableLocales);
+        if ($locale) {
+            // As str_* methods are locale aware and turkish has no upper case I
+            // Class autoloading and other checks depending on case changing break with turkish locale LC_CTYPE
+            // @see http://bugs.php.net/bug.php?id=35050
+            if (strpos($locale, 'tr') !== 0) {
+                setlocale(LC_CTYPE, ...$availableLocales);
+            }
+            setlocale(LC_MONETARY, ...$availableLocales);
+            setlocale(LC_TIME, ...$availableLocales);
+        } else {
+            GeneralUtility::makeInstance(LogManager::class)
+                ->getLogger(__CLASS__)
+                ->error('Locale "' . htmlspecialchars($siteLanguage->getLocale()) . '" not found.');
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-88473-TypoScriptFrontendController-settingLocale.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-88473-TypoScriptFrontendController-settingLocale.rst
new file mode 100644 (file)
index 0000000..b94c28b
--- /dev/null
@@ -0,0 +1,40 @@
+.. include:: ../../Includes.txt
+
+=================================================================
+Deprecation: #88473 - TypoScriptFrontendController->settingLocale
+=================================================================
+
+See :issue:`88473`
+
+Description
+===========
+
+Due to Site Handling, setting the locale information (:php:`setlocale`) can be handled
+much earlier without any dependencies on the global TSFE object.
+
+The functionality of the method :php:`TypoScriptFrontendController->settingLocale()` has
+been moved into :php:`Locales::setSystemLocaleFromSiteLanguage()`. The former method
+has been marked as deprecated.
+
+
+Impact
+======
+
+Calling :php:`TypoScriptFrontendController->settingLocale()` will trigger a PHP :php:`E_USER_DEPRECATED` error.
+
+
+Affected Installations
+======================
+
+Any TYPO3 installation with a third party extension booting up a custom Frontend system and
+explicitly call the method above.
+
+
+Migration
+=========
+
+Migrate the existing PHP code to :php:`Locales::setSystemLocaleFromSiteLanguage()` or ensure
+that the SiteResolver middleware for Frontend Requests is executed where the locale is now
+set automatically.
+
+.. index:: Frontend, PHP-API, FullyScanned
\ No newline at end of file
index 9733eab..4f044e5 100644 (file)
@@ -14,7 +14,9 @@ namespace TYPO3\CMS\Core\Tests\Unit\Localization;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Http\Uri;
 use TYPO3\CMS\Core\Localization\Locales;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 /**
@@ -22,6 +24,28 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
  */
 class LocalesTest extends UnitTestCase
 {
+    protected $resetSingletonInstances = true;
+
+    /**
+     * @var string
+     */
+    protected $originalLocale;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->originalLocale = setlocale(LC_COLLATE, 0);
+    }
+
+    protected function tearDown(): void
+    {
+        // Restore original locale
+        setlocale(LC_COLLATE, $this->originalLocale);
+        setlocale(LC_MONETARY, $this->originalLocale);
+        setlocale(LC_TIME, $this->originalLocale);
+        parent::tearDown();
+    }
+
     /**
      * @return array
      */
@@ -61,4 +85,45 @@ class LocalesTest extends UnitTestCase
         );
         $this->assertSame($expected, $detectedLanguage);
     }
+
+    /**
+     * @test
+     */
+    public function setSystemLocaleFromSiteLanguageWithoutLocaleDoesNotSetLocale(): void
+    {
+        $language = new SiteLanguage(0, '', new Uri('/'), []);
+        $result = Locales::setSystemLocaleFromSiteLanguage($language);
+        static::assertFalse($result);
+        $currentLocale = setlocale(LC_COLLATE, 0);
+        // Check that the locale was not overridden
+        static::assertEquals($this->originalLocale, $currentLocale);
+    }
+
+    /**
+     * @test
+     */
+    public function setSystemLocaleFromSiteLanguageWithProperLocaleSetsLocale(): void
+    {
+        $locale = 'en_US';
+        $language = new SiteLanguage(0, $locale, new Uri('/'), []);
+        $result = Locales::setSystemLocaleFromSiteLanguage($language);
+        static::assertTrue($result);
+        $currentLocale = setlocale(LC_COLLATE, 0);
+        // Check that the locale was overridden
+        static::assertEquals($locale, $currentLocale);
+    }
+
+    /**
+     * @test
+     */
+    public function setSystemLocaleFromSiteLanguageWithInvalidLocaleDoesNotSetLocale(): void
+    {
+        $locale = 'af_EUR';
+        $language = new SiteLanguage(0, $locale, new Uri('/'), []);
+        $result = Locales::setSystemLocaleFromSiteLanguage($language);
+        static::assertFalse($result);
+        $currentLocale = setlocale(LC_COLLATE, 0);
+        // Check that the locale was not overridden
+        static::assertEquals($this->originalLocale, $currentLocale);
+    }
 }
index 56feba2..c815de0 100644 (file)
@@ -2079,29 +2079,14 @@ class TypoScriptFrontendController implements LoggerAwareInterface
 
     /**
      * Setting locale for frontend rendering
+     * @deprecated will be removed in TYPO3 v11.0. Use Locales::setSystemLocaleFromSiteLanguage() instead.
      */
     public function settingLocale()
     {
+        trigger_error('TSFE->settingLocale() will be removed in TYPO3 v11.0. Use Locales::setSystemLocaleFromSiteLanguage() instead, as this functionality is independent of TSFE.', E_USER_DEPRECATED);
         $siteLanguage = $this->getCurrentSiteLanguage();
-        $locale = $siteLanguage->getLocale();
-        if ($locale) {
-            $availableLocales = GeneralUtility::trimExplode(',', $locale, true);
-            // If LC_NUMERIC is set e.g. to 'de_DE' PHP parses float values locale-aware resulting in strings with comma
-            // as decimal point which causes problems with value conversions - so we set all locale types except LC_NUMERIC
-            // @see https://bugs.php.net/bug.php?id=53711
-            $locale = setlocale(LC_COLLATE, ...$availableLocales);
-            if ($locale) {
-                // As str_* methods are locale aware and turkish has no upper case I
-                // Class autoloading and other checks depending on case changing break with turkish locale LC_CTYPE
-                // @see http://bugs.php.net/bug.php?id=35050
-                if (strpos($locale, 'tr') !== 0) {
-                    setlocale(LC_CTYPE, ...$availableLocales);
-                }
-                setlocale(LC_MONETARY, ...$availableLocales);
-                setlocale(LC_TIME, ...$availableLocales);
-            } else {
-                $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($locale) . '" not found.', 3);
-            }
+        if ($siteLanguage->getLocale() && !Locales::setSystemLocaleFromSiteLanguage($siteLanguage)) {
+            $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($siteLanguage->getLocale()) . '" not found.', 3);
         }
     }
 
index 2e754e6..e0f9039 100644 (file)
@@ -71,9 +71,8 @@ class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
         $this->controller->getConfigArray();
 
         // Setting language and locale
-        $this->timeTracker->push('Setting language and locale');
+        $this->timeTracker->push('Setting language');
         $this->controller->settingLanguage();
-        $this->controller->settingLocale();
         $this->timeTracker->pull();
 
         // Convert POST data to utf-8 for internal processing if metaCharset is different
index 8423e55..dc5e5de 100644 (file)
@@ -19,8 +19,10 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Localization\Locales;
 use TYPO3\CMS\Core\Routing\SiteMatcher;
 use TYPO3\CMS\Core\Routing\SiteRouteResult;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -60,6 +62,9 @@ class SiteResolver implements MiddlewareInterface
         $request = $request->withAttribute('site', $routeResult->getSite());
         $request = $request->withAttribute('language', $routeResult->getLanguage());
         $request = $request->withAttribute('routing', $routeResult);
+        if ($routeResult->getLanguage() instanceof SiteLanguage) {
+            Locales::setSystemLocaleFromSiteLanguage($routeResult->getLanguage());
+        }
         return $handler->handle($request);
     }
 }
index 68a75c9..5d2fe66 100644 (file)
@@ -93,6 +93,8 @@ class SiteResolverTest extends UnitTestCase
     {
         // restore locale to original setting
         setlocale(LC_COLLATE, $this->originalLocale);
+        setlocale(LC_MONETARY, $this->originalLocale);
+        setlocale(LC_TIME, $this->originalLocale);
         parent::tearDown();
     }
 
index 7fe89fe..101b3ce 100644 (file)
@@ -4154,4 +4154,11 @@ return [
             'Deprecation-88406-SetCacheHashnoCacheHashOptionsInViewHelpersAndUriBuilder.rst'
         ],
     ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->settingLocale' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-88473-TypoScriptFrontendController-settingLocale.rst'
+        ],
+    ],
 ];
index 5a7afc0..47abfde 100644 (file)
@@ -282,7 +282,6 @@ class RedirectService implements LoggerAwareInterface
         $controller->calculateLinkVars($queryParams);
         $controller->getConfigArray();
         $controller->settingLanguage();
-        $controller->settingLocale();
         $controller->newCObj();
         return $controller;
     }