Passed
Push — master ( baf93a...17f551 )
by
unknown
18:18
created

copyExtensionFolderToTempFolder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Extensionmanager\Controller;
17
18
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
19
use TYPO3\CMS\Core\Core\Environment;
20
use TYPO3\CMS\Core\Messaging\FlashMessage;
21
use TYPO3\CMS\Core\Security\BlockSerializationTrait;
22
use TYPO3\CMS\Core\Utility\GeneralUtility;
23
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
24
use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
25
use TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService;
26
use TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility;
27
28
/**
29
 * Controller for handling upload of a .zip file which is then placed as an extension
30
 * @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
31
 */
32
class UploadExtensionFileController extends AbstractController
33
{
34
    use BlockSerializationTrait;
35
36
    /**
37
     * @var FileHandlingUtility
38
     */
39
    protected $fileHandlingUtility;
40
41
    /**
42
     * @var ExtensionManagementService
43
     */
44
    protected $managementService;
45
46
    /**
47
     * @var string
48
     */
49
    protected $extensionBackupPath = '';
50
51
    /**
52
     * @var bool
53
     */
54
    protected $removeFromOriginalPath = false;
55
56
    /**
57
     * @param FileHandlingUtility $fileHandlingUtility
58
     */
59
    public function injectFileHandlingUtility(FileHandlingUtility $fileHandlingUtility)
60
    {
61
        $this->fileHandlingUtility = $fileHandlingUtility;
62
    }
63
64
    /**
65
     * @param ExtensionManagementService $managementService
66
     */
67
    public function injectManagementService(ExtensionManagementService $managementService)
68
    {
69
        $this->managementService = $managementService;
70
    }
71
72
    /**
73
     * Remove backup folder before destruction
74
     */
75
    public function __destruct()
76
    {
77
        $this->removeBackupFolder();
78
    }
79
80
    /**
81
     * Render upload extension form
82
     */
83
    public function formAction()
84
    {
85
        if (Environment::isComposerMode()) {
86
            throw new ExtensionManagerException(
87
                'Composer mode is active. You are not allowed to upload any extension file.',
88
                1444725828
89
            );
90
        }
91
    }
92
93
    /**
94
     * Extract an uploaded file and install the matching extension
95
     *
96
     * @param bool $overwrite Overwrite existing extension if TRUE
97
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
98
     */
99
    public function extractAction($overwrite = false)
100
    {
101
        if (Environment::isComposerMode()) {
102
            throw new ExtensionManagerException(
103
                'Composer mode is active. You are not allowed to upload any extension file.',
104
                1444725853
105
            );
106
        }
107
        $file = $_FILES['tx_extensionmanager_tools_extensionmanagerextensionmanager'];
108
        $fileName = pathinfo($file['name']['extensionFile'], PATHINFO_BASENAME);
109
        try {
110
            // If the file name isn't valid an error will be thrown
111
            $this->checkFileName($fileName);
112
            if (!empty($file['tmp_name']['extensionFile'])) {
113
                $tempFile = GeneralUtility::upload_to_tempfile($file['tmp_name']['extensionFile']);
114
            } else {
115
                throw new ExtensionManagerException(
116
                    'Creating temporary file failed. Check your upload_max_filesize and post_max_size limits.',
117
                    1342864339
118
                );
119
            }
120
            // Remove version and extension from filename to determine the extension key
121
            $extensionKey = $this->getExtensionKeyFromFileName($fileName);
122
            if (empty($extensionKey)) {
123
                throw new ExtensionManagerException(
124
                    'Could not extract extension key from uploaded file name. File name must be something like "my_extension_4.2.2.zip".',
125
                    1603087515
126
                );
127
            }
128
            $this->extractExtensionFromZipFile($tempFile, $extensionKey, (bool)$overwrite);
129
            $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'automaticInstallation');
130
            if (!$isAutomaticInstallationEnabled) {
131
                $this->addFlashMessage(
132
                    $this->translate('extensionList.uploadFlashMessage.message', [$extensionKey]),
133
                    $this->translate('extensionList.uploadFlashMessage.title'),
134
                    FlashMessage::OK
135
                );
136
            } else {
137
                if ($this->activateExtension($extensionKey)) {
138
                    $this->addFlashMessage(
139
                        $this->translate('extensionList.installedFlashMessage.message', [$extensionKey]),
140
                        '',
141
                        FlashMessage::OK
142
                    );
143
                } else {
144
                    $this->redirect('unresolvedDependencies', 'List', null, ['extensionKey' => $extensionKey]);
145
                }
146
            }
147
        } catch (StopActionException $exception) {
148
            throw $exception;
149
        } catch (\Exception $exception) {
150
            $this->removeExtensionAndRestoreFromBackup($fileName);
151
            $this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
152
        }
153
        $this->redirect('index', 'List', null, [
154
            self::TRIGGER_RefreshModuleMenu => true,
155
            self::TRIGGER_RefreshTopbar => true
156
        ]);
157
    }
158
159
    /**
160
     * Validate the filename of an uploaded file
161
     *
162
     * @param string $fileName
163
     * @throws ExtensionManagerException
164
     */
165
    protected function checkFileName($fileName)
166
    {
167
        if (empty($fileName)) {
168
            throw new ExtensionManagerException('No file given.', 1342858852);
169
        }
170
        $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
171
        if ($fileExtension !== 'zip') {
172
            throw new ExtensionManagerException('Wrong file format "' . $fileExtension . '" given. Only .zip files are allowed.', 1342858853);
173
        }
174
    }
175
176
    /**
177
     * @param string $extensionKey
178
     * @return bool
179
     */
180
    protected function activateExtension($extensionKey)
181
    {
182
        $this->managementService->reloadPackageInformation($extensionKey);
183
        $extension = $this->managementService->getExtension($extensionKey);
184
        return is_array($this->managementService->installExtension($extension));
185
    }
186
187
    /**
188
     * Extracts a given zip file and installs the extension
189
     *
190
     * @param string $uploadedFile Path to uploaded file
191
     * @param string $extensionKey
192
     * @param bool $overwrite Overwrite existing extension if TRUE
193
     * @return string
194
     * @throws ExtensionManagerException
195
     */
196
    protected function extractExtensionFromZipFile(string $uploadedFile, string $extensionKey, bool $overwrite = false): string
197
    {
198
        $isExtensionAvailable = $this->managementService->isAvailable($extensionKey);
199
        if (!$overwrite && $isExtensionAvailable) {
200
            throw new ExtensionManagerException('Extension is already available and overwriting is disabled.', 1342864311);
201
        }
202
        if ($isExtensionAvailable) {
203
            $this->copyExtensionFolderToTempFolder($extensionKey);
204
        }
205
        $this->removeFromOriginalPath = true;
206
        $this->fileHandlingUtility->unzipExtensionFromFile($uploadedFile, $extensionKey);
207
        return $extensionKey;
208
    }
209
210
    /**
211
     * As there is no information about the extension key in the zip
212
     * we have to use the file name to get that information
213
     * filename format is expected to be extensionkey_version.zip.
214
     *
215
     * Removes version and file extension from filename to determine extension key
216
     *
217
     * @param string $fileName
218
     * @return string
219
     */
220
    protected function getExtensionKeyFromFileName($fileName)
221
    {
222
        return preg_replace('/_(\\d+)(\\.|\\-)(\\d+)(\\.|\\-)(\\d+).*/i', '', strtolower(substr($fileName, 0, -4)));
223
    }
224
225
    /**
226
     * Copies current extension folder to typo3temp directory as backup
227
     *
228
     * @param string $extensionKey
229
     * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
230
     */
231
    protected function copyExtensionFolderToTempFolder($extensionKey)
232
    {
233
        $this->extensionBackupPath = Environment::getVarPath() . '/transient/' . $extensionKey . substr(sha1($extensionKey . microtime()), 0, 7) . '/';
234
        GeneralUtility::mkdir($this->extensionBackupPath);
235
        GeneralUtility::copyDirectory(
236
            $this->fileHandlingUtility->getExtensionDir($extensionKey),
237
            $this->extensionBackupPath
238
        );
239
    }
240
241
    /**
242
     * Removes the extension directory and restores the extension from the backup directory
243
     *
244
     * @param string $fileName
245
     * @see UploadExtensionFileController::extractAction
246
     */
247
    protected function removeExtensionAndRestoreFromBackup($fileName)
248
    {
249
        $extDirPath = $this->fileHandlingUtility->getExtensionDir($this->getExtensionKeyFromFileName($fileName));
250
        if ($this->removeFromOriginalPath && is_dir($extDirPath)) {
251
            GeneralUtility::rmdir($extDirPath, true);
252
        }
253
        if (!empty($this->extensionBackupPath)) {
254
            GeneralUtility::mkdir($extDirPath);
255
            GeneralUtility::copyDirectory($this->extensionBackupPath, $extDirPath);
256
        }
257
    }
258
259
    /**
260
     * Removes the backup folder in typo3temp
261
     */
262
    protected function removeBackupFolder()
263
    {
264
        if (!empty($this->extensionBackupPath)) {
265
            GeneralUtility::rmdir($this->extensionBackupPath, true);
266
            $this->extensionBackupPath = '';
267
        }
268
    }
269
}
270