[TASK] Deprecate extbase StopActionException
[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 StopActionException @deprecated since v11, will be removed in v12
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 // @deprecated since v11, change to return $this->redirect()
149 $this->redirect('unresolvedDependencies', 'List', null, ['extensionKey' => $extensionKey]);
150 }
151 }
152 } catch (StopActionException $exception) {
153 // @deprecated since v11, will be removed in v12: redirect() will no longer throw in v12, drop this catch block
154 throw $exception;
155 } catch (InvalidFileException $exception) {
156 $this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
157 } catch (\Exception $exception) {
158 $this->removeExtensionAndRestoreFromBackup($fileName);
159 $this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
160 }
161 // @deprecated since v11, change to return $this->redirect()
162 $this->redirect('index', 'List', null, [
163 self::TRIGGER_RefreshModuleMenu => true,
164 self::TRIGGER_RefreshTopbar => true
165 ]);
166 }
167
168 /**
169 * Validate the filename of an uploaded file
170 *
171 * @param string $fileName
172 * @throws InvalidFileException
173 */
174 protected function checkFileName($fileName)
175 {
176 if (empty($fileName)) {
177 throw new InvalidFileException('No file given.', 1342858852);
178 }
179 $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
180 if ($fileExtension !== 'zip') {
181 throw new InvalidFileException('Wrong file format "' . $fileExtension . '" given. Only .zip files are allowed.', 1342858853);
182 }
183 }
184
185 /**
186 * @param string $extensionKey
187 * @return bool
188 */
189 protected function activateExtension($extensionKey)
190 {
191 $this->managementService->reloadPackageInformation($extensionKey);
192 $extension = $this->managementService->getExtension($extensionKey);
193 return is_array($this->managementService->installExtension($extension));
194 }
195
196 /**
197 * Extracts a given zip file and installs the extension
198 *
199 * @param string $uploadedFile Path to uploaded file
200 * @param string $extensionKey
201 * @param bool $overwrite Overwrite existing extension if TRUE
202 * @return string
203 * @throws ExtensionManagerException
204 */
205 protected function extractExtensionFromZipFile(string $uploadedFile, string $extensionKey, bool $overwrite = false): string
206 {
207 $isExtensionAvailable = $this->managementService->isAvailable($extensionKey);
208 if (!$overwrite && $isExtensionAvailable) {
209 throw new ExtensionManagerException('Extension is already available and overwriting is disabled.', 1342864311);
210 }
211 if ($isExtensionAvailable) {
212 $this->copyExtensionFolderToTempFolder($extensionKey);
213 }
214 $this->removeFromOriginalPath = true;
215 $this->fileHandlingUtility->unzipExtensionFromFile($uploadedFile, $extensionKey);
216 return $extensionKey;
217 }
218
219 /**
220 * As there is no information about the extension key in the zip
221 * we have to use the file name to get that information
222 * filename format is expected to be extensionkey_version.zip.
223 *
224 * Removes version and file extension from filename to determine extension key
225 *
226 * @param string $fileName
227 * @return string
228 */
229 protected function getExtensionKeyFromFileName($fileName)
230 {
231 return preg_replace('/_(\\d+)(\\.|\\-)(\\d+)(\\.|\\-)(\\d+).*/i', '', strtolower(substr($fileName, 0, -4)));
232 }
233
234 /**
235 * Copies current extension folder to typo3temp directory as backup
236 *
237 * @param string $extensionKey
238 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
239 */
240 protected function copyExtensionFolderToTempFolder($extensionKey)
241 {
242 $this->extensionBackupPath = Environment::getVarPath() . '/transient/' . $extensionKey . substr(sha1($extensionKey . microtime()), 0, 7) . '/';
243 GeneralUtility::mkdir($this->extensionBackupPath);
244 GeneralUtility::copyDirectory(
245 $this->fileHandlingUtility->getExtensionDir($extensionKey),
246 $this->extensionBackupPath
247 );
248 }
249
250 /**
251 * Removes the extension directory and restores the extension from the backup directory
252 *
253 * @param string $fileName
254 * @see UploadExtensionFileController::extractAction
255 */
256 protected function removeExtensionAndRestoreFromBackup($fileName)
257 {
258 $extDirPath = $this->fileHandlingUtility->getExtensionDir($this->getExtensionKeyFromFileName($fileName));
259 if ($this->removeFromOriginalPath && is_dir($extDirPath)) {
260 GeneralUtility::rmdir($extDirPath, true);
261 }
262 if (!empty($this->extensionBackupPath)) {
263 GeneralUtility::mkdir($extDirPath);
264 GeneralUtility::copyDirectory($this->extensionBackupPath, $extDirPath);
265 }
266 }
267
268 /**
269 * Removes the backup folder in typo3temp
270 */
271 protected function removeBackupFolder()
272 {
273 if (!empty($this->extensionBackupPath)) {
274 GeneralUtility::rmdir($this->extensionBackupPath, true);
275 $this->extensionBackupPath = '';
276 }
277 }
278 }