[FEATURE] Use dynamic path for typo3temp/var/
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Controller / UploadExtensionFileController.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
18 use TYPO3\CMS\Core\Core\Environment;
19 use TYPO3\CMS\Core\Messaging\FlashMessage;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Extensionmanager\Exception\DependencyConfigurationNotFoundException;
22 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
23
24 /**
25 * Controller for handling upload of a local extension file
26 * Handles .t3x or .zip files
27 */
28 class UploadExtensionFileController extends AbstractController
29 {
30 /**
31 * @var \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository
32 */
33 protected $extensionRepository;
34
35 /**
36 * @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
37 */
38 protected $fileHandlingUtility;
39
40 /**
41 * @var \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility
42 */
43 protected $terUtility;
44
45 /**
46 * @var \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService
47 */
48 protected $managementService;
49
50 /**
51 * @var string
52 */
53 protected $extensionBackupPath = '';
54
55 /**
56 * @var bool
57 */
58 protected $removeFromOriginalPath = false;
59
60 /**
61 * @param \TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository
62 */
63 public function injectExtensionRepository(\TYPO3\CMS\Extensionmanager\Domain\Repository\ExtensionRepository $extensionRepository)
64 {
65 $this->extensionRepository = $extensionRepository;
66 }
67
68 /**
69 * @param \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility
70 */
71 public function injectFileHandlingUtility(\TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility $fileHandlingUtility)
72 {
73 $this->fileHandlingUtility = $fileHandlingUtility;
74 }
75
76 /**
77 * @param \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility $terUtility
78 */
79 public function injectTerUtility(\TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility $terUtility)
80 {
81 $this->terUtility = $terUtility;
82 }
83
84 /**
85 * @param \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService
86 */
87 public function injectManagementService(\TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService $managementService)
88 {
89 $this->managementService = $managementService;
90 }
91
92 /**
93 * Remove backup folder before destruction
94 */
95 public function __destruct()
96 {
97 $this->removeBackupFolder();
98 }
99
100 /**
101 * Render upload extension form
102 */
103 public function formAction()
104 {
105 if (Environment::isComposerMode()) {
106 throw new ExtensionManagerException(
107 'Composer mode is active. You are not allowed to upload any extension file.',
108 1444725828
109 );
110 }
111 }
112
113 /**
114 * Extract an uploaded file and install the matching extension
115 *
116 * @param bool $overwrite Overwrite existing extension if TRUE
117 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
118 */
119 public function extractAction($overwrite = false)
120 {
121 if (Environment::isComposerMode()) {
122 throw new ExtensionManagerException(
123 'Composer mode is active. You are not allowed to upload any extension file.',
124 1444725853
125 );
126 }
127 $file = $_FILES['tx_extensionmanager_tools_extensionmanagerextensionmanager'];
128 $fileName = pathinfo($file['name']['extensionFile'], PATHINFO_BASENAME);
129 try {
130 // If the file name isn't valid an error will be thrown
131 $this->checkFileName($fileName);
132 if (!empty($file['tmp_name']['extensionFile'])) {
133 $tempFile = GeneralUtility::upload_to_tempfile($file['tmp_name']['extensionFile']);
134 } else {
135 throw new ExtensionManagerException(
136 'Creating temporary file failed. Check your upload_max_filesize and post_max_size limits.',
137 1342864339
138 );
139 }
140 $extensionData = $this->extractExtensionFromFile($tempFile, $fileName, $overwrite);
141 $isAutomaticInstallationEnabled = (bool)GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('extensionmanager', 'offlineMode');
142 if (!$isAutomaticInstallationEnabled) {
143 $this->addFlashMessage(
144 $this->translate('extensionList.uploadFlashMessage.message', [$extensionData['extKey']]),
145 $this->translate('extensionList.uploadFlashMessage.title'),
146 FlashMessage::OK
147 );
148 } else {
149 if ($this->activateExtension($extensionData['extKey'])) {
150 $this->addFlashMessage(
151 $this->translate('extensionList.installedFlashMessage.message', [$extensionData['extKey']]),
152 '',
153 FlashMessage::OK
154 );
155 } else {
156 $this->redirect('unresolvedDependencies', 'List', null, ['extensionKey' => $extensionData['extKey']]);
157 }
158 }
159 } catch (\TYPO3\CMS\Extbase\Mvc\Exception\StopActionException $exception) {
160 throw $exception;
161 } catch (DependencyConfigurationNotFoundException $exception) {
162 $this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
163 } catch (\Exception $exception) {
164 $this->removeExtensionAndRestoreFromBackup($fileName);
165 $this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
166 }
167 $this->redirect('index', 'List', null, [
168 self::TRIGGER_RefreshModuleMenu => true,
169 self::TRIGGER_RefreshTopbar => true
170 ]);
171 }
172
173 /**
174 * Validate the filename of an uploaded file
175 *
176 * @param string $fileName
177 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
178 */
179 public function checkFileName($fileName)
180 {
181 $extension = pathinfo($fileName, PATHINFO_EXTENSION);
182 if (empty($fileName)) {
183 throw new ExtensionManagerException('No file given.', 1342858852);
184 }
185 if ($extension !== 't3x' && $extension !== 'zip') {
186 throw new ExtensionManagerException('Wrong file format "' . $extension . '" given. Allowed formats are t3x and zip.', 1342858853);
187 }
188 }
189
190 /**
191 * Extract a given t3x or zip file
192 *
193 * @param string $uploadPath Path to existing extension file
194 * @param string $fileName Filename of the uploaded file
195 * @param bool $overwrite If true, extension will be replaced
196 * @return array Extension data
197 * @throws ExtensionManagerException
198 * @throws DependencyConfigurationNotFoundException
199 */
200 public function extractExtensionFromFile($uploadPath, $fileName, $overwrite)
201 {
202 $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
203 if ($fileExtension === 't3x') {
204 $extensionData = $this->getExtensionFromT3xFile($uploadPath, $overwrite);
205 } else {
206 $extensionData = $this->getExtensionFromZipFile($uploadPath, $fileName, $overwrite);
207 }
208
209 return $extensionData;
210 }
211
212 /**
213 * @param string $extensionKey
214 * @return bool
215 */
216 public function activateExtension($extensionKey)
217 {
218 $this->managementService->reloadPackageInformation($extensionKey);
219 $extension = $this->managementService->getExtension($extensionKey);
220 return is_array($this->managementService->installExtension($extension));
221 }
222
223 /**
224 * Extracts a given t3x file and installs the extension
225 *
226 * @param string $file Path to uploaded file
227 * @param bool $overwrite Overwrite existing extension if TRUE
228 * @throws ExtensionManagerException
229 * @throws DependencyConfigurationNotFoundException
230 * @return array
231 */
232 protected function getExtensionFromT3xFile($file, $overwrite = false)
233 {
234 $fileContent = file_get_contents($file);
235 if (!$fileContent) {
236 throw new ExtensionManagerException('File had no or wrong content.', 1342859339);
237 }
238 $extensionData = $this->terUtility->decodeExchangeData($fileContent);
239 if (empty($extensionData['extKey'])) {
240 throw new ExtensionManagerException('Decoding the file went wrong. No extension key found', 1342864309);
241 }
242 $isExtensionAvailable = $this->managementService->isAvailable($extensionData['extKey']);
243 if (!$overwrite && $isExtensionAvailable) {
244 throw new ExtensionManagerException($this->translate('extensionList.overwritingDisabled'), 1342864310);
245 }
246 if ($isExtensionAvailable) {
247 $this->copyExtensionFolderToTempFolder($extensionData['extKey']);
248 }
249 $this->removeFromOriginalPath = true;
250 $extension = $this->extensionRepository->findOneByExtensionKeyAndVersion($extensionData['extKey'], $extensionData['EM_CONF']['version']);
251 $this->fileHandlingUtility->unpackExtensionFromExtensionDataArray($extensionData, $extension);
252
253 if (empty($extension)
254 && empty($extensionData['EM_CONF']['constraints'])
255 && !isset($extensionData['FILES']['ext_emconf.php'])
256 && !isset($extensionData['FILES']['/ext_emconf.php'])
257 ) {
258 throw new DependencyConfigurationNotFoundException('Extension cannot be installed automatically because no dependencies could be found! Please check dependencies manually (on typo3.org) before installing the extension.', 1439587168);
259 }
260
261 return $extensionData;
262 }
263
264 /**
265 * Extracts a given zip file and installs the extension
266 * As there is no information about the extension key in the zip
267 * we have to use the file name to get that information
268 * filename format is expected to be extensionkey_version.zip
269 *
270 * @param string $file Path to uploaded file
271 * @param string $fileName Filename (basename) of uploaded file
272 * @param bool $overwrite Overwrite existing extension if TRUE
273 * @return array
274 * @throws ExtensionManagerException
275 */
276 protected function getExtensionFromZipFile($file, $fileName, $overwrite = false)
277 {
278 // Remove version and extension from filename to determine the extension key
279 $extensionKey = $this->getExtensionKeyFromFileName($fileName);
280 $isExtensionAvailable = $this->managementService->isAvailable($extensionKey);
281 if (!$overwrite && $isExtensionAvailable) {
282 throw new ExtensionManagerException('Extension is already available and overwriting is disabled.', 1342864311);
283 }
284 if ($isExtensionAvailable) {
285 $this->copyExtensionFolderToTempFolder($extensionKey);
286 }
287 $this->removeFromOriginalPath = true;
288 $this->fileHandlingUtility->unzipExtensionFromFile($file, $extensionKey);
289
290 return ['extKey' => $extensionKey];
291 }
292
293 /**
294 * Removes version and file extension from filename to determine extension key
295 *
296 * @param string $fileName
297 * @return string
298 */
299 protected function getExtensionKeyFromFileName($fileName)
300 {
301 return preg_replace('/_(\\d+)(\\.|\\-)(\\d+)(\\.|\\-)(\\d+).*/i', '', strtolower(substr($fileName, 0, -4)));
302 }
303
304 /**
305 * Copies current extension folder to typo3temp directory as backup
306 *
307 * @param string $extensionKey
308 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
309 */
310 protected function copyExtensionFolderToTempFolder($extensionKey)
311 {
312 $this->extensionBackupPath = Environment::getVarPath() . '/transient/' . $extensionKey . substr(sha1($extensionKey . microtime()), 0, 7) . '/';
313 GeneralUtility::mkdir($this->extensionBackupPath);
314 GeneralUtility::copyDirectory(
315 $this->fileHandlingUtility->getExtensionDir($extensionKey),
316 $this->extensionBackupPath
317 );
318 }
319
320 /**
321 * Removes the extension directory and restores the extension from the backup directory
322 *
323 * @param string $fileName
324 * @see UploadExtensionFileController::extractAction
325 */
326 protected function removeExtensionAndRestoreFromBackup($fileName)
327 {
328 $extDirPath = $this->fileHandlingUtility->getExtensionDir($this->getExtensionKeyFromFileName($fileName));
329 if ($this->removeFromOriginalPath && is_dir($extDirPath)) {
330 GeneralUtility::rmdir($extDirPath, true);
331 }
332 if (!empty($this->extensionBackupPath)) {
333 GeneralUtility::mkdir($extDirPath);
334 GeneralUtility::copyDirectory($this->extensionBackupPath, $extDirPath);
335 }
336 }
337
338 /**
339 * Removes the backup folder in typo3temp
340 */
341 protected function removeBackupFolder()
342 {
343 if (!empty($this->extensionBackupPath)) {
344 GeneralUtility::rmdir($this->extensionBackupPath, true);
345 $this->extensionBackupPath = '';
346 }
347 }
348 }