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