FileHandlingUtility.php 9.61 KB
Newer Older
1
<?php
2

3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
7
8
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
9
 *
10
11
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
14
 * The TYPO3 project - inspiring people to share!
 */
15

16
17
namespace TYPO3\CMS\Extensionmanager\Utility;

18
19
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
20
use TYPO3\CMS\Core\Core\Environment;
21
use TYPO3\CMS\Core\Exception\Archive\ExtractException;
22
use TYPO3\CMS\Core\Localization\LanguageService;
23
use TYPO3\CMS\Core\Service\Archive\ZipService;
24
25
26
27
28
29
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;

30
31
/**
 * Utility for dealing with files and folders
32
 * @internal This class is a specific ExtensionManager implementation and is not part of the Public TYPO3 API.
33
 */
34
class FileHandlingUtility implements SingletonInterface, LoggerAwareInterface
35
{
36
37
    use LoggerAwareTrait;

38
    /**
39
     * @var EmConfUtility
40
41
42
43
     */
    protected $emConfUtility;

    /**
44
     * @var InstallUtility
45
46
47
48
     */
    protected $installUtility;

    /**
49
     * @var LanguageService
50
51
52
53
     */
    protected $languageService;

    /**
54
     * @param EmConfUtility $emConfUtility
55
     */
56
    public function injectEmConfUtility(EmConfUtility $emConfUtility)
57
58
59
60
61
    {
        $this->emConfUtility = $emConfUtility;
    }

    /**
62
     * @param InstallUtility $installUtility
63
     */
64
    public function injectInstallUtility(InstallUtility $installUtility)
65
66
67
68
69
    {
        $this->installUtility = $installUtility;
    }

    /**
70
     * @param LanguageService $languageService
71
     */
72
    public function injectLanguageService(LanguageService $languageService)
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
    {
        $this->languageService = $languageService;
    }

    /**
     * Initialize method - loads language file
     */
    public function initializeObject()
    {
        $this->languageService->includeLLFile('EXT:extensionmanager/Resources/Private/Language/locallang.xlf');
    }

    /**
     * Unpack an extension in t3x data format and write files
     *
88
     * @param string $extensionKey
89
90
91
     * @param array $extensionData
     * @param string $pathType
     */
92
    public function unpackExtensionFromExtensionDataArray(string $extensionKey, array $extensionData, $pathType = 'Local')
93
    {
94
95
96
        $files = $extensionData['FILES'] ?? [];
        $emConfData = $extensionData['EM_CONF'] ?? [];
        $extensionDir = $this->makeAndClearExtensionDir($extensionKey, $pathType);
97
98
99
100
        $directories = $this->extractDirectoriesFromExtensionData($files);
        $files = array_diff_key($files, array_flip($directories));
        $this->createDirectoriesForExtensionFiles($directories, $extensionDir);
        $this->writeExtensionFiles($files, $extensionDir);
101
102
        $this->writeEmConfToFile($extensionKey, $emConfData, $extensionDir);
        $this->reloadPackageInformation($extensionKey);
103
104
105
106
107
108
109
110
111
112
    }

    /**
     * Extract needed directories from given extensionDataFilesArray
     *
     * @param array $files
     * @return array
     */
    protected function extractDirectoriesFromExtensionData(array $files)
    {
113
        $directories = [];
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
        foreach ($files as $filePath => $file) {
            preg_match('/(.*)\\//', $filePath, $matches);
            if (!empty($matches[0])) {
                $directories[] = $matches[0];
            }
        }
        return array_unique($directories);
    }

    /**
     * Loops over an array of directories and creates them in the given root path
     * It also creates nested directory structures
     *
     * @param array $directories
     * @param string $rootPath
     */
130
    protected function createDirectoriesForExtensionFiles(array $directories, string $rootPath)
131
132
133
134
135
136
137
    {
        foreach ($directories as $directory) {
            $this->createNestedDirectory($rootPath . $directory);
        }
    }

    /**
138
     * Wrapper for utility method to create directory recursively
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
     *
     * @param string $directory Absolute path
     * @throws ExtensionManagerException
     */
    protected function createNestedDirectory($directory)
    {
        try {
            GeneralUtility::mkdir_deep($directory);
        } catch (\RuntimeException $exception) {
            throw new ExtensionManagerException(
                sprintf($this->languageService->getLL('fileHandling.couldNotCreateDirectory'), $this->getRelativePath($directory)),
                1337280416
            );
        }
    }

    /**
     * Loops over an array of files and writes them to the given rootPath
     *
     * @param array $files
     * @param string $rootPath
     */
    protected function writeExtensionFiles(array $files, $rootPath)
    {
        foreach ($files as $file) {
            GeneralUtility::writeFile($rootPath . $file['name'], $file['content']);
        }
    }

    /**
     * Removes the current extension of $type and creates the base folder for
     * the new one (which is going to be imported)
     *
     * @param string $extensionKey
     * @param string $pathType Extension installation scope (Local,Global,System)
     * @throws ExtensionManagerException
     * @return string
     */
    protected function makeAndClearExtensionDir($extensionKey, $pathType = 'Local')
    {
        $extDirPath = $this->getExtensionDir($extensionKey, $pathType);
        if (is_dir($extDirPath)) {
            $this->removeDirectory($extDirPath);
        }
        $this->addDirectory($extDirPath);
        return $extDirPath;
    }

    /**
     * Returns the installation directory for an extension depending on the installation scope
     *
     * @param string $extensionKey
     * @param string $pathType Extension installation scope (Local,Global,System)
     * @return string
     * @throws ExtensionManagerException
     */
    public function getExtensionDir($extensionKey, $pathType = 'Local')
    {
        $paths = Extension::returnInstallPaths();
198
        $path = $paths[$pathType] ?? '';
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
        if (!$path || !is_dir($path) || !$extensionKey) {
            throw new ExtensionManagerException(
                sprintf($this->languageService->getLL('fileHandling.installPathWasNoDirectory'), $this->getRelativePath($path)),
                1337280417
            );
        }

        return $path . $extensionKey . '/';
    }

    /**
     * Add specified directory
     *
     * @param string $extDirPath
     * @throws ExtensionManagerException
     */
    protected function addDirectory($extDirPath)
    {
        GeneralUtility::mkdir($extDirPath);
        if (!is_dir($extDirPath)) {
            throw new ExtensionManagerException(
                sprintf($this->languageService->getLL('fileHandling.couldNotCreateDirectory'), $this->getRelativePath($extDirPath)),
                1337280418
            );
        }
    }

    /**
     * Remove specified directory
     *
     * @param string $extDirPath
     * @throws ExtensionManagerException
     */
    public function removeDirectory($extDirPath)
    {
        $extDirPath = GeneralUtility::fixWindowsFilePath($extDirPath);
        $extensionPathWithoutTrailingSlash = rtrim($extDirPath, '/');
236
        if (is_link($extensionPathWithoutTrailingSlash) && !Environment::isWindows()) {
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
            $result = unlink($extensionPathWithoutTrailingSlash);
        } else {
            $result = GeneralUtility::rmdir($extDirPath, true);
        }
        if ($result === false) {
            throw new ExtensionManagerException(
                sprintf($this->languageService->getLL('fileHandling.couldNotRemoveDirectory'), $this->getRelativePath($extDirPath)),
                1337280415
            );
        }
    }

    /**
     * Constructs emConf and writes it to corresponding file
     *
252
253
     * @param string $extensionKey
     * @param array $emConfData
254
255
     * @param string $rootPath
     */
256
    protected function writeEmConfToFile(string $extensionKey, array $emConfData, string $rootPath)
257
    {
258
        $emConfContent = $this->emConfUtility->constructEmConf($extensionKey, $emConfData);
259
260
261
262
263
264
265
266
267
        GeneralUtility::writeFile($rootPath . 'ext_emconf.php', $emConfContent);
    }

    /**
     * Returns relative path
     *
     * @param string $absolutePath
     * @return string
     */
268
    protected function getRelativePath(string $absolutePath): string
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
    {
        return PathUtility::stripPathSitePrefix($absolutePath);
    }

    /**
     * Unzip an extension.zip.
     *
     * @param string $file path to zip file
     * @param string $fileName file name
     * @param string $pathType path type (Local, Global, System)
     * @throws ExtensionManagerException
     */
    public function unzipExtensionFromFile($file, $fileName, $pathType = 'Local')
    {
        $extensionDir = $this->makeAndClearExtensionDir($fileName, $pathType);
284
285
286
287
288

        try {
            $zipService = GeneralUtility::makeInstance(ZipService::class);
            if ($zipService->verify($file)) {
                $zipService->extract($file, $extensionDir);
289
            }
290
291
292
        } catch (ExtractException $e) {
            $this->logger->error('Extracting the extension archive failed', ['exception' => $e]);
            throw new ExtensionManagerException('Extracting the extension archive failed: ' . $e->getMessage(), 1565777179, $e);
293
        }
294
295

        GeneralUtility::fixPermissions($extensionDir, true);
296
297
298
299
300
301
302
303
304
    }

    /**
     * @param string $extensionKey
     */
    protected function reloadPackageInformation($extensionKey)
    {
        $this->installUtility->reloadPackageInformation($extensionKey);
    }
305
}