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