[TASK] Instantiate $GLOBALS[LANG] via factory 26/62826/5
authorBenni Mack <benni@typo3.org>
Tue, 7 Jan 2020 08:25:12 +0000 (09:25 +0100)
committerSusanne Moog <look@susi.dev>
Tue, 14 Jan 2020 11:43:09 +0000 (12:43 +0100)
LanguageService a.k.a. $GLOBALS[LANG] is used in various places,
not just for backend-labels anymore. The "init()" method (which stems
from the fact that PHP did not have constructors !!! back then), should
be covered - which is now done via factory methods.

The goal of the factory methods are to explicitly define the dependencies
where LanguageService is used, instead of calling "init()" in the emitters
code.

This change is a pre-cursor for TYPO3 to
- avoid accessing Bootstrap API for initializing $GLOBALS[LANG]
- streamline the various places where this API is instantiated
- have a first step to switch to "real" locales at _some_ point
- and eventually get rid of accessing LanguageService via globals.

In addition the PHP class comments are streamlined to reflect
the purpose of this class.

The next step is to adapt the testing framework to easily use this
API so our tests will not have to worry about $GLOBALS[LANG]
anymore.

My 2c: We should actually rename "LanguageService" to "LabelService"
or "LabelProvider" at some point.

Resolves: #90062
Releases: master
Change-Id: Ib1b6a8aacb2c2aecc3ab2931db04002dd02f0a99
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62826
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Jörg Bösche <typo3@joergboesche.de>
Tested-by: Sascha Rademacher <sascha.rademacher+typo3@gmail.com>
Tested-by: Henning Liebe <h.liebe@neusta.de>
Tested-by: Susanne Moog <look@susi.dev>
Reviewed-by: Jörg Bösche <typo3@joergboesche.de>
Reviewed-by: Sascha Rademacher <sascha.rademacher+typo3@gmail.com>
Reviewed-by: Henning Liebe <h.liebe@neusta.de>
Reviewed-by: Susanne Moog <look@susi.dev>
12 files changed:
typo3/sysext/backend/Classes/Middleware/BackendUserAuthenticator.php
typo3/sysext/core/Classes/Console/CommandApplication.php
typo3/sysext/core/Classes/Console/CommandRequestHandler.php
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Classes/Information/Typo3Information.php
typo3/sysext/core/Classes/Localization/LanguageService.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php
typo3/sysext/indexed_search/Classes/Hook/CrawlerHook.php
typo3/sysext/install/Classes/Controller/SettingsController.php
typo3/sysext/rte_ckeditor/Classes/Controller/BrowseLinksController.php

index 7541094..352802a 100644 (file)
@@ -24,6 +24,7 @@ use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -73,7 +74,7 @@ class BackendUserAuthenticator implements MiddlewareInterface
         Bootstrap::initializeBackendUser();
         // @todo: once this logic is in this method, the redirect URL should be handled as response here
         Bootstrap::initializeBackendAuthentication($this->isLoggedInBackendUserRequired($pathToRoute));
-        Bootstrap::initializeLanguageObject();
+        $GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
         // Register the backend user as aspect
         $this->setBackendUserAspect($GLOBALS['BE_USER']);
 
index 4610d88..c31654b 100644 (file)
@@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Core\ApplicationInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Information\Typo3Version;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -75,7 +76,7 @@ class CommandApplication implements ApplicationInterface
         Bootstrap::loadExtTables();
         // create the BE_USER object (not logged in yet)
         Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
-        Bootstrap::initializeLanguageObject();
+        $GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
         // Make sure output is not buffered, so command-line output and interaction can take place
         ob_clean();
 
index 980151b..b866523 100644 (file)
@@ -24,6 +24,7 @@ use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Information\Typo3Version;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -64,7 +65,7 @@ class CommandRequestHandler implements RequestHandlerInterface
         Bootstrap::loadExtTables();
         // create the BE_USER object (not logged in yet)
         Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
-        Bootstrap::initializeLanguageObject();
+        $GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
         // Make sure output is not buffered, so command-line output and interaction can take place
         ob_clean();
 
index efb6815..db36a23 100644 (file)
@@ -32,6 +32,7 @@ use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
 use TYPO3\CMS\Core\Imaging\IconRegistry;
 use TYPO3\CMS\Core\Information\Typo3Version;
 use TYPO3\CMS\Core\IO\PharStreamWrapperInterceptor;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\Package\FailsafePackageManager;
 use TYPO3\CMS\Core\Package\PackageManager;
@@ -618,7 +619,6 @@ class Bootstrap
     public static function initializeLanguageObject()
     {
         /** @var $GLOBALS['LANG'] \TYPO3\CMS\Core\Localization\LanguageService */
-        $GLOBALS['LANG'] = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
-        $GLOBALS['LANG']->init($GLOBALS['BE_USER']->uc['lang']);
+        $GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
     }
 }
index 3d65879..01df3f8 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Core\Information;
  */
 
 use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Contains information and links, or copyright information for the project.
@@ -41,8 +40,7 @@ class Typo3Information
         } elseif ($GLOBALS['LANG'] instanceof LanguageService) {
             $this->languageService = $GLOBALS['LANG'];
         } else {
-            $this->languageService = GeneralUtility::makeInstance(LanguageService::class);
-            $this->languageService->init('default');
+            $this->languageService = LanguageService::create('default');
         }
     }
 
index bef331e..1c61f54 100644 (file)
@@ -14,16 +14,29 @@ namespace TYPO3\CMS\Core\Localization;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
- * Contains the TYPO3 Backend Language class
+ * Main API to fetch labels from XLF (label files) based on the current system
+ * language of TYPO3. It is able to resolve references to files + their pointers to the
+ * proper language. If you see something about "LLL", this class does the trick for you. It
+ * is not related for language handling of content, but rather of labels for plugins.
+ *
+ * Usually this is injected into $GLOBALS['LANG'] when in backend or CLI context, and
+ * populated by the current backend user. Don't rely on $GLOBAL['LANG'] in frontend, as it is only
+ * available in certain circumstances!
+ * In Frontend, this is also used to translate "labels", see TypoScriptFrontendController->sL()
+ * for that.
+ *
+ * As TYPO3 internally does not match the proper ISO locale standard, the "locale" here
+ * is actually a list of supported language keys, (see Locales class), whereas "english"
+ * has the language key "default".
+ *
  * For detailed information about how localization is handled,
  * please refer to the 'Inside TYPO3' document which describes this.
- * This class is normally instantiated as the global variable $GLOBALS['LANG']
- * It's only available in the backend and under certain circumstances in the frontend
- * @see \TYPO3\CMS\Backend\Template\DocumentTemplate
  */
 class LanguageService
 {
@@ -374,6 +387,7 @@ class LanguageService
      * @param string $prefix Prefix to select the correct labels
      * @param string $strip Sub-prefix to be removed from label names in the result
      * @return array Processed labels
+     * @todo: deprecate
      */
     public function getLabelsWithPrefix($prefix, $strip = '')
     {
@@ -394,4 +408,30 @@ class LanguageService
         }
         return $extraction;
     }
+
+    /**
+     * Factory method to create a language service object.
+     *
+     * @param string $locale the locale (= the TYPO3-internal locale given)
+     * @return static
+     */
+    public static function create(string $locale): self
+    {
+        $obj = GeneralUtility::makeInstance(LanguageService::class);
+        $obj->init($locale);
+        return $obj;
+    }
+
+    public static function createFromUserPreferences(?AbstractUserAuthentication $user): self
+    {
+        if ($user && ($user->uc['lang'] ?? false)) {
+            return static::create($user->uc['lang']);
+        }
+        return static::create('default');
+    }
+
+    public static function createFromSiteLanguage(SiteLanguage $language): self
+    {
+        return static::create($language->getTypo3Language());
+    }
 }
index b4197e1..bf911e4 100644 (file)
@@ -705,7 +705,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $this->initPageRenderer();
         $this->initCaches();
         // Initialize LLL behaviour
-        $this->setOutputLanguage($this->language->getTypo3Language());
+        $this->setOutputLanguage();
     }
 
     /**
@@ -3598,15 +3598,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     /**
      * Sets all internal measures what language the page should be rendered.
      * This is not for records, but rather the HTML / charset and the locallang labels
-     *
-     * @param string $language - usually set via Site Handling
      */
-    protected function setOutputLanguage($language = 'default')
+    protected function setOutputLanguage()
     {
-        $this->languageService = GeneralUtility::makeInstance(LanguageService::class);
+        $this->languageService = LanguageService::createFromSiteLanguage($this->language);
         // Always disable debugging for TSFE
         $this->languageService->debugKey = false;
-        $this->languageService->init($language);
     }
 
     /**
index 6f10a85..ea7eb53 100644 (file)
@@ -27,6 +27,7 @@ use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Http\NormalizedParams;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -70,7 +71,7 @@ class BackendUserAuthenticator implements MiddlewareInterface
         // like $GLOBALS['LANG'] for labels in the language of the BE User, the router, and ext_tables.php for all modules
         // So things like Frontend Editing and Admin Panel can use this for generating links to the TYPO3 Backend.
         if ($GLOBALS['BE_USER'] instanceof FrontendBackendUserAuthentication) {
-            Bootstrap::initializeLanguageObject();
+            $GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
             Bootstrap::initializeBackendRouter();
             Bootstrap::loadExtTables();
             $this->setBackendUserAspect($GLOBALS['BE_USER']);
index 654d6d1..123849e 100644 (file)
@@ -128,6 +128,7 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
     {
         $string = StringUtility::getUniqueId();
         $this->subject->page = [];
+        $this->subject->language = new SiteLanguage(0, 'fr', new Uri('/'), ['typo3Language' => 'fr']);
         $this->subject->_call('setOutputLanguage');
         self::assertEquals($string, $this->subject->sL($string));
     }
index 7d85cb5..dec2969 100644 (file)
@@ -15,7 +15,6 @@ namespace TYPO3\CMS\IndexedSearch\Hook;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -52,17 +51,6 @@ class CrawlerHook
     public $callBack = self::class;
 
     /**
-     * The constructor
-     */
-    public function __construct()
-    {
-        // To make sure the backend charset is available:
-        if (!is_object($GLOBALS['LANG'])) {
-            Bootstrap::initializeLanguageObject();
-        }
-    }
-
-    /**
      * Initialization of crawler hook.
      * This function is asked for each instance of the crawler and we must check if something is timed to happen and if so put entry(s) in the crawlers log to start processing.
      * In reality we select indexing configurations and evaluate if any of them needs to run.
index acedac4..04d8876 100644 (file)
@@ -20,7 +20,6 @@ use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
-use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
 use TYPO3\CMS\Core\Database\Connection;
@@ -28,6 +27,7 @@ use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
 use TYPO3\CMS\Core\Http\JsonResponse;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
 use TYPO3\CMS\Core\Package\PackageManager;
@@ -377,7 +377,7 @@ class SettingsController extends AbstractController
     public function extensionConfigurationGetContentAction(ServerRequestInterface $request): ResponseInterface
     {
         // Extension configuration needs initialized $GLOBALS['LANG']
-        Bootstrap::initializeLanguageObject();
+        $GLOBALS['LANG'] = LanguageService::create('default');
         $extensionConfigurationService = new ExtensionConfigurationService();
         $extensionsWithConfigurations = [];
         $activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
index 910a181..5d08f95 100644 (file)
@@ -37,7 +37,7 @@ class BrowseLinksController extends AbstractLinkBrowserController
     /**
      * TYPO3 language code of the content language
      *
-     * @var int
+     * @var string
      */
     protected $contentsLanguage;
 
@@ -124,7 +124,7 @@ class BrowseLinksController extends AbstractLinkBrowserController
         $this->contentsLanguage = $queryParameters['contentsLanguage'];
         $this->RTEtsConfigParams = $queryParameters['RTEtsConfigParams'] ?? null;
 
-        $this->contentLanguageService->init($this->contentsLanguage);
+        $this->contentLanguageService = LanguageService::create($this->contentsLanguage);
 
         $tcaFieldConf = ['enableRichtext' => true];
         if (!empty($queryParameters['P']['richtextConfigurationName'])) {