Passed
Push — master ( a15053...89cce6 )
by
unknown
18:50
created

FileController::init()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 26
rs 9.6333
c 0
b 0
f 0
cc 4
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
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
namespace TYPO3\CMS\Backend\Controller\File;
19
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
use TYPO3\CMS\Backend\Clipboard\Clipboard;
23
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
24
use TYPO3\CMS\Backend\Routing\UriBuilder;
25
use TYPO3\CMS\Backend\Utility\BackendUtility;
26
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27
use TYPO3\CMS\Core\Http\HtmlResponse;
28
use TYPO3\CMS\Core\Http\JsonResponse;
29
use TYPO3\CMS\Core\Http\RedirectResponse;
30
use TYPO3\CMS\Core\Imaging\Icon;
31
use TYPO3\CMS\Core\Imaging\IconFactory;
32
use TYPO3\CMS\Core\Messaging\AbstractMessage;
33
use TYPO3\CMS\Core\Messaging\FlashMessageService;
34
use TYPO3\CMS\Core\Resource\DuplicationBehavior;
35
use TYPO3\CMS\Core\Resource\File;
36
use TYPO3\CMS\Core\Resource\Folder;
37
use TYPO3\CMS\Core\Resource\ProcessedFile;
38
use TYPO3\CMS\Core\Resource\ResourceFactory;
39
use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
40
use TYPO3\CMS\Core\Utility\GeneralUtility;
41
use TYPO3\CMS\Core\Utility\PathUtility;
42
43
/**
44
 * Gateway for TCE (TYPO3 Core Engine) file-handling through POST forms.
45
 * This script serves as the file administration part of the TYPO3 Core Engine.
46
 * Basically it includes two libraries which are used to manipulate files on the server.
47
 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
48
 */
49
class FileController
50
{
51
    /**
52
     * Array of file-operations.
53
     *
54
     * @var array
55
     */
56
    protected $file;
57
58
    /**
59
     * Clipboard operations array
60
     *
61
     * @var array
62
     */
63
    protected $CB;
64
65
    /**
66
     * Defines behaviour when uploading files with names that already exist; possible values are
67
     * the values of the \TYPO3\CMS\Core\Resource\DuplicationBehavior enumeration
68
     *
69
     * @var \TYPO3\CMS\Core\Resource\DuplicationBehavior
70
     */
71
    protected $overwriteExistingFiles;
72
73
    /**
74
     * The page where the user should be redirected after everything is done
75
     *
76
     * @var string
77
     */
78
    protected $redirect;
79
80
    /**
81
     * Internal, dynamic:
82
     * File processor object
83
     *
84
     * @var ExtendedFileUtility
85
     */
86
    protected $fileProcessor;
87
88
    /**
89
     * The result array from the file processor
90
     *
91
     * @var array
92
     */
93
    protected $fileData;
94
95
    /**
96
     * Injects the request object for the current request or subrequest
97
     * As this controller goes only through the main() method, it just redirects to the given URL afterwards.
98
     *
99
     * @param ServerRequestInterface $request the current request
100
     * @return ResponseInterface the response with the content
101
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
102
     */
103
    public function mainAction(ServerRequestInterface $request): ResponseInterface
104
    {
105
        $this->init($request);
106
        $this->main();
107
108
        BackendUtility::setUpdateSignal('updateFolderTree');
109
110
        // go and edit the new created file
111
        if ($request->getParsedBody()['edit'] ?? '') {
112
            /** @var File $file */
113
            $file = $this->fileData['newfile'][0];
114
            if ($file !== null) {
115
                $this->redirect = $this->getFileEditRedirect($file) ?? $this->redirect;
116
            }
117
        }
118
        if ($this->redirect) {
119
            return new RedirectResponse(
120
                GeneralUtility::locationHeaderUrl($this->redirect),
121
                303
122
            );
123
        }
124
        // empty response
125
        return new HtmlResponse('');
126
    }
127
128
    /**
129
     * Handles the actual process from within the ajaxExec function
130
     * therefore, it does exactly the same as the real typo3/tce_file.php.
131
     *
132
     * @param ServerRequestInterface $request
133
     * @return ResponseInterface
134
     */
135
    public function processAjaxRequest(ServerRequestInterface $request): ResponseInterface
136
    {
137
        $this->init($request);
138
        $this->main();
139
        $includeMessages = (bool)($request->getQueryParams()['includeMessages'] ?? false);
140
        $errors = $this->fileProcessor->getErrorMessages();
141
        if (!$includeMessages && !empty($errors)) {
142
            return (new HtmlResponse('<t3err>' . implode(',', $errors) . '</t3err>'))->withStatus(500, '(AJAX)');
143
        }
144
        $flatResult = [];
145
        foreach ($this->fileData as $action => $results) {
146
            foreach ($results as $result) {
147
                if (is_array($result)) {
148
                    foreach ($result as $subResult) {
149
                        $flatResult[$action][] = $this->flattenResultDataValue($subResult);
150
                    }
151
                } else {
152
                    $flatResult[$action][] = $this->flattenResultDataValue($result);
153
                }
154
            }
155
        }
156
157
        // Used in the FileStorageTree when moving / copying folders
158
        if ($includeMessages) {
159
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
160
            $messages = $flashMessageService->getMessageQueueByIdentifier()->getAllMessagesAndFlush();
161
            if (!empty($messages)) {
162
                foreach ($messages as $message) {
163
                    $flatResult['messages'][] = [
164
                        'title'    => $message->getTitle(),
165
                        'message'  => $message->getMessage(),
166
                        'severity' => $message->getSeverity()
167
                    ];
168
                    if ($message->getSeverity() === AbstractMessage::ERROR) {
169
                        $flatResult['hasErrors'] = true;
170
                    }
171
                }
172
            }
173
        }
174
        return (new JsonResponse())->setPayload($flatResult);
175
    }
176
177
    /**
178
     * Ajax entry point to check if a file exists in a folder
179
     *
180
     * @param ServerRequestInterface $request
181
     * @return ResponseInterface
182
     */
183
    public function fileExistsInFolderAction(ServerRequestInterface $request): ResponseInterface
184
    {
185
        $this->init($request);
186
        $fileName = $request->getParsedBody()['fileName'] ?? $request->getQueryParams()['fileName'] ?? null;
187
        $fileTarget = $request->getParsedBody()['fileTarget'] ?? $request->getQueryParams()['fileTarget'] ?? null;
188
189
        $fileFactory = GeneralUtility::makeInstance(ResourceFactory::class);
190
        $fileTargetObject = $fileFactory->retrieveFileOrFolderObject($fileTarget);
191
        $processedFileName = $fileTargetObject->getStorage()->sanitizeFileName($fileName, $fileTargetObject);
192
193
        $result = [];
194
        if ($fileTargetObject->hasFile($processedFileName)) {
195
            $result = $this->flattenResultDataValue($fileTargetObject->getStorage()->getFileInFolder($processedFileName, $fileTargetObject));
196
        }
197
        return (new JsonResponse())->setPayload($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean and string; however, parameter $data of TYPO3\CMS\Core\Http\JsonResponse::setPayload() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

197
        return (new JsonResponse())->setPayload(/** @scrutinizer ignore-type */ $result);
Loading history...
198
    }
199
200
    /**
201
     * Registering incoming data
202
     *
203
     * @param ServerRequestInterface $request
204
     */
205
    protected function init(ServerRequestInterface $request): void
206
    {
207
        // Set the GPvars from outside
208
        $parsedBody = $request->getParsedBody();
209
        $queryParams = $request->getQueryParams();
210
        $this->file = $parsedBody['data'] ?? $queryParams['data'] ?? null;
211
        $redirectUrl = $parsedBody['redirect'] ?? $queryParams['redirect'] ?? '';
212
        if ($this->file === null || !empty($redirectUrl)) {
213
            // This in clipboard mode or when a new folder is created
214
            $this->redirect = GeneralUtility::sanitizeLocalUrl($redirectUrl);
215
        } else {
216
            $mode = key($this->file);
217
            $elementKey = key($this->file[$mode]);
218
            $this->redirect = GeneralUtility::sanitizeLocalUrl($this->file[$mode][$elementKey]['redirect']);
219
        }
220
        $this->CB = $parsedBody['CB'] ?? $queryParams['CB'] ?? null;
221
222
        if (isset($this->file['rename'][0]['conflictMode'])) {
223
            $conflictMode = $this->file['rename'][0]['conflictMode'];
224
            unset($this->file['rename'][0]['conflictMode']);
225
            $this->overwriteExistingFiles = DuplicationBehavior::cast($conflictMode);
226
        } else {
227
            $this->overwriteExistingFiles = DuplicationBehavior::cast($parsedBody['overwriteExistingFiles'] ?? $queryParams['overwriteExistingFiles'] ?? null);
228
        }
229
        $this->initClipboard();
230
        $this->fileProcessor = GeneralUtility::makeInstance(ExtendedFileUtility::class);
231
    }
232
233
    /**
234
     * Initialize the Clipboard. This will fetch the data about files to paste/delete if such an action has been sent.
235
     */
236
    protected function initClipboard(): void
237
    {
238
        if (is_array($this->CB)) {
0 ignored issues
show
introduced by
The condition is_array($this->CB) is always true.
Loading history...
239
            $clipObj = GeneralUtility::makeInstance(Clipboard::class);
240
            $clipObj->initializeClipboard();
241
            if ($this->CB['paste']) {
242
                $clipObj->setCurrentPad($this->CB['pad']);
243
                $this->file = $clipObj->makePasteCmdArray_file($this->CB['paste'], $this->file);
244
            }
245
            if ($this->CB['delete']) {
246
                $clipObj->setCurrentPad($this->CB['pad']);
247
                $this->file = $clipObj->makeDeleteCmdArray_file($this->file);
248
            }
249
        }
250
    }
251
252
    /**
253
     * Performing the file admin action:
254
     * Initializes the objects, setting permissions, sending data to object.
255
     */
256
    protected function main(): void
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
     * Gets URI to be used for editing given file (if file extension is defined in textfile_ext)
267
     *
268
     * @param File $file to be edited
269
     * @return string|null URI to be redirected to
270
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
271
     */
272
    protected function getFileEditRedirect(File $file): ?string
273
    {
274
        if (!$file->isTextFile()) {
275
            return null;
276
        }
277
        $properties = $file->getProperties();
278
        $urlParameters = [
279
            'target' =>  $properties['storage'] . ':' . $properties['identifier']
280
        ];
281
        if ($this->redirect) {
282
            $urlParameters['returnUrl'] = $this->redirect;
283
        }
284
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
285
        try {
286
            return (string)$uriBuilder->buildUriFromRoute('file_edit', $urlParameters);
287
        } catch (RouteNotFoundException $exception) {
288
            // no route for editing files available
289
            return '';
290
        }
291
    }
292
293
    /**
294
     * Flatten result value from FileProcessor
295
     *
296
     * The value can be a File, Folder or boolean
297
     *
298
     * @param bool|File|Folder $result
299
     *
300
     * @return bool|string|array
301
     */
302
    protected function flattenResultDataValue($result)
303
    {
304
        if ($result instanceof File) {
305
            $thumbUrl = '';
306
            if ($result->isImage()) {
307
                $processedFile = $result->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, []);
308
                if ($processedFile) {
0 ignored issues
show
introduced by
$processedFile is of type TYPO3\CMS\Core\Resource\ProcessedFile, thus it always evaluated to true.
Loading history...
309
                    $thumbUrl = PathUtility::getAbsoluteWebPath($processedFile->getPublicUrl());
310
                }
311
            }
312
            $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
313
            $result = array_merge(
314
                $result->toArray(),
315
                [
316
                    'date' => BackendUtility::date($result->getModificationTime()),
317
                    'icon' => $iconFactory->getIconForFileExtension($result->getExtension(), Icon::SIZE_SMALL)->render(),
318
                    'thumbUrl' => $thumbUrl
319
                ]
320
            );
321
        } elseif ($result instanceof Folder) {
322
            $result = $result->getIdentifier();
323
        }
324
325
        return $result;
326
    }
327
328
    /**
329
     * Returns the current BE user.
330
     *
331
     * @return BackendUserAuthentication
332
     */
333
    protected function getBackendUser(): BackendUserAuthentication
334
    {
335
        return $GLOBALS['BE_USER'];
336
    }
337
}
338