‪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;
36 
44 {
48  protected ‪$locales;
49 
53  protected ‪$registry;
54 
58  protected ‪$eventDispatcher;
59 
63  protected ‪$requestFactory;
64 
68  protected ‪$logger;
69 
70  private const ‪LANGUAGE_PACK_URL = 'https://localize.typo3.org/xliff/';
71 
72  public function ‪__construct(
73  EventDispatcherInterface ‪$eventDispatcher,
75  LoggerInterface ‪$logger
76  ) {
77  $this->eventDispatcher = ‪$eventDispatcher;
78  $this->locales = GeneralUtility::makeInstance(Locales::class);
79  $this->registry = GeneralUtility::makeInstance(Registry::class);
80  $this->requestFactory = ‪$requestFactory;
81  $this->logger = ‪$logger;
82  }
83 
89  public function ‪getAvailableLanguages(): array
90  {
91  return $this->locales->getLanguages();
92  }
93 
97  public function ‪getActiveLanguages(): array
98  {
99  $availableLanguages = ‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? [];
100  return array_filter($availableLanguages);
101  }
102 
106  public function ‪getLanguageDetails(): array
107  {
108  $availableLanguages = $this->‪getAvailableLanguages();
109  $activeLanguages = $this->‪getActiveLanguages();
110  ‪$languages = [];
111  foreach ($availableLanguages as $iso => $name) {
112  if ($iso === 'default') {
113  continue;
114  }
115  $lastUpdate = $this->registry->get('languagePacks', $iso);
116  ‪$languages[] = [
117  'iso' => $iso,
118  'name' => $name,
119  'active' => in_array($iso, $activeLanguages, true),
120  'lastUpdate' => $this->‪getFormattedDate($lastUpdate),
121  'dependencies' => $this->locales->getLocaleDependencies($iso),
122  ];
123  }
124  usort(‪$languages, static function ($a, $b) {
125  // Sort languages by name
126  if ($a['name'] === $b['name']) {
127  return 0;
128  }
129  return $a['name'] < $b['name'] ? -1 : 1;
130  });
131  return ‪$languages;
132  }
133 
137  public function ‪getExtensionLanguagePackDetails(): array
138  {
139  $activeLanguages = $this->‪getActiveLanguages();
140  $packageManager = GeneralUtility::makeInstance(PackageManager::class);
141  $activePackages = $packageManager->getActivePackages();
142  $extensions = [];
143  foreach ($activePackages as $package) {
144  $path = $package->getPackagePath();
145  ‪$finder = new Finder();
146  try {
147  $files = ‪$finder->files()->in($path . 'Resources/Private/Language/')->name('*.xlf');
148  if ($files->count() === 0) {
149  // This extension has no .xlf files
150  continue;
151  }
152  } catch (\InvalidArgumentException $e) {
153  // Dir does not exist
154  continue;
155  }
156  $key = $package->getPackageKey();
157  $title = $package->getValueFromComposerManifest('description') ?? '';
158  if (is_file($path . 'ext_emconf.php')) {
159  $_EXTKEY = $key;
160  ‪$EM_CONF = [];
161  include $path . 'ext_emconf.php';
162  $title = ‪$EM_CONF[$key]['title'] ?? $title;
163 
164  $state = ‪$EM_CONF[$key]['state'] ?? '';
165  if ($state === 'excludeFromUpdates') {
166  continue;
167  }
168  }
169  $extension = [
170  'key' => $key,
171  'title' => $title,
172  'type' => $package->getPackageMetaData()->getPackageType(),
173  ];
174  if (!empty(‪ExtensionManagementUtility::getExtensionIcon($path, false))) {
176  }
177  $extension['packs'] = [];
178  foreach ($activeLanguages as $iso) {
179  $isLanguagePackDownloaded = is_dir(‪Environment::getLabelsPath() . '/' . $iso . '/' . $key . '/');
180  $lastUpdate = $this->registry->get('languagePacks', $iso . '-' . $key);
181  $extension['packs'][$iso] = [
182  'iso' => $iso,
183  'exists' => $isLanguagePackDownloaded,
184  'lastUpdate' => $this->‪getFormattedDate($lastUpdate),
185  ];
186  }
187  $extensions[$key] = $extension;
188  }
189  ksort($extensions);
190  $event = $this->eventDispatcher->dispatch(new ModifyLanguagePacksEvent($extensions));
191  return $event->getExtensions();
192  }
193 
202  public function ‪languagePackDownload(string $key, string $iso): string
203  {
204  // Sanitize extension and iso code
205  $availableLanguages = $this->‪getAvailableLanguages();
206  $activeLanguages = $this->‪getActiveLanguages();
207  if (!array_key_exists($iso, $availableLanguages) || !in_array($iso, $activeLanguages, true)) {
208  throw new \RuntimeException('Language iso code ' . (string)$iso . ' not available or active', 1520117054);
209  }
210  $packageManager = GeneralUtility::makeInstance(PackageManager::class);
211  $package = $packageManager->getActivePackages()[$key] ?? null;
212  if (!$package) {
213  throw new \RuntimeException('Extension ' . (string)$key . ' not loaded', 1520117245);
214  }
215 
216  // Kinda hacky, but we need this as the install tool requests every language for every extension
217  $extensions = $this->‪getExtensionLanguagePackDetails();
218  if (!isset($extensions[$key]['packs'][$iso])) {
219  return 'skipped';
220  }
221 
222  $languagePackBaseUrl = ‪self::LANGUAGE_PACK_URL;
223 
224  // Allow to modify the base url on the fly
225  $event = $this->eventDispatcher->dispatch(new ModifyLanguagePackRemoteBaseUrlEvent(new Uri($languagePackBaseUrl), $key));
226  $languagePackBaseUrl = $event->getBaseUrl();
227  $majorVersion = GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion();
228  if ($package->getPackageMetaData()->isFrameworkType()) {
229  // This is a system extension and the package URL should be adapted to have different packs per core major version
230  // https://localize.typo3.org/xliff/b/a/backend-l10n/backend-l10n-fr.v9.zip
231  $packageUrl = $key[0] . '/' . $key[1] . '/' . $key . '-l10n/' . $key . '-l10n-' . $iso . '.v' . $majorVersion . '.zip';
232  } else {
233  // Typical non sysext path, Hungarian:
234  // https://localize.typo3.org/xliff/a/n/anextension-l10n/anextension-l10n-hu.zip
235  $packageUrl = $key[0] . '/' . $key[1] . '/' . $key . '-l10n/' . $key . '-l10n-' . $iso . '.zip';
236  }
237 
238  $absoluteLanguagePath = ‪Environment::getLabelsPath() . '/' . $iso . '/';
239  $absoluteExtractionPath = $absoluteLanguagePath . $key . '/';
240  $absolutePathToZipFile = ‪Environment::getVarPath() . '/transient/' . $key . '-l10n-' . $iso . '.zip';
241 
242  $packExists = is_dir($absoluteExtractionPath);
243 
244  $packResult = $packExists ? 'update' : 'new';
245 
246  $operationResult = false;
247 
248  $response = $this->requestFactory->request($languagePackBaseUrl . $packageUrl, 'GET', ['http_errors' => false]);
249  if ($response->getStatusCode() === 200) {
250  $languagePackContent = $response->getBody()->getContents();
251  if (!empty($languagePackContent)) {
252  $operationResult = true;
253  if ($packExists) {
254  $operationResult = ‪GeneralUtility::rmdir($absoluteExtractionPath, true);
255  }
256  if ($operationResult) {
258  $operationResult = ‪GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $languagePackContent) === null;
259  }
260  $this->‪unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath);
261  if ($operationResult) {
262  $operationResult = unlink($absolutePathToZipFile);
263  }
264  }
265  } else {
266  $operationResult = false;
267 
268  $this->logger->warning('Requesting {request} was not successful, got status code {status} ({reason})', [
269  'request' => $languagePackBaseUrl . $packageUrl,
270  'status' => $response->getStatusCode(),
271  'reason' => $response->getReasonPhrase(),
272  ]);
273  }
274  if (!$operationResult) {
275  $packResult = 'failed';
276  $this->registry->set('languagePacks', $iso . '-' . $key, time());
277  }
278  return $packResult;
279  }
280 
287  public function ‪setLastUpdatedIsoCode(array $isos)
288  {
289  $activeLanguages = ‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? [];
290  foreach ($isos as $iso) {
291  if (!in_array($iso, $activeLanguages, true)) {
292  throw new \RuntimeException('Language iso code ' . (string)$iso . ' not available or active', 1520176318);
293  }
294  $this->registry->set('languagePacks', $iso, time());
295  }
296  }
297 
304  protected function ‪getFormattedDate($timestamp)
305  {
306  if (is_int($timestamp)) {
307  $date = new \DateTime('@' . $timestamp);
308  $format = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
309  $timestamp = $date->format($format);
310  }
311  return $timestamp;
312  }
313 
320  protected function ‪unzipTranslationFile(string $file, string $path)
321  {
322  if (!is_dir($path)) {
324  }
325 
326  $zipService = GeneralUtility::makeInstance(ZipService::class);
327  if ($zipService->verify($file)) {
328  $zipService->extract($file, $path);
329  }
331  }
332 }
‪TYPO3\CMS\Install\Service\LanguagePackService\getExtensionLanguagePackDetails
‪getExtensionLanguagePackDetails()
Definition: LanguagePackService.php:132
‪TYPO3\CMS\Install\Service\LanguagePackService\getFormattedDate
‪string null getFormattedDate($timestamp)
Definition: LanguagePackService.php:299
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\getExtensionIcon
‪static getExtensionIcon(string $extensionPath, bool $returnFullPath=false)
Definition: ExtensionManagementUtility.php:1144
‪TYPO3\CMS\Install\Service\LanguagePackService\getActiveLanguages
‪getActiveLanguages()
Definition: LanguagePackService.php:92
‪$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\Core\Core\Environment\getLabelsPath
‪static getLabelsPath()
Definition: Environment.php:233
‪TYPO3\CMS\Core\Localization\Locales
Definition: Locales.php:33
‪TYPO3\CMS\Install\Service\LanguagePackService\languagePackDownload
‪string languagePackDownload(string $key, string $iso)
Definition: LanguagePackService.php:197
‪TYPO3\CMS\Install\Service\LanguagePackService\LANGUAGE_PACK_URL
‪const LANGUAGE_PACK_URL
Definition: LanguagePackService.php:65
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static getVarPath()
Definition: Environment.php:197
‪TYPO3\CMS\Core\Http\Uri
Definition: Uri.php:30
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:40
‪TYPO3\CMS\Install\Service\LanguagePackService\setLastUpdatedIsoCode
‪setLastUpdatedIsoCode(array $isos)
Definition: LanguagePackService.php:282
‪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:315
‪TYPO3\CMS\Install\Service\LanguagePackService\getLanguageDetails
‪getLanguageDetails()
Definition: LanguagePackService.php:101
‪TYPO3\CMS\Install\Service\LanguagePackService\__construct
‪__construct(EventDispatcherInterface $eventDispatcher, RequestFactory $requestFactory, LoggerInterface $logger)
Definition: LanguagePackService.php:67
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixPermissions
‪static mixed fixPermissions($path, $recursive=false)
Definition: GeneralUtility.php:1479
‪TYPO3\CMS\Core\Utility\GeneralUtility\mkdir_deep
‪static mkdir_deep($directory)
Definition: GeneralUtility.php:1638
‪TYPO3\CMS\Install\Service\LanguagePackService\$locales
‪Locales $locales
Definition: LanguagePackService.php:47
‪TYPO3\CMS\Install\Service\LanguagePackService\$logger
‪LoggerInterface $logger
Definition: LanguagePackService.php:63
‪TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent
Definition: ModifyLanguagePacksEvent.php:24
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:30
‪TYPO3\CMS\Install\Service\LanguagePackService
Definition: LanguagePackService.php:44
‪TYPO3\CMS\Core\Service\Archive\ZipService
Definition: ZipService.php:29
‪TYPO3\CMS\Install\Service\LanguagePackService\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: LanguagePackService.php:55
‪$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\Core\Utility\GeneralUtility\rmdir
‪static bool rmdir($path, $removeNonEmpty=false)
Definition: GeneralUtility.php:1691
‪TYPO3\CMS\Install\Service\LanguagePackService\getAvailableLanguages
‪array getAvailableLanguages()
Definition: LanguagePackService.php:84
‪TYPO3\CMS\Install\Service\LanguagePackService\$registry
‪Registry $registry
Definition: LanguagePackService.php:51
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Install\Service
Definition: ClearCacheService.php:16
‪TYPO3\CMS\Install\Service\LanguagePackService\$requestFactory
‪RequestFactory $requestFactory
Definition: LanguagePackService.php:59
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFileToTypo3tempDir
‪static string null writeFileToTypo3tempDir($filepath, $content)
Definition: GeneralUtility.php:1544