‪TYPO3CMS  9.5
LanguagePackService.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
18 use Symfony\Component\Finder\Finder;
23 use TYPO3\CMS\Core\Package\PackageManager;
30 
38 {
42  protected ‪$locales;
43 
47  protected ‪$registry;
48 
52  protected ‪$requestFactory;
53 
54  private const ‪OLD_LANGUAGE_PACK_URLS = [
55  'https://extensions.typo3.org/fileadmin/l10n/',
56  'https://typo3.org/fileadmin/ter/',
57  'https://beta-translation.typo3.org/fileadmin/ter/',
58  'https://localize.typo3.org/fileadmin/ter/'
59  ];
60 
61  private const ‪OLD_LANGUAGE_PACK_URL = 'https://extensions.typo3.org/fileadmin/l10n/';
62 
63  private const ‪LANGUAGE_PACK_URL = 'https://localize.typo3.org/xliff/';
64 
65  public function ‪__construct()
66  {
67  $this->locales = GeneralUtility::makeInstance(Locales::class);
68  $this->registry = GeneralUtility::makeInstance(Registry::class);
69  $this->requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
70  }
71 
77  public function ‪getAvailableLanguages(): array
78  {
79  return $this->locales->getLanguages();
80  }
81 
87  public function ‪getActiveLanguages(): array
88  {
89  $availableLanguages = ‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? [];
90  return array_filter($availableLanguages);
91  }
92 
98  public function ‪getLanguageDetails(): array
99  {
100  $availableLanguages = $this->‪getAvailableLanguages();
101  $activeLanguages = $this->‪getActiveLanguages();
102  $languages = [];
103  foreach ($availableLanguages as $iso => $name) {
104  if ($iso === 'default') {
105  continue;
106  }
107  $lastUpdate = $this->registry->get('languagePacks', $iso);
108  $languages[] = [
109  'iso' => $iso,
110  'name' => $name,
111  'active' => in_array($iso, $activeLanguages, true),
112  'lastUpdate' => $this->‪getFormattedDate($lastUpdate),
113  'dependencies' => $this->locales->getLocaleDependencies($iso),
114  ];
115  }
116  usort($languages, function ($a, $b) {
117  // Sort languages by name
118  if ($a['name'] === $b['name']) {
119  return 0;
120  }
121  return $a['name'] < $b['name'] ? -1 : 1;
122  });
123  return $languages;
124  }
125 
131  public function ‪getExtensionLanguagePackDetails(): array
132  {
133  $activeLanguages = $this->‪getActiveLanguages();
134  $packageManager = GeneralUtility::makeInstance(PackageManager::class);
135  $activePackages = $packageManager->getActivePackages();
136  $extensions = [];
137  $activeExtensions = [];
138  foreach ($activePackages as $package) {
139  $path = $package->getPackagePath();
140  ‪$finder = new Finder();
141  try {
142  $files = ‪$finder->files()->in($path . 'Resources/Private/Language/')->name('*.xlf');
143  if ($files->count() === 0) {
144  // This extension has no .xlf files
145  continue;
146  }
147  } catch (\InvalidArgumentException $e) {
148  // Dir does not exist
149  continue;
150  }
151  $key = $package->getPackageKey();
152  $activeExtensions[] = $key;
153  $title = $package->getValueFromComposerManifest('description') ?? '';
154  if (is_file($path . 'ext_emconf.php')) {
155  $_EXTKEY = $key;
156  ‪$EM_CONF = [];
157  include $path . 'ext_emconf.php';
158  $title = ‪$EM_CONF[$key]['title'] ?? $title;
159  }
160  $extension = [
161  'key' => $key,
162  'title' => $title,
163  ];
164  if (!empty(‪ExtensionManagementUtility::getExtensionIcon($path, false))) {
166  }
167  $extension['packs'] = [];
168  foreach ($activeLanguages as $iso) {
169  $isLanguagePackDownloaded = is_dir(‪Environment::getLabelsPath() . '/' . $iso . '/' . $key . '/');
170  $lastUpdate = $this->registry->get('languagePacks', $iso . '-' . $key);
171  $extension['packs'][] = [
172  'iso' => $iso,
173  'exists' => $isLanguagePackDownloaded,
174  'lastUpdate' => $this->‪getFormattedDate($lastUpdate),
175  ];
176  }
177  $extensions[] = $extension;
178  }
179  usort($extensions, function ($a, $b) {
180  // Sort extensions by key
181  if ($a['key'] === $b['key']) {
182  return 0;
183  }
184  return $a['key'] < $b['key'] ? -1 : 1;
185  });
186  return $extensions;
187  }
188 
195  public function ‪updateMirrorBaseUrl(): string
196  {
197  $repositoryUrl = 'https://repositories.typo3.org/mirrors.xml.gz';
198  $downloadBaseUrl = false;
199  try {
200  $response = $this->requestFactory->request($repositoryUrl);
201  if ($response->getStatusCode() === 200) {
202  $xmlContent = @gzdecode($response->getBody()->getContents());
203  if (!empty($xmlContent['mirror']['host']) && !empty($xmlContent['mirror']['path'])) {
204  $downloadBaseUrl = 'https://' . $xmlContent['mirror']['host'] . $xmlContent['mirror']['path'];
205  }
206  }
207  } catch (\Exception $e) {
208  // Catch generic exception, fallback handled below
209  }
210  if (empty($downloadBaseUrl)) {
211  // Hard coded fallback if something went wrong fetching & parsing mirror list
212  $downloadBaseUrl = ‪self::OLD_LANGUAGE_PACK_URL;
213  }
214  $this->registry->set('languagePacks', 'baseUrl', $downloadBaseUrl);
215  return $downloadBaseUrl;
216  }
217 
226  public function ‪languagePackDownload(string $key, string $iso): string
227  {
228  // Sanitize extension and iso code
229  $availableLanguages = $this->‪getAvailableLanguages();
230  $activeLanguages = $this->‪getActiveLanguages();
231  if (!array_key_exists($iso, $availableLanguages) || !in_array($iso, $activeLanguages, true)) {
232  throw new \RuntimeException('Language iso code ' . (string)$iso . ' not available or active', 1520117054);
233  }
234  $packageManager = GeneralUtility::makeInstance(PackageManager::class);
235  $activePackages = $packageManager->getActivePackages();
236  $packageActive = false;
237  foreach ($activePackages as $package) {
238  if ($package->getPackageKey() === $key) {
239  $packageActive = true;
240  break;
241  }
242  }
243  if (!$packageActive) {
244  throw new \RuntimeException('Extension ' . (string)$key . ' not loaded', 1520117245);
245  }
246 
247  $languagePackBaseUrl = $this->registry->get('languagePacks', 'baseUrl');
248  if (empty($languagePackBaseUrl)) {
249  throw new \RuntimeException('Language pack baseUrl not found', 1520169691);
250  }
251 
252  if (in_array($languagePackBaseUrl, self::OLD_LANGUAGE_PACK_URLS, true)
253  && GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('newTranslationServer')) {
254  $languagePackBaseUrl = ‪self::LANGUAGE_PACK_URL;
255  }
256 
257  // Allow to modify the base url on the fly by calling a signal
258  ‪$signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
259  ‪$signalSlotDispatcher->dispatch(
260  'TYPO3\\CMS\\Lang\\Service\\TranslationService',
261  'postProcessMirrorUrl',
262  [
263  'extensionKey' => $key,
264  'mirrorUrl' => &$languagePackBaseUrl,
265  ]
266  );
267 
269  $majorVersion = explode('.', TYPO3_branch)[0];
270  if (strpos($path, '/sysext/') !== false) {
271  // This is a system extension and the package URL should be adapted to have different packs per core major version
272  // https://extensions.typo3.org/fileadmin/l10n/b/a/backend-l10n/backend-l10n-fr.v9.zip
273  $packageUrl = $key[0] . '/' . $key[1] . '/' . $key . '-l10n/' . $key . '-l10n-' . $iso . '.v' . $majorVersion . '.zip';
274  } else {
275  // Typical non sysext path, Hungarian:
276  // https://extensions.typo3.org/fileadmin/l10n/a/n/anextension-l10n/anextension-l10n-hu.zip
277  $packageUrl = $key[0] . '/' . $key[1] . '/' . $key . '-l10n/' . $key . '-l10n-' . $iso . '.zip';
278  }
279 
280  $absoluteLanguagePath = ‪Environment::getLabelsPath() . '/' . $iso . '/';
281  $absoluteExtractionPath = $absoluteLanguagePath . $key . '/';
282  $absolutePathToZipFile = ‪Environment::getVarPath() . '/transient/' . $key . '-l10n-' . $iso . '.zip';
283 
284  $packExists = is_dir($absoluteExtractionPath);
285 
286  $packResult = $packExists ? 'update' : 'new';
287 
288  $operationResult = false;
289  try {
290  $response = $this->requestFactory->request($languagePackBaseUrl . $packageUrl);
291  if ($response->getStatusCode() === 200) {
292  $languagePackContent = $response->getBody()->getContents();
293  if (!empty($languagePackContent)) {
294  $operationResult = true;
295  if ($packExists) {
296  $operationResult = GeneralUtility::rmdir($absoluteExtractionPath, true);
297  }
298  if ($operationResult) {
299  GeneralUtility::mkdir_deep(‪Environment::getVarPath() . '/transient/');
300  $operationResult = GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $languagePackContent) === null;
301  }
302  $this->‪unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath);
303  if ($operationResult) {
304  $operationResult = unlink($absolutePathToZipFile);
305  }
306  }
307  }
308  } catch (\Exception $e) {
309  $operationResult = false;
310  }
311  if (!$operationResult) {
312  $packResult = 'failed';
313  $this->registry->set('languagePacks', $iso . '-' . $key, time());
314  }
315  return $packResult;
316  }
317 
324  public function ‪setLastUpdatedIsoCode(array $isos)
325  {
326  $activeLanguages = ‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? [];
327  ‪$registry = GeneralUtility::makeInstance(Registry::class);
328  foreach ($isos as $iso) {
329  if (!in_array($iso, $activeLanguages, true)) {
330  throw new \RuntimeException('Language iso code ' . (string)$iso . ' not available or active', 1520176318);
331  }
332  ‪$registry->‪set('languagePacks', $iso, time());
333  }
334  }
335 
342  protected function ‪getFormattedDate($timestamp)
343  {
344  if (is_int($timestamp)) {
345  $date = new \DateTime('@' . $timestamp);
346  $format = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
347  $timestamp = $date->format($format);
348  }
349  return $timestamp;
350  }
351 
358  protected function ‪unzipTranslationFile(string $file, string $path)
359  {
360  if (!is_dir($path)) {
361  GeneralUtility::mkdir_deep($path);
362  }
363 
364  $zipService = GeneralUtility::makeInstance(ZipService::class);
365  if ($zipService->verify($file)) {
366  $zipService->extract($file, $path);
367  }
368  GeneralUtility::fixPermissions($path, true);
369  }
370 }
‪TYPO3\CMS\Install\Service\LanguagePackService\getFormattedDate
‪string null getFormattedDate($timestamp)
Definition: LanguagePackService.php:339
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:23
‪TYPO3\CMS\Core\Core\Environment\getLabelsPath
‪static string getLabelsPath()
Definition: Environment.php:209
‪TYPO3\CMS\Core\Utility\PathUtility\stripPathSitePrefix
‪static string stripPathSitePrefix($path)
Definition: PathUtility.php:371
‪$finder
‪$finder
Definition: annotationChecker.php:102
‪TYPO3\CMS\Core\Registry
Definition: Registry.php:32
‪TYPO3\CMS\Install\Service\LanguagePackService\__construct
‪__construct()
Definition: LanguagePackService.php:62
‪TYPO3\CMS\Core\Localization\Locales
Definition: Locales.php:29
‪TYPO3\CMS\Install\Service\LanguagePackService\getActiveLanguages
‪array getActiveLanguages()
Definition: LanguagePackService.php:84
‪TYPO3\CMS\Install\Service\LanguagePackService\languagePackDownload
‪string languagePackDownload(string $key, string $iso)
Definition: LanguagePackService.php:223
‪TYPO3\CMS\Install\Service\LanguagePackService\updateMirrorBaseUrl
‪string updateMirrorBaseUrl()
Definition: LanguagePackService.php:192
‪TYPO3\CMS\Core\Registry\set
‪set($namespace, $key, $value)
Definition: Registry.php:72
‪TYPO3\CMS\Install\Service\LanguagePackService\LANGUAGE_PACK_URL
‪const LANGUAGE_PACK_URL
Definition: LanguagePackService.php:60
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:36
‪TYPO3\CMS\Install\Service\LanguagePackService\setLastUpdatedIsoCode
‪setLastUpdatedIsoCode(array $isos)
Definition: LanguagePackService.php:321
‪TYPO3\CMS\Install\Service\LanguagePackService\unzipTranslationFile
‪unzipTranslationFile(string $file, string $path)
Definition: LanguagePackService.php:355
‪TYPO3\CMS\Core\Configuration\Features
Definition: Features.php:54
‪TYPO3\CMS\Install\Service\LanguagePackService\$locales
‪Locales $locales
Definition: LanguagePackService.php:41
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:27
‪TYPO3\CMS\Install\Service\LanguagePackService
Definition: LanguagePackService.php:38
‪TYPO3\CMS\Core\Service\Archive\ZipService
Definition: ZipService.php:27
‪TYPO3\CMS\Install\Service\LanguagePackService\getExtensionLanguagePackDetails
‪array getExtensionLanguagePackDetails()
Definition: LanguagePackService.php:128
‪TYPO3\CMS\Install\Service\LanguagePackService\OLD_LANGUAGE_PACK_URLS
‪const OLD_LANGUAGE_PACK_URLS
Definition: LanguagePackService.php:51
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:39
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static string extPath($key, $script='')
Definition: ExtensionManagementUtility.php:149
‪$EM_CONF
‪$EM_CONF[$_EXTKEY]
Definition: ext_emconf.php:2
‪TYPO3\CMS\Install\Service\LanguagePackService\getAvailableLanguages
‪array getAvailableLanguages()
Definition: LanguagePackService.php:74
‪TYPO3\CMS\Install\Service\LanguagePackService\OLD_LANGUAGE_PACK_URL
‪const OLD_LANGUAGE_PACK_URL
Definition: LanguagePackService.php:58
‪TYPO3\CMS\Install\Service\LanguagePackService\$registry
‪Registry $registry
Definition: LanguagePackService.php:45
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\getExtensionIcon
‪static string getExtensionIcon($extensionPath, $returnFullPath=false)
Definition: ExtensionManagementUtility.php:1511
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Install\Service\LanguagePackService\getLanguageDetails
‪array getLanguageDetails()
Definition: LanguagePackService.php:95
‪TYPO3\CMS\Install\Service
Definition: ClearCacheService.php:2
‪TYPO3\CMS\Install\Service\LanguagePackService\$requestFactory
‪RequestFactory $requestFactory
Definition: LanguagePackService.php:49
‪$signalSlotDispatcher
‪$signalSlotDispatcher
Definition: ext_localconf.php:6
‪TYPO3\CMS\Install\Service\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static string getVarPath()
Definition: Environment.php:165
‪TYPO3\CMS\Extbase\SignalSlot\Dispatcher
Definition: Dispatcher.php:28