‪TYPO3CMS  ‪main
LanguagePackService.php
Go to the documentation of this file.
1 <?php
2 
3 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 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use Psr\Log\LoggerInterface;
22 use Symfony\Component\Finder\Finder;
28 use TYPO3\CMS\Core\Package\PackageManager;
35 
43 {
47  protected ‪$locales;
48 
52  protected ‪$registry;
53 
54  private const ‪LANGUAGE_PACK_URL = 'https://localize.typo3.org/xliff/';
55 
56  public function ‪__construct(
57  protected readonly EventDispatcherInterface $eventDispatcher,
58  protected readonly ‪RequestFactory $requestFactory,
59  protected readonly LoggerInterface $logger
60  ) {
61  $this->locales = GeneralUtility::makeInstance(Locales::class);
62  $this->registry = GeneralUtility::makeInstance(Registry::class);
63  }
64 
70  public function ‪getAvailableLanguages(): array
71  {
72  return $this->locales->getLanguages();
73  }
74 
79  public function ‪getActiveLanguages(): array
80  {
81  $availableLanguages = ‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? [];
82  return array_values(array_filter($availableLanguages));
83  }
84 
88  public function ‪getLanguageDetails(): array
89  {
90  $availableLanguages = $this->‪getAvailableLanguages();
91  $activeLanguages = $this->‪getActiveLanguages();
92  ‪$languages = [];
93  foreach ($availableLanguages as $iso => $name) {
94  if ($iso === 'default') {
95  continue;
96  }
97  $lastUpdate = $this->registry->get('languagePacks', $iso);
98  ‪$languages[] = [
99  'iso' => $iso,
100  'name' => $name,
101  'active' => in_array($iso, $activeLanguages, true),
102  'lastUpdate' => $this->‪getFormattedDate($lastUpdate),
103  'dependencies' => $this->locales->getLocaleDependencies($iso),
104  ];
105  }
106  usort(‪$languages, static function ($a, $b) {
107  // Sort languages by name
108  if ($a['name'] === $b['name']) {
109  return 0;
110  }
111  return $a['name'] < $b['name'] ? -1 : 1;
112  });
113  return ‪$languages;
114  }
115 
119  public function ‪getExtensionLanguagePackDetails(): array
120  {
121  $activeLanguages = $this->‪getActiveLanguages();
122  $packageManager = GeneralUtility::makeInstance(PackageManager::class);
123  $activePackages = $packageManager->getActivePackages();
124  $extensions = [];
125  foreach ($activePackages as $package) {
126  $path = $package->getPackagePath();
127  ‪$finder = new Finder();
128  try {
129  $files = ‪$finder->files()->in($path . 'Resources/Private/Language/')->name('*.xlf');
130  if ($files->count() === 0) {
131  // This extension has no .xlf files
132  continue;
133  }
134  } catch (\InvalidArgumentException $e) {
135  // Dir does not exist
136  continue;
137  }
138  $key = $package->getPackageKey();
139  $title = $package->getValueFromComposerManifest('description') ?? '';
140  if (is_file($path . 'ext_emconf.php')) {
141  $_EXTKEY = $key;
142  ‪$EM_CONF = [];
143  include $path . 'ext_emconf.php';
144  $title = ‪$EM_CONF[$key]['title'] ?? $title;
145 
146  $state = ‪$EM_CONF[$key]['state'] ?? '';
147  if ($state === 'excludeFromUpdates') {
148  continue;
149  }
150  }
151  $extension = [
152  'key' => $key,
153  'title' => $title,
154  'type' => $package->getPackageMetaData()->getPackageType(),
155  ];
156  if (!empty($package->getPackageIcon())) {
157  $extension['icon'] = ‪PathUtility::getAbsoluteWebPath($package->getPackagePath() . $package->getPackageIcon());
158  }
159  $extension['packs'] = [];
160  foreach ($activeLanguages as $iso) {
161  $isLanguagePackDownloaded = is_dir(‪Environment::getLabelsPath() . '/' . $iso . '/' . $key . '/');
162  $lastUpdate = $this->registry->get('languagePacks', $iso . '-' . $key);
163  $extension['packs'][$iso] = [
164  'iso' => $iso,
165  'exists' => $isLanguagePackDownloaded,
166  'lastUpdate' => $this->‪getFormattedDate($lastUpdate),
167  ];
168  }
169  $extensions[$key] = $extension;
170  }
171  ksort($extensions);
172  $event = $this->eventDispatcher->dispatch(new ModifyLanguagePacksEvent($extensions));
173  return $event->getExtensions();
174  }
175 
184  public function ‪languagePackDownload(string $key, string $iso): string
185  {
186  // Sanitize extension and iso code
187  $availableLanguages = $this->‪getAvailableLanguages();
188  $activeLanguages = $this->‪getActiveLanguages();
189  if (!array_key_exists($iso, $availableLanguages) || !in_array($iso, $activeLanguages, true)) {
190  throw new \RuntimeException('Language iso code ' . (string)$iso . ' not available or active', 1520117054);
191  }
192  $packageManager = GeneralUtility::makeInstance(PackageManager::class);
193  $package = $packageManager->getActivePackages()[$key] ?? null;
194  if (!$package) {
195  throw new \RuntimeException('Extension ' . (string)$key . ' not loaded', 1520117245);
196  }
197 
198  // Kinda hacky, but we need this as the install tool requests every language for every extension
199  $extensions = $this->‪getExtensionLanguagePackDetails();
200  if (!isset($extensions[$key]['packs'][$iso])) {
201  return 'skipped';
202  }
203 
204  $languagePackBaseUrl = ‪self::LANGUAGE_PACK_URL;
205 
206  // Allow to modify the base url on the fly
207  $event = $this->eventDispatcher->dispatch(new ModifyLanguagePackRemoteBaseUrlEvent(new Uri($languagePackBaseUrl), $key));
208  $languagePackBaseUrl = $event->getBaseUrl();
209  $majorVersion = GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion();
210  if ($package->getPackageMetaData()->isFrameworkType()) {
211  // This is a system extension and the package URL should be adapted to have different packs per core major version
212  // https://localize.typo3.org/xliff/b/a/backend-l10n/backend-l10n-fr.v9.zip
213  $packageUrl = $key[0] . '/' . $key[1] . '/' . $key . '-l10n/' . $key . '-l10n-' . $iso . '.v' . $majorVersion . '.zip';
214  } else {
215  // Typical non sysext path, Hungarian:
216  // https://localize.typo3.org/xliff/a/n/anextension-l10n/anextension-l10n-hu.zip
217  $packageUrl = $key[0] . '/' . $key[1] . '/' . $key . '-l10n/' . $key . '-l10n-' . $iso . '.zip';
218  }
219 
220  $absoluteLanguagePath = ‪Environment::getLabelsPath() . '/' . $iso . '/';
221  $absoluteExtractionPath = $absoluteLanguagePath . $key . '/';
222  $absolutePathToZipFile = ‪Environment::getVarPath() . '/transient/' . $key . '-l10n-' . $iso . '.zip';
223 
224  $packExists = is_dir($absoluteExtractionPath);
225 
226  $packResult = $packExists ? 'update' : 'new';
227 
228  $operationResult = false;
229 
230  $response = $this->requestFactory->request($languagePackBaseUrl . $packageUrl, 'GET', ['http_errors' => false]);
231  if ($response->getStatusCode() === 200) {
232  $languagePackContent = $response->getBody()->getContents();
233  if (!empty($languagePackContent)) {
234  $operationResult = true;
235  if ($packExists) {
236  $operationResult = ‪GeneralUtility::rmdir($absoluteExtractionPath, true);
237  }
238  if ($operationResult) {
240  $operationResult = ‪GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $languagePackContent) === null;
241  }
242  $this->‪unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath);
243  if ($operationResult) {
244  $operationResult = unlink($absolutePathToZipFile);
245  }
246  }
247  } else {
248  $operationResult = false;
249 
250  $this->logger->warning('Requesting {request} was not successful, got status code {status} ({reason})', [
251  'request' => $languagePackBaseUrl . $packageUrl,
252  'status' => $response->getStatusCode(),
253  'reason' => $response->getReasonPhrase(),
254  ]);
255  }
256  if (!$operationResult) {
257  $packResult = 'failed';
258  $this->registry->set('languagePacks', $iso . '-' . $key, time());
259  }
260  return $packResult;
261  }
262 
269  public function ‪setLastUpdatedIsoCode(array $isos)
270  {
271  $activeLanguages = ‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? [];
272  foreach ($isos as $iso) {
273  if (!in_array($iso, $activeLanguages, true)) {
274  throw new \RuntimeException('Language iso code ' . (string)$iso . ' not available or active', 1520176318);
275  }
276  $this->registry->set('languagePacks', $iso, time());
277  }
278  }
279 
286  protected function ‪getFormattedDate($timestamp)
287  {
288  if (is_int($timestamp)) {
289  $date = (new \DateTime())->setTimestamp($timestamp);
290  $format = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
291  $timestamp = $date->format($format);
292  }
293  return $timestamp;
294  }
295 
302  protected function ‪unzipTranslationFile(string $file, string $path)
303  {
304  if (!is_dir($path)) {
306  }
307 
308  $zipService = GeneralUtility::makeInstance(ZipService::class);
309  if ($zipService->verify($file)) {
310  $zipService->extract($file, $path);
311  }
313  }
314 }
‪TYPO3\CMS\Install\Service\LanguagePackService\getExtensionLanguagePackDetails
‪getExtensionLanguagePackDetails()
Definition: LanguagePackService.php:117
‪TYPO3\CMS\Install\Service\LanguagePackService\getFormattedDate
‪string null getFormattedDate($timestamp)
Definition: LanguagePackService.php:284
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪$EM_CONF
‪$EM_CONF[$_EXTKEY]
Definition: ext_emconf.php:3
‪TYPO3\CMS\Core\Information\Typo3Version
Definition: Typo3Version.php:21
‪$languages
‪$languages
Definition: updateIsoDatabase.php:104
‪TYPO3\CMS\Core\Registry
Definition: Registry.php:33
‪TYPO3\CMS\Install\Service\LanguagePackService\getActiveLanguages
‪list< non-empty-string > getActiveLanguages()
Definition: LanguagePackService.php:77
‪TYPO3\CMS\Core\Core\Environment\getLabelsPath
‪static getLabelsPath()
Definition: Environment.php:233
‪TYPO3\CMS\Core\Localization\Locales
Definition: Locales.php:36
‪TYPO3\CMS\Install\Service\LanguagePackService\languagePackDownload
‪string languagePackDownload(string $key, string $iso)
Definition: LanguagePackService.php:182
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep(string $directory)
Definition: GeneralUtility.php:1654
‪TYPO3\CMS\Install\Service\LanguagePackService\LANGUAGE_PACK_URL
‪const LANGUAGE_PACK_URL
Definition: LanguagePackService.php:52
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static getVarPath()
Definition: Environment.php:197
‪TYPO3\CMS\Core\Http\Uri
Definition: Uri.php:30
‪TYPO3\CMS\Install\Service\LanguagePackService\setLastUpdatedIsoCode
‪setLastUpdatedIsoCode(array $isos)
Definition: LanguagePackService.php:267
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsoluteWebPath
‪static string getAbsoluteWebPath(string $targetPath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:52
‪TYPO3\CMS\Install\Service\LanguagePackService\unzipTranslationFile
‪unzipTranslationFile(string $file, string $path)
Definition: LanguagePackService.php:300
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFileToTypo3tempDir
‪static string null writeFileToTypo3tempDir(string $filepath, string $content)
Definition: GeneralUtility.php:1561
‪TYPO3\CMS\Install\Service\LanguagePackService\getLanguageDetails
‪getLanguageDetails()
Definition: LanguagePackService.php:86
‪TYPO3\CMS\Install\Service\LanguagePackService\$locales
‪Locales $locales
Definition: LanguagePackService.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility\rmdir
‪static bool rmdir(string $path, bool $removeNonEmpty=false)
Definition: GeneralUtility.php:1702
‪TYPO3\CMS\Install\Service\LanguagePackService\__construct
‪__construct(protected readonly EventDispatcherInterface $eventDispatcher, protected readonly RequestFactory $requestFactory, protected readonly LoggerInterface $logger)
Definition: LanguagePackService.php:54
‪TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent
Definition: ModifyLanguagePacksEvent.php:24
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:30
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixPermissions
‪static bool fixPermissions(string $path, bool $recursive=false)
Definition: GeneralUtility.php:1496
‪TYPO3\CMS\Install\Service\LanguagePackService
Definition: LanguagePackService.php:43
‪TYPO3\CMS\Core\Service\Archive\ZipService
Definition: ZipService.php:29
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Install\Service\Event\ModifyLanguagePackRemoteBaseUrlEvent
Definition: ModifyLanguagePackRemoteBaseUrlEvent.php:26
‪TYPO3\CMS\Install\Service\LanguagePackService\getAvailableLanguages
‪array getAvailableLanguages()
Definition: LanguagePackService.php:68
‪TYPO3\CMS\Install\Service\LanguagePackService\$registry
‪Registry $registry
Definition: LanguagePackService.php:50
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Install\Service
Definition: ClearCacheService.php:16