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