[BUGFIX] Load language file for error messages
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Utility / FileHandlingUtility.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\Utility;
3 use TYPO3\CMS\Core\Utility\PathUtility;
4 use TYPO3\CMS\Core\Utility\GeneralUtility;
5 use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
6 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
7 use TYPO3\CMS\Lang\LanguageService;
8
9 /**
10 * This file is part of the TYPO3 CMS project.
11 *
12 * It is free software; you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License, either version 2
14 * of the License, or any later version.
15 *
16 * For the full copyright and license information, please read the
17 * LICENSE.txt file that was distributed with this source code.
18 *
19 * The TYPO3 project - inspiring people to share!
20 */
21 /**
22 * Utility for dealing with files and folders
23 *
24 * @author Susanne Moog <susanne.moog@typo3.org>
25 */
26 class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
27
28 /**
29 * @var \TYPO3\CMS\Extensionmanager\Utility\EmConfUtility
30 * @inject
31 */
32 protected $emConfUtility;
33
34 /**
35 * @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
36 * @inject
37 */
38 protected $installUtility;
39
40 /**
41 * Initialize method - loads language file
42 */
43 public function initializeObject() {
44 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_mod_tools_em.xlf');
45 }
46
47 /**
48 * Unpack an extension in t3x data format and write files
49 *
50 * @param array $extensionData
51 * @param Extension $extension
52 * @param string $pathType
53 * @return void
54 */
55 public function unpackExtensionFromExtensionDataArray(array $extensionData, Extension $extension = NULL, $pathType = 'Local') {
56 $extensionDir = $this->makeAndClearExtensionDir($extensionData['extKey'], $pathType);
57 $files = $this->extractFilesArrayFromExtensionData($extensionData);
58 $directories = $this->extractDirectoriesFromExtensionData($files);
59 $files = array_diff_key($files, array_flip($directories));
60 $this->createDirectoriesForExtensionFiles($directories, $extensionDir);
61 $this->writeExtensionFiles($files, $extensionDir);
62 $this->writeEmConfToFile($extensionData, $extensionDir, $extension);
63 }
64
65 /**
66 * Extract needed directories from given extensionDataFilesArray
67 *
68 * @param array $files
69 * @return array
70 */
71 protected function extractDirectoriesFromExtensionData(array $files) {
72 $directories = array();
73 foreach ($files as $filePath => $file) {
74 preg_match('/(.*)\\//', $filePath, $matches);
75 if (!empty($matches[0])) {
76 $directories[] = $matches[0];
77 }
78 }
79 return array_unique($directories);
80 }
81
82 /**
83 * Returns the "FILES" part from the data array
84 *
85 * @param array $extensionData
86 * @return mixed
87 */
88 protected function extractFilesArrayFromExtensionData(array $extensionData) {
89 return $extensionData['FILES'];
90 }
91
92 /**
93 * Loops over an array of directories and creates them in the given root path
94 * It also creates nested directory structures
95 *
96 * @param array $directories
97 * @param string $rootPath
98 * @return void
99 */
100 protected function createDirectoriesForExtensionFiles(array $directories, $rootPath) {
101 foreach ($directories as $directory) {
102 $this->createNestedDirectory($rootPath . $directory);
103 }
104 }
105
106 /**
107 * Wrapper for utility method to create directory recusively
108 *
109 * @param string $directory Absolute path
110 * @throws ExtensionManagerException
111 */
112 protected function createNestedDirectory($directory) {
113 try {
114 GeneralUtility::mkdir_deep($directory);
115 } catch(\RuntimeException $exception) {
116 throw new ExtensionManagerException(
117 sprintf($this->getLanguageService()->getLL('clearMakeExtDir_could_not_create_dir'), $this->getRelativePath($directory)),
118 1337280416
119 );
120 }
121
122 }
123
124 /**
125 * Loops over an array of files and writes them to the given rootPath
126 *
127 * @param array $files
128 * @param string $rootPath
129 * @return void
130 */
131 protected function writeExtensionFiles(array $files, $rootPath) {
132 foreach ($files as $file) {
133 GeneralUtility::writeFile($rootPath . $file['name'], $file['content']);
134 }
135 }
136
137 /**
138 * Removes the current extension of $type and creates the base folder for
139 * the new one (which is going to be imported)
140 *
141 * @param string $extensionKey
142 * @param string $pathType Extension installation scope (Local,Global,System)
143 * @throws ExtensionManagerException
144 * @return string
145 */
146 protected function makeAndClearExtensionDir($extensionKey, $pathType = 'Local') {
147 $extDirPath = $this->getExtensionDir($extensionKey, $pathType);
148 if (is_dir($extDirPath)) {
149 $this->removeDirectory($extDirPath);
150 }
151 $this->addDirectory($extDirPath);
152
153 return $extDirPath;
154 }
155
156 /**
157 * Returns the installation directory for an extension depending on the installation scope
158 *
159 * @param string $extensionKey
160 * @param string $pathType Extension installation scope (Local,Global,System)
161 * @return string
162 * @throws ExtensionManagerException
163 */
164 public function getExtensionDir($extensionKey, $pathType = 'Local') {
165 $paths = Extension::returnInstallPaths();
166 $path = $paths[$pathType];
167 if (!$path || !is_dir($path) || !$extensionKey) {
168 throw new ExtensionManagerException(sprintf('ERROR: The extension install path "%s" was no directory!', $this->getRelativePath($path)), 1337280417);
169 }
170
171 return $path . $extensionKey . '/';
172 }
173
174 /**
175 * Add specified directory
176 *
177 * @param string $extDirPath
178 * @throws ExtensionManagerException
179 * @return void
180 */
181 protected function addDirectory($extDirPath) {
182 GeneralUtility::mkdir($extDirPath);
183 if (!is_dir($extDirPath)) {
184 throw new ExtensionManagerException(
185 sprintf($this->getLanguageService()->getLL('clearMakeExtDir_could_not_create_dir'), $this->getRelativePath($extDirPath)),
186 1337280418
187 );
188 }
189 }
190
191 /**
192 * Creates directories configured in ext_emconf.php if not already present
193 *
194 * @param array $extension
195 */
196 public function ensureConfiguredDirectoriesExist(array $extension) {
197 foreach ($this->getAbsolutePathsToConfiguredDirectories($extension) as $directory) {
198 if (!$this->directoryExists($directory)) {
199 $this->createNestedDirectory($directory);
200 }
201 }
202 }
203
204 /**
205 * Wrapper method for directory existance check
206 *
207 * @param string $directory
208 * @return boolean
209 */
210 protected function directoryExists($directory) {
211 return is_dir($directory);
212 }
213
214 /**
215 * Checks configuration and returns an array of absolute paths that should be created
216 *
217 * @param array $extension
218 * @return array
219 */
220 protected function getAbsolutePathsToConfiguredDirectories(array $extension) {
221 $requestedDirectories = array();
222 $requestUploadFolder = isset($extension['uploadfolder']) ? (boolean)$extension['uploadfolder'] : FALSE;
223 if ($requestUploadFolder) {
224 $requestedDirectories[] = $this->getAbsolutePath($this->getPathToUploadFolder($extension));
225 }
226
227 $requestCreateDirectories = empty($extension['createDirs']) ? FALSE : (string)$extension['createDirs'];
228 if ($requestCreateDirectories) {
229 foreach (GeneralUtility::trimExplode(',', $extension['createDirs']) as $directoryToCreate) {
230 $requestedDirectories[] = $this->getAbsolutePath($directoryToCreate);
231 }
232 }
233
234 return $requestedDirectories;
235 }
236
237 /**
238 * Upload folders always reside in “uploads/tx_[extKey-with-no-underscore]”
239 *
240 * @param array $extension
241 * @return string
242 */
243 protected function getPathToUploadFolder($extension) {
244 return 'uploads/tx_' . str_replace('_', '', $extension['key']) . '/';
245 }
246
247 /**
248 * Remove specified directory
249 *
250 * @param string $extDirPath
251 * @throws ExtensionManagerException
252 * @return void
253 */
254 public function removeDirectory($extDirPath) {
255 $extDirPath = GeneralUtility::fixWindowsFilePath($extDirPath);
256 $extensionPathWithoutTrailingSlash = rtrim($extDirPath, '/');
257 if (is_link($extensionPathWithoutTrailingSlash) && TYPO3_OS !== 'WIN') {
258 $result = unlink($extensionPathWithoutTrailingSlash);
259 } else {
260 $result = GeneralUtility::rmdir($extDirPath, TRUE);
261 }
262 if ($result === FALSE) {
263 throw new ExtensionManagerException(
264 sprintf($this->getLanguageService()->getLL('clearMakeExtDir_could_not_remove_dir'), $this->getRelativePath($extDirPath)),
265 1337280415
266 );
267 }
268 }
269
270 /**
271 * Constructs emConf and writes it to corresponding file
272 *
273 * @param array $extensionData
274 * @param string $rootPath
275 * @param Extension $extension
276 * @return void
277 */
278 protected function writeEmConfToFile(array $extensionData, $rootPath, Extension $extension = NULL) {
279 $emConfContent = $this->emConfUtility->constructEmConf($extensionData, $extension);
280 GeneralUtility::writeFile($rootPath . 'ext_emconf.php', $emConfContent);
281 }
282
283 /**
284 * Is the given path a valid path for extension installation
285 *
286 * @param string $path the absolute (!) path in question
287 * @return boolean
288 */
289 public function isValidExtensionPath($path) {
290 $allowedPaths = Extension::returnAllowedInstallPaths();
291 foreach ($allowedPaths as $allowedPath) {
292 if (GeneralUtility::isFirstPartOfStr($path, $allowedPath)) {
293 return TRUE;
294 }
295 }
296 return FALSE;
297 }
298
299 /**
300 * Returns absolute path
301 *
302 * @param string $relativePath
303 * @throws ExtensionManagerException
304 * @return string
305 */
306 protected function getAbsolutePath($relativePath) {
307 $absolutePath = GeneralUtility::getFileAbsFileName(GeneralUtility::resolveBackPath(PATH_site . $relativePath));
308 if (empty($absolutePath)) {
309 throw new ExtensionManagerException('Illegal relative path given', 1350742864);
310 }
311 return $absolutePath;
312 }
313
314 /**
315 * Returns relative path
316 *
317 * @param string $absolutePath
318 * @return string
319 */
320 protected function getRelativePath($absolutePath) {
321 return PathUtility::stripPathSitePrefix($absolutePath);
322 }
323
324 /**
325 * Get extension path for an available or installed extension
326 *
327 * @param string $extension
328 * @return string
329 */
330 public function getAbsoluteExtensionPath($extension) {
331 $extension = $this->installUtility->enrichExtensionWithDetails($extension);
332 $absolutePath = $this->getAbsolutePath($extension['siteRelPath']);
333 return $absolutePath;
334 }
335
336 /**
337 * Get version of an available or installed extension
338 *
339 * @param string $extension
340 * @return string
341 */
342 public function getExtensionVersion($extension) {
343 $extensionData = $this->installUtility->enrichExtensionWithDetails($extension);
344 $version = $extensionData['version'];
345 return $version;
346 }
347
348 /**
349 * Create a zip file from an extension
350 *
351 * @param array $extension
352 * @return string Name and path of create zip file
353 */
354 public function createZipFileFromExtension($extension) {
355
356 $extensionPath = $this->getAbsoluteExtensionPath($extension);
357
358 // Add trailing slash to the extension path, getAllFilesAndFoldersInPath explicitly requires that.
359 $extensionPath = PathUtility::sanitizeTrailingSeparator($extensionPath);
360
361 $version = $this->getExtensionVersion($extension);
362 if (empty($version)) {
363 $version = '0.0.0';
364 }
365
366 $fileName = $this->getAbsolutePath('typo3temp/' . $extension . '_' . $version . '_' . date('YmdHi') . '.zip');
367
368 $zip = new \ZipArchive();
369 $zip->open($fileName, \ZipArchive::CREATE);
370
371 $excludePattern = $GLOBALS['TYPO3_CONF_VARS']['EXT']['excludeForPackaging'];
372
373 // Get all the files of the extension, but exclude the ones specified in the excludePattern
374 $files = GeneralUtility::getAllFilesAndFoldersInPath(
375 array(), // No files pre-added
376 $extensionPath, // Start from here
377 '', // Do not filter files by extension
378 TRUE, // Include subdirectories
379 PHP_INT_MAX, // Recursion level
380 $excludePattern // Files and directories to exclude.
381 );
382
383 // Make paths relative to extension root directory.
384 $files = GeneralUtility::removePrefixPathFromList($files, $extensionPath);
385
386 // Remove the one empty path that is the extension dir itself.
387 $files = array_filter($files);
388
389 foreach ($files as $file) {
390 $fullPath = $extensionPath . $file;
391 // Distinguish between files and directories, as creation of the archive
392 // fails on Windows when trying to add a directory with "addFile".
393 if (is_dir($fullPath)) {
394 $zip->addEmptyDir($file);
395 } else {
396 $zip->addFile($fullPath, $file);
397 }
398 }
399
400 $zip->close();
401 return $fileName;
402 }
403
404 /**
405 * Unzip an extension.zip.
406 *
407 * @param string $file path to zip file
408 * @param string $fileName file name
409 * @param string $pathType path type (Local, Global, System)
410 * @throws ExtensionManagerException
411 * @return void
412 */
413 public function unzipExtensionFromFile($file, $fileName, $pathType = 'Local') {
414 $extensionDir = $this->makeAndClearExtensionDir($fileName, $pathType);
415 $zip = zip_open($file);
416 if (is_resource($zip)) {
417 while (($zipEntry = zip_read($zip)) !== FALSE) {
418 if (strpos(zip_entry_name($zipEntry), '/') !== FALSE) {
419 $last = strrpos(zip_entry_name($zipEntry), '/');
420 $dir = substr(zip_entry_name($zipEntry), 0, $last);
421 $file = substr(zip_entry_name($zipEntry), strrpos(zip_entry_name($zipEntry), '/') + 1);
422 if (!is_dir($dir)) {
423 GeneralUtility::mkdir_deep($extensionDir . $dir);
424 }
425 if (strlen(trim($file)) > 0) {
426 $return = GeneralUtility::writeFile($extensionDir . $dir . '/' . $file, zip_entry_read($zipEntry, zip_entry_filesize($zipEntry)));
427 if ($return === FALSE) {
428 throw new ExtensionManagerException('Could not write file ' . $this->getRelativePath($file), 1344691048);
429 }
430 }
431 } else {
432 GeneralUtility::writeFile($extensionDir . zip_entry_name($zipEntry), zip_entry_read($zipEntry, zip_entry_filesize($zipEntry)));
433 }
434 }
435 } else {
436 throw new ExtensionManagerException('Unable to open zip file ' . $this->getRelativePath($file), 1344691049);
437 }
438 }
439
440 /**
441 * Sends a zip file to the browser and deletes it afterwards
442 *
443 * @param string $fileName
444 * @param string $downloadName
445 * @return void
446 */
447 public function sendZipFileToBrowserAndDelete($fileName, $downloadName = '') {
448 if ($downloadName === '') {
449 $downloadName = basename($fileName, '.zip');
450 }
451 header('Content-Type: application/zip');
452 header('Content-Length: ' . filesize($fileName));
453 header('Content-Disposition: attachment; filename="' . $downloadName . '.zip"');
454 readfile($fileName);
455 unlink($fileName);
456 die;
457 }
458
459 /**
460 * Sends the sql dump file to the browser and deletes it afterwards
461 *
462 * @param string $fileName
463 * @param string $downloadName
464 * @return void
465 */
466 public function sendSqlDumpFileToBrowserAndDelete($fileName, $downloadName = '') {
467 if ($downloadName === '') {
468 $downloadName = basename($fileName, '.sql');
469 } else {
470 $downloadName = basename($downloadName, '.sql');
471 }
472 header('Content-Type: text');
473 header('Content-Length: ' . filesize($fileName));
474 header('Content-Disposition: attachment; filename="' . $downloadName . '.sql"');
475 readfile($fileName);
476 unlink($fileName);
477 die;
478 }
479
480 /**
481 * @return LanguageService
482 */
483 protected function getLanguageService() {
484 return $GLOBALS['LANG'];
485 }
486
487 }