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