TYPO3 CMS  TYPO3_8-7
TerService.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Lang\Service;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
26 use TYPO3\CMS\Lang\Exception\Language as LanguageException;
28 
33 class TerService extends TerUtility implements SingletonInterface, LoggerAwareInterface
34 {
35  use LoggerAwareTrait;
36 
44  public function fetchTranslationStatus($extensionKey, $mirrorUrl)
45  {
46  $result = false;
47  $extPath = strtolower($extensionKey);
48  $mirrorUrl .= $extPath[0] . '/' . $extPath[1] . '/' . $extPath . '-l10n/' . $extPath . '-l10n.xml';
49  $remote = GeneralUtility::getUrl($mirrorUrl);
50  if ($remote !== false) {
51  $parsed = $this->parseL10nXML($remote);
52  $result = $parsed['languagePackIndex'];
53  }
54  return $result;
55  }
56 
64  protected function parseL10nXML($string)
65  {
66  // Create parser:
67  $parser = xml_parser_create();
68  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
69  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
70  $values = [];
71  $index = [];
72  xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
73  xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
74  // Parse content
75  xml_parse_into_struct($parser, $string, $values, $index);
76  libxml_disable_entity_loader($previousValueOfEntityLoader);
77  // If error, return error message
78  if (xml_get_error_code($parser)) {
79  $line = xml_get_current_line_number($parser);
80  $error = xml_error_string(xml_get_error_code($parser));
81  xml_parser_free($parser);
82  throw new XmlParserException('Error in XML parser while decoding l10n XML file. Line ' . $line . ': ' . $error, 1345736517);
83  }
84  // Init vars
85  $stack = [[]];
86  $stacktop = 0;
87  $current = [];
88  $tagName = '';
89  $documentTag = '';
90  // Traverse the parsed XML structure:
91  foreach ($values as $val) {
92  // First, process the tag-name (which is used in both cases, whether "complete" or "close")
93  $tagName = (string)($val['tag'] === 'languagepack' && $val['type'] === 'open') ? $val['attributes']['language'] : $val['tag'];
94  if (!$documentTag) {
95  $documentTag = $tagName;
96  }
97  // Setting tag-values, manage stack:
98  switch ($val['type']) {
99  // If open tag it means there is an array stored in sub-elements.
100  // Therefore increase the stackpointer and reset the accumulation array
101  case 'open':
102  // Setting blank place holder
103  $current[$tagName] = [];
104  $stack[$stacktop++] = $current;
105  $current = [];
106  break;
107  // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
108  case 'close':
109  $oldCurrent = $current;
110  $current = $stack[--$stacktop];
111  // Going to the end of array to get placeholder key, key($current), and fill in array next
112  end($current);
113  $current[key($current)] = $oldCurrent;
114  unset($oldCurrent);
115  break;
116  // If "complete", then it's a value. Omits the tag if the value is empty.
117  case 'complete':
118  $trimmedValue = trim((string)$val['value']);
119  if ($trimmedValue !== '') {
120  $current[$tagName] = $trimmedValue;
121  }
122  break;
123  }
124  }
125  $result = $current[$tagName];
126 
127  return $result;
128  }
129 
138  public function updateTranslation($extensionKey, $language, $mirrorUrl)
139  {
140  $result = false;
141  try {
142  $l10n = $this->fetchTranslation($extensionKey, $language, $mirrorUrl);
143  if (is_array($l10n)) {
144  $absolutePathToZipFile = GeneralUtility::getFileAbsFileName('typo3temp/var/transient/' . $extensionKey . '-l10n-' . $language . '.zip');
145  $relativeLanguagePath = 'l10n' . '/' . $language . '/';
146  $absoluteLanguagePath = GeneralUtility::getFileAbsFileName(PATH_typo3conf . $relativeLanguagePath);
147  $absoluteExtensionLanguagePath = GeneralUtility::getFileAbsFileName(PATH_typo3conf . $relativeLanguagePath . $extensionKey . '/');
148  if (empty($absolutePathToZipFile) || empty($absoluteLanguagePath) || empty($absoluteExtensionLanguagePath)) {
149  throw new LanguageException('Given path is invalid.', 1352565336);
150  }
151  if (!is_dir($absoluteLanguagePath)) {
152  GeneralUtility::mkdir_deep(PATH_typo3conf, $relativeLanguagePath);
153  }
154  GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $l10n[0]);
155  if (is_dir($absoluteExtensionLanguagePath)) {
156  GeneralUtility::rmdir($absoluteExtensionLanguagePath, true);
157  }
158  if ($this->unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath)) {
159  $result = true;
160  }
161  }
162  } catch (Exception $exception) {
163  // @todo logging
164  }
165  return $result;
166  }
167 
177  protected function fetchTranslation($extensionKey, $language, $mirrorUrl)
178  {
179  $extensionPath = strtolower($extensionKey);
180  // Typical non sysext path, Hungarian:
181  // http://my.mirror/ter/a/n/anextension-l10n/anextension-l10n-hu.zip
182  $packageUrl = $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath .
183  '-l10n/' . $extensionPath . '-l10n-' . $language . '.zip';
184 
185  try {
186  $path = ExtensionManagementUtility::extPath($extensionPath);
187  if (strpos($path, '/sysext/') !== false) {
188  // This is a system extension and the package URL should be adapted
189  list($majorVersion, ) = explode('.', TYPO3_branch);
190  // Typical non sysext path, mind the additional version part, French
191  // http://my.mirror/ter/b/a/backend-l10n/backend-l10n-fr.v7.zip
192  $packageUrl = $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath .
193  '-l10n/' . $extensionPath . '-l10n-' . $language . '.v' . $majorVersion . '.zip';
194  }
195  } catch (\BadFunctionCallException $e) {
196  // Nothing to do
197  }
198 
199  $l10nResponse = GeneralUtility::getUrl($mirrorUrl . $packageUrl);
200  if ($l10nResponse === false) {
201  throw new XmlParserException('Error: Translation could not be fetched.', 1345736785);
202  }
203  return [$l10nResponse];
204  }
205 
214  protected function unzipTranslationFile($file, $path)
215  {
216  if (!is_dir($path)) {
218  }
219 
220  try {
221  $zipService = GeneralUtility::makeInstance(ZipService::class);
222  if ($zipService->verify($file)) {
223  $zipService->extract($file, $path);
224  }
225  } catch (ExtractException $e) {
226  $this->logger->error('Extracting the language archive failed', ['exception' => $e]);
227  throw new LanguageException('Extracting the language archive failed: ' . $e->getMessage(), 1576247863, $e);
228  }
229  GeneralUtility::fixPermissions($path, true);
230 
231  return true;
232  }
233 }
static mkdir_deep($directory, $deepDirectory='')
static writeFileToTypo3tempDir($filepath, $content)
static getFileAbsFileName($filename, $_=null, $_2=null)
fetchTranslation($extensionKey, $language, $mirrorUrl)
Definition: TerService.php:177
static makeInstance($className,... $constructorArguments)
static fixPermissions($path, $recursive=false)
static rmdir($path, $removeNonEmpty=false)
updateTranslation($extensionKey, $language, $mirrorUrl)
Definition: TerService.php:138
fetchTranslationStatus($extensionKey, $mirrorUrl)
Definition: TerService.php:44