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