9111b8c690f9f22baacb7b7fbdc8cfe8bb13c198
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / File / FileController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Backend\Controller\File;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Backend\Clipboard\Clipboard;
21 use TYPO3\CMS\Backend\Routing\UriBuilder;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24 use TYPO3\CMS\Core\Http\HtmlResponse;
25 use TYPO3\CMS\Core\Http\JsonResponse;
26 use TYPO3\CMS\Core\Http\RedirectResponse;
27 use TYPO3\CMS\Core\Imaging\Icon;
28 use TYPO3\CMS\Core\Imaging\IconFactory;
29 use TYPO3\CMS\Core\Resource\DuplicationBehavior;
30 use TYPO3\CMS\Core\Resource\File;
31 use TYPO3\CMS\Core\Resource\Folder;
32 use TYPO3\CMS\Core\Resource\ProcessedFile;
33 use TYPO3\CMS\Core\Resource\ResourceFactory;
34 use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
35 use TYPO3\CMS\Core\Utility\GeneralUtility;
36 use TYPO3\CMS\Core\Utility\HttpUtility;
37
38 /**
39 * Gateway for TCE (TYPO3 Core Engine) file-handling through POST forms.
40 * This script serves as the file administration part of the TYPO3 Core Engine.
41 * Basically it includes two libraries which are used to manipulate files on the server.
42 */
43 class FileController
44 {
45 /**
46 * Array of file-operations.
47 *
48 * @var array
49 */
50 protected $file;
51
52 /**
53 * Clipboard operations array
54 *
55 * @var array
56 */
57 protected $CB;
58
59 /**
60 * Defines behaviour when uploading files with names that already exist; possible values are
61 * the values of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
62 *
63 * @var \TYPO3\CMS\Core\Resource\DuplicationBehavior
64 */
65 protected $overwriteExistingFiles;
66
67 /**
68 * The page where the user should be redirected after everything is done
69 *
70 * @var string
71 */
72 protected $redirect;
73
74 /**
75 * Internal, dynamic:
76 * File processor object
77 *
78 * @var ExtendedFileUtility
79 */
80 protected $fileProcessor;
81
82 /**
83 * The result array from the file processor
84 *
85 * @var array
86 */
87 protected $fileData;
88
89 /**
90 * Constructor
91 */
92 public function __construct()
93 {
94 $GLOBALS['SOBE'] = $this;
95 // @deprecated since v9, will be moved out of __construct() in v10
96 $this->init($GLOBALS['TYPO3_REQUEST']);
97 }
98
99 /**
100 * Injects the request object for the current request or subrequest
101 * As this controller goes only through the main() method, it just redirects to the given URL afterwards.
102 *
103 * @param ServerRequestInterface $request the current request
104 * @return ResponseInterface the response with the content
105 */
106 public function mainAction(ServerRequestInterface $request): ResponseInterface
107 {
108 $this->main();
109
110 BackendUtility::setUpdateSignal('updateFolderTree');
111
112 // go and edit the new created file
113 if ($request->getParsedBody()['edit'] ?? '') {
114 /** @var File $file */
115 $file = $this->fileData['newfile'][0];
116 $properties = $file->getProperties();
117 $urlParameters = [
118 'target' => $properties['storage'] . ':' . $properties['identifier']
119 ];
120 if ($this->redirect) {
121 $urlParameters['returnUrl'] = $this->redirect;
122 }
123 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
124 $this->redirect = (string)$uriBuilder->buildUriFromRoute('file_edit', $urlParameters);
125 }
126 if ($this->redirect) {
127 return new RedirectResponse(
128 GeneralUtility::locationHeaderUrl($this->redirect),
129 303
130 );
131 }
132 // empty response
133 return new HtmlResponse('');
134 }
135
136 /**
137 * Handles the actual process from within the ajaxExec function
138 * therefore, it does exactly the same as the real typo3/tce_file.php
139 * but without calling the "finish" method, thus makes it simpler to deal with the
140 * actual return value
141 *
142 * @param ServerRequestInterface $request
143 * @return ResponseInterface
144 */
145 public function processAjaxRequest(ServerRequestInterface $request): ResponseInterface
146 {
147 $this->main();
148 $errors = $this->fileProcessor->getErrorMessages();
149 if (!empty($errors)) {
150 return (new HtmlResponse('<t3err>' . implode(',', $errors) . '</t3err>'))->withStatus(500, '(AJAX)');
151 }
152 $flatResult = [];
153 foreach ($this->fileData as $action => $results) {
154 foreach ($results as $result) {
155 if (is_array($result)) {
156 foreach ($result as $subResult) {
157 $flatResult[$action][] = $this->flattenResultDataValue($subResult);
158 }
159 } else {
160 $flatResult[$action][] = $this->flattenResultDataValue($result);
161 }
162 }
163 }
164 return (new JsonResponse())->setPayload($flatResult);
165 }
166
167 /**
168 * Ajax entry point to check if a file exists in a folder
169 *
170 * @param ServerRequestInterface $request
171 * @return ResponseInterface
172 */
173 public function fileExistsInFolderAction(ServerRequestInterface $request): ResponseInterface
174 {
175 $fileName = $request->getParsedBody()['fileName'] ?? $request->getQueryParams()['fileName'] ?? null;
176 $fileTarget = $request->getParsedBody()['fileTarget'] ?? $request->getQueryParams()['fileTarget'] ?? null;
177
178 $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
179 $fileTargetObject = $fileFactory->retrieveFileOrFolderObject($fileTarget);
180 $processedFileName = $fileTargetObject->getStorage()->sanitizeFileName($fileName, $fileTargetObject);
181
182 $result = [];
183 if ($fileTargetObject->hasFile($processedFileName)) {
184 $result = $this->flattenResultDataValue($fileTargetObject->getStorage()->getFileInFolder($processedFileName, $fileTargetObject));
185 }
186 return (new JsonResponse())->setPayload($result);
187 }
188
189 /**
190 * Registering incoming data
191 *
192 * @param ServerRequestInterface $request
193 */
194 protected function init(ServerRequestInterface $request): void
195 {
196 // Set the GPvars from outside
197 $parsedBody = $request->getParsedBody();
198 $queryParams = $request->getQueryParams();
199 $this->file = $parsedBody['data'] ?? $queryParams['data'] ?? null;
200 if ($this->file === null) {
201 // This happens in clipboard mode only
202 $this->redirect = GeneralUtility::sanitizeLocalUrl($parsedBody['redirect'] ?? $queryParams['redirect'] ?? '');
203 } else {
204 $mode = key($this->file);
205 $elementKey = key($this->file[$mode]);
206 $this->redirect = GeneralUtility::sanitizeLocalUrl($this->file[$mode][$elementKey]['redirect']);
207 }
208 $this->CB = $parsedBody['CB'] ?? $queryParams['CB'] ?? null;
209
210 if (isset($this->file['rename'][0]['conflictMode'])) {
211 $conflictMode = $this->file['rename'][0]['conflictMode'];
212 unset($this->file['rename'][0]['conflictMode']);
213 $this->overwriteExistingFiles = DuplicationBehavior::cast($conflictMode);
214 } else {
215 $this->overwriteExistingFiles = DuplicationBehavior::cast($parsedBody['overwriteExistingFiles'] ?? $queryParams['overwriteExistingFiles'] ?? null);
216 }
217 $this->initClipboard();
218 $this->fileProcessor = GeneralUtility::makeInstance(ExtendedFileUtility::class);
219 }
220
221 /**
222 * Initialize the Clipboard. This will fetch the data about files to paste/delete if such an action has been sent.
223 */
224 public function initClipboard(): void
225 {
226 // Foreign class call? Method will be protected in v10, giving core freedom to move stuff around
227 $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
228 if (end($backtrace)['class'] !== __CLASS__) {
229 // @deprecated since TYPO3 v9, this method will be set to protected in v10
230 trigger_error('Method initClipboard() will be set to protected in v10. Do not call from other extension', E_USER_DEPRECATED);
231 }
232 if (is_array($this->CB)) {
233 $clipObj = GeneralUtility::makeInstance(Clipboard::class);
234 $clipObj->initializeClipboard();
235 if ($this->CB['paste']) {
236 $clipObj->setCurrentPad($this->CB['pad']);
237 $this->file = $clipObj->makePasteCmdArray_file($this->CB['paste'], $this->file);
238 }
239 if ($this->CB['delete']) {
240 $clipObj->setCurrentPad($this->CB['pad']);
241 $this->file = $clipObj->makeDeleteCmdArray_file($this->file);
242 }
243 }
244 }
245
246 /**
247 * Performing the file admin action:
248 * Initializes the objects, setting permissions, sending data to object.
249 */
250 public function main(): void
251 {
252 // Foreign class call? Method will be protected in v10, giving core freedom to move stuff around
253 $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
254 if (end($backtrace)['class'] !== __CLASS__) {
255 // @deprecated since TYPO3 v9, this method will be set to protected in v10
256 trigger_error('Method main() will be set to protected in v10. Do not call from other extension', E_USER_DEPRECATED);
257 }
258 // Initializing:
259 $this->fileProcessor->setActionPermissions();
260 $this->fileProcessor->setExistingFilesConflictMode($this->overwriteExistingFiles);
261 $this->fileProcessor->start($this->file);
262 $this->fileData = $this->fileProcessor->processData();
263 }
264
265 /**
266 * Redirecting the user after the processing has been done.
267 * Might also display error messages directly, if any.
268 *
269 * @deprecated since TYPO3 v9. Will be removed in v10.
270 */
271 public function finish()
272 {
273 trigger_error('Method finish() will be removed in v10. Do not call from other extension', E_USER_DEPRECATED);
274 BackendUtility::setUpdateSignal('updateFolderTree');
275 if ($this->redirect) {
276 HttpUtility::redirect($this->redirect);
277 }
278 }
279
280 /**
281 * Flatten result value from FileProcessor
282 *
283 * The value can be a File, Folder or boolean
284 *
285 * @param bool|File|Folder $result
286 *
287 * @return bool|string|array
288 */
289 protected function flattenResultDataValue($result)
290 {
291 if ($result instanceof File) {
292 $thumbUrl = '';
293 if (GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $result->getExtension())) {
294 $processedFile = $result->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, []);
295 if ($processedFile) {
296 $thumbUrl = $processedFile->getPublicUrl(true);
297 }
298 }
299 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
300 $result = array_merge(
301 $result->toArray(),
302 [
303 'date' => BackendUtility::date($result->getModificationTime()),
304 'icon' => $iconFactory->getIconForFileExtension($result->getExtension(), Icon::SIZE_SMALL)->render(),
305 'thumbUrl' => $thumbUrl
306 ]
307 );
308 } elseif ($result instanceof Folder) {
309 $result = $result->getIdentifier();
310 }
311
312 return $result;
313 }
314
315 /**
316 * Returns the current BE user.
317 *
318 * @return BackendUserAuthentication
319 */
320 protected function getBackendUser(): BackendUserAuthentication
321 {
322 return $GLOBALS['BE_USER'];
323 }
324 }