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