[TASK] Refactor extension upload controller
[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\Messaging\FlashMessage;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
20
21 /**
22 * Controller for handling upload of a local extension file
23 * Handles .t3x or .zip files
24 *
25 * @author Susanne Moog <typo3@susannemoog.de>
26 */
27 class UploadExtensionFileController extends AbstractController {
28
29 /**
30 * @var \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
31 * @inject
32 */
33 protected $fileHandlingUtility;
34
35 /**
36 * @var \TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility
37 * @inject
38 */
39 protected $terUtility;
40
41 /**
42 * @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility
43 * @inject
44 */
45 protected $installUtility;
46
47 /**
48 * @var string
49 */
50 protected $extensionBackupPath = '';
51
52 /**
53 * @var bool
54 */
55 protected $removeFromOriginalPath = FALSE;
56
57 /**
58 * Remove backup folder before destruction
59 */
60 public function __destruct() {
61 $this->removeBackupFolder();
62 }
63
64 /**
65 * Render upload extension form
66 *
67 * @return void
68 */
69 public function formAction() {
70 }
71
72 /**
73 * Extract an uploaded file and install the matching extension
74 *
75 * @param boolean $overwrite Overwrite existing extension if TRUE
76 * @throws ExtensionManagerException
77 * @return void
78 */
79 public function extractAction($overwrite = FALSE) {
80 $file = $_FILES['tx_extensionmanager_tools_extensionmanagerextensionmanager'];
81 $fileName = pathinfo($file['name']['extensionFile'], PATHINFO_BASENAME);
82 try {
83 // If the file name isn't valid an error will be thrown
84 $this->checkFileName($fileName);
85 if (!empty($file['tmp_name']['extensionFile'])) {
86 $tempFile = GeneralUtility::upload_to_tempfile($file['tmp_name']['extensionFile']);
87 } else {
88 throw new ExtensionManagerException(
89 'Creating temporary file failed. Check your upload_max_filesize and post_max_size limits.',
90 1342864339
91 );
92 }
93 $extensionData = $this->extractExtensionFromFile($tempFile, $fileName, $overwrite);
94 $this->activateExtension($extensionData['extKey']);
95 $this->addFlashMessage(
96 htmlspecialchars($this->translate('extensionList.uploadFlashMessage.message', array($extensionData['extKey']))),
97 htmlspecialchars($this->translate('extensionList.uploadFlashMessage.title')),
98 FlashMessage::OK
99 );
100 $this->addFlashMessage(
101 htmlspecialchars($this->translate('extensionList.installedFlashMessage.message', array($extensionData['extKey']))),
102 '',
103 FlashMessage::OK
104 );
105 } catch (\Exception $exception) {
106 $this->removeExtensionAndRestoreFromBackup($fileName);
107 $this->addFlashMessage(htmlspecialchars($exception->getMessage()), '', FlashMessage::ERROR);
108 }
109 $this->redirect('index', 'List', NULL, array(self::TRIGGER_RefreshModuleMenu => TRUE));
110 }
111
112 /**
113 * Validate the filename of an uploaded file
114 *
115 * @param string $fileName
116 * @return void
117 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
118 */
119 public function checkFileName($fileName) {
120 $extension = pathinfo($fileName, PATHINFO_EXTENSION);
121 if (empty($fileName)) {
122 throw new ExtensionManagerException('No file given.', 1342858852);
123 }
124 if ($extension !== 't3x' && $extension !== 'zip') {
125 throw new ExtensionManagerException('Wrong file format "' . $extension . '" given. Allowed formats are t3x and zip.', 1342858853);
126 }
127 }
128
129 /**
130 * Extract a given t3x or zip file
131 *
132 * @param string $uploadPath Path to existing extension file
133 * @param string $fileName Filename of the uploaded file
134 * @param bool $overwrite If true, extension will be replaced
135 * @return array Extension data
136 * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
137 */
138 public function extractExtensionFromFile($uploadPath, $fileName, $overwrite) {
139 $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION);
140 if ($fileExtension === 't3x') {
141 $extensionData = $this->getExtensionFromT3xFile($uploadPath, $overwrite);
142 } else {
143 $extensionData = $this->getExtensionFromZipFile($uploadPath, $fileName, $overwrite);
144 }
145
146 return $extensionData;
147 }
148
149 /**
150 * @param string $extensionKey
151 * @return void
152 */
153 public function activateExtension($extensionKey) {
154 $this->installUtility->install($extensionKey);
155 }
156
157 /**
158 * Extracts a given t3x file and installs the extension
159 *
160 * @param string $file Path to uploaded file
161 * @param boolean $overwrite Overwrite existing extension if TRUE
162 * @throws ExtensionManagerException
163 * @return array
164 */
165 protected function getExtensionFromT3xFile($file, $overwrite = FALSE) {
166 $fileContent = GeneralUtility::getUrl($file);
167 if (!$fileContent) {
168 throw new ExtensionManagerException('File had no or wrong content.', 1342859339);
169 }
170 $extensionData = $this->terUtility->decodeExchangeData($fileContent);
171 if (empty($extensionData['extKey'])) {
172 throw new ExtensionManagerException('Decoding the file went wrong. No extension key found', 1342864309);
173 }
174 $isExtensionAvailable = $this->installUtility->isAvailable($extensionData['extKey']);
175 if (!$overwrite && $isExtensionAvailable) {
176 throw new ExtensionManagerException($this->translate('extensionList.overwritingDisabled'), 1342864310);
177 }
178 if ($isExtensionAvailable) {
179 $this->copyExtensionFolderToTempFolder($extensionData['extKey']);
180 }
181 $this->removeFromOriginalPath = TRUE;
182 $this->fileHandlingUtility->unpackExtensionFromExtensionDataArray($extensionData);
183
184 return $extensionData;
185 }
186
187 /**
188 * Extracts a given zip file and installs the extension
189 * As there is no information about the extension key in the zip
190 * we have to use the file name to get that information
191 * filename format is expected to be extensionkey_version.zip
192 *
193 * @param string $file Path to uploaded file
194 * @param string $fileName Filename (basename) of uploaded file
195 * @param boolean $overwrite Overwrite existing extension if TRUE
196 * @return array
197 * @throws ExtensionManagerException
198 */
199 protected function getExtensionFromZipFile($file, $fileName, $overwrite = FALSE) {
200 // Remove version and extension from filename to determine the extension key
201 $extensionKey = $this->getExtensionKeyFromFileName($fileName);
202 $isExtensionAvailable = $this->installUtility->isAvailable($extensionKey);
203 if (!$overwrite && $isExtensionAvailable) {
204 throw new ExtensionManagerException('Extension is already available and overwriting is disabled.', 1342864311);
205 }
206 if ($isExtensionAvailable) {
207 $this->copyExtensionFolderToTempFolder($extensionKey);
208 }
209 $this->removeFromOriginalPath = TRUE;
210 $this->fileHandlingUtility->unzipExtensionFromFile($file, $extensionKey);
211
212 return array('extKey' => $extensionKey);
213 }
214
215 /**
216 * Removes version and file extension from filename to determine extension key
217 *
218 * @param string $fileName
219 * @return string
220 */
221 protected function getExtensionKeyFromFileName($fileName) {
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 * @return void
231 */
232 protected function copyExtensionFolderToTempFolder($extensionKey) {
233 $this->extensionBackupPath = PATH_site . 'typo3temp/' . $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 * @return void
247 */
248 protected function removeExtensionAndRestoreFromBackup($fileName) {
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 * @return void
262 */
263 protected function removeBackupFolder() {
264 if (!empty($this->extensionBackupPath)) {
265 GeneralUtility::rmdir($this->extensionBackupPath, TRUE);
266 $this->extensionBackupPath = '';
267 }
268 }
269 }