Passed
Push — master ( 3a93bc...ab01fd )
by
unknown
14:58
created

FileProvider::isFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\Filelist\ContextMenu\ItemProviders;
19
20
use TYPO3\CMS\Backend\ContextMenu\ItemProviders\AbstractProvider;
21
use TYPO3\CMS\Backend\Utility\BackendUtility;
22
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
23
use TYPO3\CMS\Core\Resource\File;
24
use TYPO3\CMS\Core\Resource\Folder;
25
use TYPO3\CMS\Core\Resource\ResourceFactory;
26
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
29
/**
30
 * Provides click menu items for files and folders
31
 */
32
class FileProvider extends AbstractProvider
33
{
34
    /**
35
     * @var File|Folder|null
36
     */
37
    protected $record;
38
39
    /**
40
     * @var array
41
     */
42
    protected $itemsConfiguration = [
43
        'edit' => [
44
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.edit',
45
            'iconIdentifier' => 'actions-page-open',
46
            'callbackAction' => 'editFile'
47
        ],
48
        'rename' => [
49
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.rename',
50
            'iconIdentifier' => 'actions-edit-rename',
51
            'callbackAction' => 'renameFile'
52
        ],
53
        'upload' => [
54
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.upload',
55
            'iconIdentifier' => 'actions-edit-upload',
56
            'callbackAction' => 'uploadFile'
57
        ],
58
        'new' => [
59
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.new',
60
            'iconIdentifier' => 'actions-document-new',
61
            'callbackAction' => 'createFile'
62
        ],
63
        'newFileMount' => [
64
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.newFilemount',
65
            'iconIdentifier' => 'mimetypes-x-sys_filemounts',
66
            'callbackAction' => 'createFilemount'
67
        ],
68
        'info' => [
69
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info',
70
            'iconIdentifier' => 'actions-document-info',
71
            'callbackAction' => 'openInfoPopUp'
72
        ],
73
        'divider' => [
74
            'type' => 'divider'
75
        ],
76
        'copy' => [
77
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy',
78
            'iconIdentifier' => 'actions-edit-copy',
79
            'callbackAction' => 'copyFile'
80
        ],
81
        'copyRelease' => [
82
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy',
83
            'iconIdentifier' => 'actions-edit-copy-release',
84
            'callbackAction' => 'copyReleaseFile'
85
        ],
86
        'cut' => [
87
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cut',
88
            'iconIdentifier' => 'actions-edit-cut',
89
            'callbackAction' => 'cutFile'
90
        ],
91
        'cutRelease' => [
92
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cutrelease',
93
            'iconIdentifier' => 'actions-edit-cut-release',
94
            'callbackAction' => 'cutReleaseFile'
95
        ],
96
        'pasteInto' => [
97
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.pasteinto',
98
            'iconIdentifier' => 'actions-document-paste-into',
99
            'callbackAction' => 'pasteFileInto'
100
        ],
101
        'divider2' => [
102
            'type' => 'divider'
103
        ],
104
        'delete' => [
105
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete',
106
            'iconIdentifier' => 'actions-edit-delete',
107
            'callbackAction' => 'deleteFile'
108
        ],
109
    ];
110
111
    /**
112
     * @return bool
113
     */
114
    public function canHandle(): bool
115
    {
116
        return $this->table === 'sys_file';
117
    }
118
119
    /**
120
     * Initialize file object
121
     */
122
    protected function initialize()
123
    {
124
        parent::initialize();
125
        try {
126
            $this->record = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($this->identifier);
127
        } catch (ResourceDoesNotExistException $e) {
128
            $this->record = null;
129
        }
130
    }
131
132
    /**
133
     * Checks whether certain item can be rendered (e.g. check for disabled items or permissions)
134
     *
135
     * @param string $itemName
136
     * @param string $type
137
     * @return bool
138
     */
139
    protected function canRender(string $itemName, string $type): bool
140
    {
141
        if (in_array($type, ['divider', 'submenu'], true)) {
142
            return true;
143
        }
144
        if (in_array($itemName, $this->disabledItems, true)) {
145
            return false;
146
        }
147
        $canRender = false;
148
        switch ($itemName) {
149
            //just for files
150
            case 'edit':
151
                $canRender = $this->canBeEdited();
152
                break;
153
            case 'info':
154
                $canRender = $this->canShowInfo();
155
                break;
156
157
            //just for folders
158
            case 'upload':
159
            case 'new':
160
                $canRender = $this->canCreateNew();
161
                break;
162
            case 'newFileMount':
163
                $canRender = $this->canCreateNewFilemount();
164
                break;
165
            case 'pasteInto':
166
                $canRender = $this->canBePastedInto();
167
                break;
168
169
            //for both files and folders
170
            case 'rename':
171
                $canRender = $this->canBeRenamed();
172
                break;
173
            case 'copy':
174
                $canRender = $this->canBeCopied();
175
                break;
176
            case 'copyRelease':
177
                $canRender = $this->isRecordInClipboard('copy');
178
                break;
179
            case 'cut':
180
                $canRender = $this->canBeCut();
181
                break;
182
            case 'cutRelease':
183
                $canRender = $this->isRecordInClipboard('cut');
184
                break;
185
            case 'delete':
186
                $canRender = $this->canBeDeleted();
187
                break;
188
        }
189
        return $canRender;
190
    }
191
192
    /**
193
     * @return bool
194
     */
195
    protected function canBeEdited(): bool
196
    {
197
        return $this->isFile()
198
           && $this->record->checkActionPermission('write')
199
           && $this->record->isTextFile();
200
    }
201
202
    /**
203
     * @return bool
204
     */
205
    protected function canBeRenamed(): bool
206
    {
207
        return $this->record->checkActionPermission('rename');
0 ignored issues
show
Bug introduced by
The method checkActionPermission() does not exist on null. ( Ignorable by Annotation )

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

207
        return $this->record->/** @scrutinizer ignore-call */ checkActionPermission('rename');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
208
    }
209
210
    /**
211
     * @return bool
212
     */
213
    protected function canBeDeleted(): bool
214
    {
215
        return $this->record->checkActionPermission('delete');
216
    }
217
218
    /**
219
     * @return bool
220
     */
221
    protected function canShowInfo(): bool
222
    {
223
        return $this->isFile();
224
    }
225
226
    /**
227
     * @return bool
228
     */
229
    protected function canCreateNew(): bool
230
    {
231
        return $this->isFolder() && $this->record->checkActionPermission('write');
232
    }
233
234
    /**
235
     * New filemounts can only be created for readable folders by admins
236
     *
237
     * @return bool
238
     */
239
    protected function canCreateNewFilemount(): bool
240
    {
241
        return $this->isFolder() && $this->record->checkActionPermission('read') && $this->backendUser->isAdmin();
242
    }
243
244
    /**
245
     * @return bool
246
     */
247
    protected function canBeCopied(): bool
248
    {
249
        return $this->record->checkActionPermission('read') && $this->record->checkActionPermission('copy') && !$this->isRecordInClipboard('copy');
250
    }
251
252
    /**
253
     * @return bool
254
     */
255
    protected function canBeCut(): bool
256
    {
257
        return $this->record->checkActionPermission('move') && !$this->isRecordInClipboard('cut');
258
    }
259
260
    /**
261
     * @return bool
262
     */
263
    protected function canBePastedInto(): bool
264
    {
265
        $elArr = $this->clipboard->elFromTable('_FILE');
266
        if (empty($elArr)) {
267
            return false;
268
        }
269
        $selItem = reset($elArr);
270
        $fileOrFolderInClipBoard = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($selItem);
271
272
        return $this->isFolder()
273
            && $this->record->checkActionPermission('write')
274
            && (
275
                !$fileOrFolderInClipBoard instanceof Folder
276
                || !$fileOrFolderInClipBoard->getStorage()->isWithinFolder($fileOrFolderInClipBoard, $this->record)
277
            )
278
            && $this->isFoldersAreInTheSameRoot($fileOrFolderInClipBoard);
279
    }
280
281
    /**
282
     * Checks if folder and record are in the same filemount
283
     * Cannot copy folders between filemounts
284
     *
285
     * @param File|Folder|null $fileOrFolderInClipBoard
286
     * @return bool
287
     */
288
    protected function isFoldersAreInTheSameRoot($fileOrFolderInClipBoard): bool
289
    {
290
        return (!$fileOrFolderInClipBoard instanceof Folder)
291
            || (
292
                $this->record->getStorage()->getRootLevelFolder()->getCombinedIdentifier()
293
                == $fileOrFolderInClipBoard->getStorage()->getRootLevelFolder()->getCombinedIdentifier()
294
            );
295
    }
296
297
    /**
298
     * Checks if a file record is in the "normal" pad of the clipboard
299
     *
300
     * @param string $mode "copy", "cut" or '' for any mode
301
     * @return bool
302
     */
303
    protected function isRecordInClipboard(string $mode = ''): bool
304
    {
305
        if ($mode !== '' && !$this->record->checkActionPermission($mode)) {
306
            return false;
307
        }
308
        $isSelected = '';
309
        // Pseudo table name for use in the clipboard.
310
        $table = '_FILE';
311
        $uid = GeneralUtility::shortMD5($this->record->getCombinedIdentifier());
312
        if ($this->clipboard->current === 'normal') {
313
            $isSelected = $this->clipboard->isSelected($table, $uid);
0 ignored issues
show
Bug introduced by
$uid of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Backend\Clipbo...Clipboard::isSelected(). ( Ignorable by Annotation )

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

313
            $isSelected = $this->clipboard->isSelected($table, /** @scrutinizer ignore-type */ $uid);
Loading history...
314
        }
315
        return $mode === '' ? !empty($isSelected) : $isSelected === $mode;
316
    }
317
318
    /**
319
     * @return bool
320
     */
321
    protected function isStorageRoot(): bool
322
    {
323
        return $this->record->getIdentifier() === $this->record->getStorage()->getRootLevelFolder()->getIdentifier();
324
    }
325
326
    /**
327
     * @return bool
328
     */
329
    protected function isFile(): bool
330
    {
331
        return $this->record instanceof File;
332
    }
333
334
    /**
335
     * @return bool
336
     */
337
    protected function isFolder(): bool
338
    {
339
        return $this->record instanceof Folder;
340
    }
341
342
    /**
343
     * @param string $itemName
344
     * @return array
345
     */
346
    protected function getAdditionalAttributes(string $itemName): array
347
    {
348
        $attributes = [
349
            'data-callback-module' => 'TYPO3/CMS/Filelist/ContextMenuActions'
350
        ];
351
        if ($itemName === 'delete' && $this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
352
            $recordTitle = GeneralUtility::fixed_lgd_cs($this->record->getName(), $this->backendUser->uc['titleLen']);
353
354
            $title = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:delete');
355
            $confirmMessage = sprintf(
356
                $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.delete'),
357
                $recordTitle
358
            );
359
            if ($this->isFolder()) {
360
                $confirmMessage .= BackendUtility::referenceCount(
361
                    '_FILE',
362
                    $this->record->getIdentifier(),
363
                    ' ' . $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFolder')
364
                );
365
            } else {
366
                $confirmMessage .= BackendUtility::referenceCount(
367
                    'sys_file',
368
                    (string)$this->record->getUid(),
369
                    ' ' . $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile')
370
                );
371
            }
372
            $closeText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.cancel');
373
            $deleteText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.delete');
374
            $attributes += [
375
                'data-title' => htmlspecialchars($title),
376
                'data-message' => htmlspecialchars($confirmMessage),
377
                'data-button-close-text' => htmlspecialchars($closeText),
378
                'data-button-ok-text' => htmlspecialchars($deleteText),
379
            ];
380
        }
381
        if ($itemName === 'pasteInto' && $this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
382
            $elArr = $this->clipboard->elFromTable('_FILE');
383
            $selItem = reset($elArr);
384
            $fileOrFolderInClipBoard = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($selItem);
385
386
            $title = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste');
387
388
            $confirmMessage = sprintf(
389
                $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.'
390
                    . ($this->clipboard->currentMode() === 'copy' ? 'copy' : 'move') . '_into'),
391
                $fileOrFolderInClipBoard->getName(),
392
                $this->record->getName()
393
            );
394
            $closeText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.cancel');
395
            $okLabel = $this->clipboard->currentMode() === 'copy' ? 'copy' : 'pasteinto';
396
            $okText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . $okLabel);
397
            $attributes += [
398
                'data-title' => htmlspecialchars($title),
399
                'data-message' => htmlspecialchars($confirmMessage),
400
                'data-button-close-text' => htmlspecialchars($closeText),
401
                'data-button-ok-text' => htmlspecialchars($okText),
402
            ];
403
        }
404
405
        return $attributes;
406
    }
407
408
    /**
409
     * @return string
410
     */
411
    protected function getIdentifier(): string
412
    {
413
        return $this->record->getCombinedIdentifier();
414
    }
415
}
416