2 namespace TYPO3\CMS\Lang\Service
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Core\Exception
;
18 use TYPO3\CMS\Core\SingletonInterface
;
19 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility
;
20 use TYPO3\CMS\Core\Utility\GeneralUtility
;
21 use TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility
;
22 use TYPO3\CMS\Lang\Exception\Language
as LanguageException
;
23 use TYPO3\CMS\Lang\Exception\XmlParser
as XmlParserException
;
26 * Extends of extensionmanager ter connection to enrich with translation
29 class TerService
extends TerUtility
implements SingletonInterface
32 * Fetches extensions translation status
34 * @param string $extensionKey Extension Key
35 * @param string $mirrorUrl URL of mirror to use
38 public function fetchTranslationStatus($extensionKey, $mirrorUrl)
41 $extPath = GeneralUtility
::strtolower($extensionKey);
42 $mirrorUrl .= $extPath[0] . '/' . $extPath[1] . '/' . $extPath . '-l10n/' . $extPath . '-l10n.xml';
43 $remote = GeneralUtility
::getURL($mirrorUrl, 0, array(TYPO3_user_agent
));
44 if ($remote !== false) {
45 $parsed = $this->parseL10nXML($remote);
46 $result = $parsed['languagePackIndex'];
52 * Parses content of *-l10n.xml into a suitable array
54 * @param string $string: XML data to parse
55 * @throws \TYPO3\CMS\Lang\Exception\XmlParser
56 * @return array Array representation of XML data
58 protected function parseL10nXML($string)
61 $parser = xml_parser_create();
64 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING
, 0);
65 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE
, 0);
67 xml_parse_into_struct($parser, $string, $values, $index);
68 // If error, return error message
69 if (xml_get_error_code($parser)) {
70 $line = xml_get_current_line_number($parser);
71 $error = xml_error_string(xml_get_error_code($parser));
72 xml_parser_free($parser);
73 throw new XmlParserException('Error in XML parser while decoding l10n XML file. Line ' . $line . ': ' . $error, 1345736517);
76 $stack = array(array());
81 // Traverse the parsed XML structure:
82 foreach ($values as $val) {
83 // First, process the tag-name (which is used in both cases, whether "complete" or "close")
84 $tagName = (string)($val['tag'] == 'languagepack' && $val['type'] == 'open') ?
$val['attributes']['language'] : $val['tag'];
86 $documentTag = $tagName;
88 // Setting tag-values, manage stack:
89 switch ($val['type']) {
90 // If open tag it means there is an array stored in sub-elements.
91 // Therefore increase the stackpointer and reset the accumulation array
93 // Setting blank place holder
94 $current[$tagName] = array();
95 $stack[$stacktop++
] = $current;
98 // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
100 $oldCurrent = $current;
101 $current = $stack[--$stacktop];
102 // Going to the end of array to get placeholder key, key($current), and fill in array next
104 $current[key($current)] = $oldCurrent;
107 // If "complete", then it's a value. Omits the tag if the value is empty.
109 $trimmedValue = trim((string)$val['value']);
110 if ($trimmedValue !== '') {
111 $current[$tagName] = $trimmedValue;
116 $result = $current[$tagName];
122 * Install translations for all selected languages for an extension
124 * @param string $extensionKey The extension key to install the translations for
125 * @param string $language Language code of translation to fetch
126 * @param string $mirrorUrl Mirror URL to fetch data from
127 * @return bool TRUE on success, error string on failure
129 public function updateTranslation($extensionKey, $language, $mirrorUrl)
133 $l10n = $this->fetchTranslation($extensionKey, $language, $mirrorUrl);
134 if (is_array($l10n)) {
135 $absolutePathToZipFile = GeneralUtility
::getFileAbsFileName('typo3temp/Language/' . $extensionKey . '-l10n-' . $language . '.zip');
136 $relativeLanguagePath = 'l10n' . '/' . $language . '/';
137 $absoluteLanguagePath = GeneralUtility
::getFileAbsFileName(PATH_typo3conf
. $relativeLanguagePath);
138 $absoluteExtensionLanguagePath = GeneralUtility
::getFileAbsFileName(PATH_typo3conf
. $relativeLanguagePath . $extensionKey . '/');
139 if (empty($absolutePathToZipFile) ||
empty($absoluteLanguagePath) ||
empty($absoluteExtensionLanguagePath)) {
140 throw new LanguageException('Given path is invalid.', 1352565336);
142 if (!is_dir($absoluteLanguagePath)) {
143 GeneralUtility
::mkdir_deep(PATH_typo3conf
, $relativeLanguagePath);
145 GeneralUtility
::writeFileToTypo3tempDir($absolutePathToZipFile, $l10n[0]);
146 if (is_dir($absoluteExtensionLanguagePath)) {
147 GeneralUtility
::rmdir($absoluteExtensionLanguagePath, true);
149 if ($this->unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath)) {
153 } catch (Exception
$exception) {
160 * Fetches an extensions l10n file from the given mirror
162 * @param string $extensionKey Extension Key
163 * @param string $language The language code of the translation to fetch
164 * @param string $mirrorUrl URL of mirror to use
165 * @throws \TYPO3\CMS\Lang\Exception\XmlParser
166 * @return array Array containing l10n data
168 protected function fetchTranslation($extensionKey, $language, $mirrorUrl)
170 $extensionPath = GeneralUtility
::strtolower($extensionKey);
171 // Typical non sysext path, Hungarian:
172 // http://my.mirror/ter/a/n/anextension-l10n/anextension-l10n-hu.zip
173 $packageUrl = $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath .
174 '-l10n/' . $extensionPath . '-l10n-' . $language . '.zip';
177 $path = ExtensionManagementUtility
::extPath($extensionPath);
178 if (strpos($path, '/sysext/') !== false) {
179 // This is a system extension and the package URL should be adapted
180 list($majorVersion, ) = explode('.', TYPO3_branch
);
181 // Typical non sysext path, mind the additional version part, French
182 // http://my.mirror/ter/b/a/backend-l10n/backend-l10n-fr.v7.zip
183 $packageUrl = $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath .
184 '-l10n/' . $extensionPath . '-l10n-' . $language . '.v' . $majorVersion . '.zip';
186 } catch (\BadFunctionCallException
$e) {
190 $l10nResponse = GeneralUtility
::getURL($mirrorUrl . $packageUrl, 0, array(TYPO3_user_agent
));
191 if ($l10nResponse === false) {
192 throw new XmlParserException('Error: Translation could not be fetched.', 1345736785);
194 return array($l10nResponse);
199 * Unzip an language zip file
201 * @param string $file path to zip file
202 * @param string $path path to extract to
203 * @throws \TYPO3\CMS\Lang\Exception\Language
206 protected function unzipTranslationFile($file, $path)
208 $zip = zip_open($file);
209 if (is_resource($zip)) {
211 if (!is_dir($path)) {
212 GeneralUtility
::mkdir_deep($path);
214 while (($zipEntry = zip_read($zip)) !== false) {
215 $zipEntryName = zip_entry_name($zipEntry);
216 if (strpos($zipEntryName, '/') !== false) {
217 $zipEntryPathSegments = explode('/', $zipEntryName);
218 $fileName = array_pop($zipEntryPathSegments);
219 // It is a folder, because the last segment is empty, let's create it
220 if (empty($fileName)) {
221 GeneralUtility
::mkdir_deep($path, implode('/', $zipEntryPathSegments));
223 $absoluteTargetPath = GeneralUtility
::getFileAbsFileName($path . implode('/', $zipEntryPathSegments) . '/' . $fileName);
224 if (trim($absoluteTargetPath) !== '') {
225 $return = GeneralUtility
::writeFile(
226 $absoluteTargetPath, zip_entry_read($zipEntry, zip_entry_filesize($zipEntry))
228 if ($return === false) {
229 throw new LanguageException('Could not write file ' . $zipEntryName, 1345304560);
232 throw new LanguageException('Could not write file ' . $zipEntryName, 1352566904);
236 throw new LanguageException('Extension directory missing in zip file!', 1352566905);
240 throw new LanguageException('Unable to open zip file ' . $file, 1345304561);