8ec920a9e560b7b552e7b10dbcaa689598c99be9
[Packages/TYPO3.CMS.git] / typo3 / sysext / lang / Classes / Service / TerService.php
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
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * Extends of extensionmanager ter connection to enrich with translation
21 * related methods
22 *
23 * @author Sebastian Fischer <typo3@evoweb.de>
24 * @author Kai Vogel <k.vogel@reply.de>
25 */
26 class TerService extends \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility implements \TYPO3\CMS\Core\SingletonInterface {
27
28 /**
29 * Fetches extensions translation status
30 *
31 * @param string $extensionKey Extension Key
32 * @param string $mirrorUrl URL of mirror to use
33 * @return mixed
34 */
35 public function fetchTranslationStatus($extensionKey, $mirrorUrl) {
36 $result = FALSE;
37 $extPath = GeneralUtility::strtolower($extensionKey);
38 $mirrorUrl .= $extPath[0] . '/' . $extPath[1] . '/' . $extPath . '-l10n/' . $extPath . '-l10n.xml';
39 $remote = GeneralUtility::getURL($mirrorUrl, 0, array(TYPO3_user_agent));
40 if ($remote !== FALSE) {
41 $parsed = $this->parseL10nXML($remote);
42 $result = $parsed['languagePackIndex'];
43 }
44 return $result;
45 }
46
47 /**
48 * Parses content of *-l10n.xml into a suitable array
49 *
50 * @param string $string: XML data to parse
51 * @throws \TYPO3\CMS\Lang\Exception\XmlParser
52 * @return array Array representation of XML data
53 */
54 protected function parseL10nXML($string) {
55 // Create parser:
56 $parser = xml_parser_create();
57 $values = array();
58 $index = array();
59 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
60 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);
61 // Parse content
62 xml_parse_into_struct($parser, $string, $values, $index);
63 // If error, return error message
64 if (xml_get_error_code($parser)) {
65 $line = xml_get_current_line_number($parser);
66 $error = xml_error_string(xml_get_error_code($parser));
67 xml_parser_free($parser);
68 throw new \TYPO3\CMS\Lang\Exception\XmlParser('Error in XML parser while decoding l10n XML file. Line ' . $line . ': ' . $error, 1345736517);
69 } else {
70 // Init vars
71 $stack = array(array());
72 $stacktop = 0;
73 $current = array();
74 $tagName = '';
75 $documentTag = '';
76 // Traverse the parsed XML structure:
77 foreach ($values as $val) {
78 // First, process the tag-name (which is used in both cases, whether "complete" or "close")
79 $tagName = ($val['tag'] == 'languagepack' && $val['type'] == 'open') ? $val['attributes']['language'] : $val['tag'];
80 if (!$documentTag) {
81 $documentTag = $tagName;
82 }
83 // Setting tag-values, manage stack:
84 switch ($val['type']) {
85 // If open tag it means there is an array stored in sub-elements.
86 // Therefore increase the stackpointer and reset the accumulation array
87 case 'open':
88 // Setting blank place holder
89 $current[$tagName] = array();
90 $stack[$stacktop++] = $current;
91 $current = array();
92 break;
93 // If the tag is "close" then it is an array which is closing and we decrease the stack pointer.
94 case 'close':
95 $oldCurrent = $current;
96 $current = $stack[--$stacktop];
97 // Going to the end of array to get placeholder key, key($current), and fill in array next
98 end($current);
99 $current[key($current)] = $oldCurrent;
100 unset($oldCurrent);
101 break;
102 // If "complete", then it's a value. Omits the tag if the value is empty.
103 case 'complete':
104 $trimmedValue = trim((string)$val['value']);
105 if ($trimmedValue !== '') {
106 $current[$tagName] = $trimmedValue;
107 }
108 break;
109 }
110 }
111 $result = $current[$tagName];
112 }
113 return $result;
114 }
115
116 /**
117 * Install translations for all selected languages for an extension
118 *
119 * @param string $extensionKey The extension key to install the translations for
120 * @param string $language Language code of translation to fetch
121 * @param string $mirrorUrl Mirror URL to fetch data from
122 * @return bool TRUE on success, error string on failure
123 */
124 public function updateTranslation($extensionKey, $language, $mirrorUrl) {
125 $result = FALSE;
126 try {
127 $l10n = $this->fetchTranslation($extensionKey, $language, $mirrorUrl);
128 if (is_array($l10n)) {
129 $absolutePathToZipFile = GeneralUtility::getFileAbsFileName('typo3temp/' . $extensionKey . '-l10n-' . $language . '.zip');
130 $relativeLanguagePath = 'l10n' . '/' . $language . '/';
131 $absoluteLanguagePath = GeneralUtility::getFileAbsFileName(PATH_typo3conf . $relativeLanguagePath);
132 $absoluteExtensionLanguagePath = GeneralUtility::getFileAbsFileName(PATH_typo3conf . $relativeLanguagePath . $extensionKey . '/');
133 if (empty($absolutePathToZipFile) || empty($absoluteLanguagePath) || empty($absoluteExtensionLanguagePath)) {
134 throw new \TYPO3\CMS\Lang\Exception\Language('Given path is invalid.', 1352565336);
135 }
136 if (!is_dir($absoluteLanguagePath)) {
137 GeneralUtility::mkdir_deep(PATH_typo3conf, $relativeLanguagePath);
138 }
139 GeneralUtility::writeFile($absolutePathToZipFile, $l10n[0]);
140 if (is_dir($absoluteExtensionLanguagePath)) {
141 GeneralUtility::rmdir($absoluteExtensionLanguagePath, TRUE);
142 }
143 if ($this->unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath)) {
144 $result = TRUE;
145 }
146 }
147 } catch (\TYPO3\CMS\Core\Exception $exception) {
148 // @todo logging
149 }
150 return $result;
151 }
152
153 /**
154 * Fetches an extensions l10n file from the given mirror
155 *
156 * @param string $extensionKey Extension Key
157 * @param string $language The language code of the translation to fetch
158 * @param string $mirrorUrl URL of mirror to use
159 * @throws \TYPO3\CMS\Lang\Exception\XmlParser
160 * @return array Array containing l10n data
161 */
162 protected function fetchTranslation($extensionKey, $language, $mirrorUrl) {
163 $extensionPath = GeneralUtility::strtolower($extensionKey);
164 $mirrorUrl .= $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath .
165 '-l10n/' . $extensionPath . '-l10n-' . $language . '.zip';
166 $l10nResponse = GeneralUtility::getURL($mirrorUrl, 0, array(TYPO3_user_agent));
167 if ($l10nResponse === FALSE) {
168 throw new \TYPO3\CMS\Lang\Exception\XmlParser('Error: Translation could not be fetched.', 1345736785);
169 } else {
170 return array($l10nResponse);
171 }
172 }
173
174 /**
175 * Unzip an language zip file
176 *
177 * @param string $file path to zip file
178 * @param string $path path to extract to
179 * @throws \TYPO3\CMS\Lang\Exception\Language
180 * @return bool
181 */
182 protected function unzipTranslationFile($file, $path) {
183 $zip = zip_open($file);
184 if (is_resource($zip)) {
185 $result = TRUE;
186 if (!is_dir($path)) {
187 GeneralUtility::mkdir_deep($path);
188 }
189 while (($zipEntry = zip_read($zip)) !== FALSE) {
190 $zipEntryName = zip_entry_name($zipEntry);
191 if (strpos($zipEntryName, '/') !== FALSE) {
192 $zipEntryPathSegments = explode('/', $zipEntryName);
193 $fileName = array_pop($zipEntryPathSegments);
194 // It is a folder, because the last segment is empty, let's create it
195 if (empty($fileName)) {
196 GeneralUtility::mkdir_deep($path, implode('/', $zipEntryPathSegments));
197 } else {
198 $absoluteTargetPath = GeneralUtility::getFileAbsFileName($path . implode('/', $zipEntryPathSegments) . '/' . $fileName);
199 if (strlen(trim($absoluteTargetPath)) > 0) {
200 $return = GeneralUtility::writeFile(
201 $absoluteTargetPath, zip_entry_read($zipEntry, zip_entry_filesize($zipEntry))
202 );
203 if ($return === FALSE) {
204 throw new \TYPO3\CMS\Lang\Exception\Language('Could not write file ' . $zipEntryName, 1345304560);
205 }
206 } else {
207 throw new \TYPO3\CMS\Lang\Exception\Language('Could not write file ' . $zipEntryName, 1352566904);
208 }
209 }
210 } else {
211 throw new \TYPO3\CMS\Lang\Exception\Language('Extension directory missing in zip file!', 1352566905);
212 }
213 }
214 } else {
215 throw new \TYPO3\CMS\Lang\Exception\Language('Unable to open zip file ' . $file, 1345304561);
216 }
217 return $result;
218 }
219
220 }