FileProvider   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 407
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 66
eloc 198
c 1
b 0
f 0
dl 0
loc 407
rs 3.12

20 Methods

Rating   Name   Duplication   Size   Complexity  
A canBeDeleted() 0 3 1
A getIdentifier() 0 3 1
A isFoldersAreInTheSameRoot() 0 6 2
A canBeEdited() 0 5 3
A canShowInfo() 0 3 1
A canEditMetadata() 0 7 5
A initialize() 0 7 2
A isFile() 0 3 1
A isStorageRoot() 0 3 1
A canBeCopied() 0 3 3
A canBePastedInto() 0 16 6
A isFolder() 0 3 1
A canBeCut() 0 3 2
A isRecordInClipboard() 0 13 5
A canBeRenamed() 0 3 1
A canHandle() 0 3 1
A canCreateNewFilemount() 0 3 3
B getAdditionalAttributes() 0 65 9
A canCreateNew() 0 3 2
C canRender() 0 54 16

How to fix   Complexity   

Complex Class

Complex classes like FileProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileProvider, and based on these observations, apply Extract Interface, too.

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.editcontent',
45
            'iconIdentifier' => 'actions-page-open',
46
            'callbackAction' => 'editFile'
47
        ],
48
        'editMetadata' => [
49
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editMetadata',
50
            'iconIdentifier' => 'actions-open',
51
            'callbackAction' => 'editMetadata'
52
        ],
53
        'rename' => [
54
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.rename',
55
            'iconIdentifier' => 'actions-edit-rename',
56
            'callbackAction' => 'renameFile'
57
        ],
58
        'upload' => [
59
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.upload',
60
            'iconIdentifier' => 'actions-edit-upload',
61
            'callbackAction' => 'uploadFile'
62
        ],
63
        'new' => [
64
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.new',
65
            'iconIdentifier' => 'actions-document-new',
66
            'callbackAction' => 'createFile'
67
        ],
68
        'newFileMount' => [
69
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.newFilemount',
70
            'iconIdentifier' => 'mimetypes-x-sys_filemounts',
71
            'callbackAction' => 'createFilemount'
72
        ],
73
        'info' => [
74
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info',
75
            'iconIdentifier' => 'actions-document-info',
76
            'callbackAction' => 'openInfoPopUp'
77
        ],
78
        'divider' => [
79
            'type' => 'divider'
80
        ],
81
        'copy' => [
82
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy',
83
            'iconIdentifier' => 'actions-edit-copy',
84
            'callbackAction' => 'copyFile'
85
        ],
86
        'copyRelease' => [
87
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy',
88
            'iconIdentifier' => 'actions-edit-copy-release',
89
            'callbackAction' => 'copyReleaseFile'
90
        ],
91
        'cut' => [
92
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cut',
93
            'iconIdentifier' => 'actions-edit-cut',
94
            'callbackAction' => 'cutFile'
95
        ],
96
        'cutRelease' => [
97
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cutrelease',
98
            'iconIdentifier' => 'actions-edit-cut-release',
99
            'callbackAction' => 'cutReleaseFile'
100
        ],
101
        'pasteInto' => [
102
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.pasteinto',
103
            'iconIdentifier' => 'actions-document-paste-into',
104
            'callbackAction' => 'pasteFileInto'
105
        ],
106
        'divider2' => [
107
            'type' => 'divider'
108
        ],
109
        'delete' => [
110
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete',
111
            'iconIdentifier' => 'actions-edit-delete',
112
            'callbackAction' => 'deleteFile'
113
        ],
114
    ];
115
116
    /**
117
     * @return bool
118
     */
119
    public function canHandle(): bool
120
    {
121
        return $this->table === 'sys_file';
122
    }
123
124
    /**
125
     * Initialize file object
126
     */
127
    protected function initialize()
128
    {
129
        parent::initialize();
130
        try {
131
            $this->record = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($this->identifier);
132
        } catch (ResourceDoesNotExistException $e) {
133
            $this->record = null;
134
        }
135
    }
136
137
    /**
138
     * Checks whether certain item can be rendered (e.g. check for disabled items or permissions)
139
     *
140
     * @param string $itemName
141
     * @param string $type
142
     * @return bool
143
     */
144
    protected function canRender(string $itemName, string $type): bool
145
    {
146
        if (in_array($type, ['divider', 'submenu'], true)) {
147
            return true;
148
        }
149
        if (in_array($itemName, $this->disabledItems, true)) {
150
            return false;
151
        }
152
        $canRender = false;
153
        switch ($itemName) {
154
            //just for files
155
            case 'edit':
156
                $canRender = $this->canBeEdited();
157
                break;
158
            case 'editMetadata':
159
                $canRender = $this->canEditMetadata();
160
                break;
161
            case 'info':
162
                $canRender = $this->canShowInfo();
163
                break;
164
165
            //just for folders
166
            case 'upload':
167
            case 'new':
168
                $canRender = $this->canCreateNew();
169
                break;
170
            case 'newFileMount':
171
                $canRender = $this->canCreateNewFilemount();
172
                break;
173
            case 'pasteInto':
174
                $canRender = $this->canBePastedInto();
175
                break;
176
177
            //for both files and folders
178
            case 'rename':
179
                $canRender = $this->canBeRenamed();
180
                break;
181
            case 'copy':
182
                $canRender = $this->canBeCopied();
183
                break;
184
            case 'copyRelease':
185
                $canRender = $this->isRecordInClipboard('copy');
186
                break;
187
            case 'cut':
188
                $canRender = $this->canBeCut();
189
                break;
190
            case 'cutRelease':
191
                $canRender = $this->isRecordInClipboard('cut');
192
                break;
193
            case 'delete':
194
                $canRender = $this->canBeDeleted();
195
                break;
196
        }
197
        return $canRender;
198
    }
199
200
    /**
201
     * @return bool
202
     */
203
    protected function canBeEdited(): bool
204
    {
205
        return $this->isFile()
206
           && $this->record->checkActionPermission('write')
207
           && $this->record->isTextFile();
208
    }
209
210
    /**
211
     * @return bool
212
     */
213
    protected function canEditMetadata(): bool
214
    {
215
        return $this->isFile()
216
           && $this->record->isIndexed()
217
           && $this->record->checkActionPermission('editMeta')
218
           && $this->record->getMetaData()->offsetExists('uid')
219
           && $this->backendUser->check('tables_modify', 'sys_file_metadata');
220
    }
221
222
    /**
223
     * @return bool
224
     */
225
    protected function canBeRenamed(): bool
226
    {
227
        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

227
        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...
228
    }
229
230
    /**
231
     * @return bool
232
     */
233
    protected function canBeDeleted(): bool
234
    {
235
        return $this->record->checkActionPermission('delete');
236
    }
237
238
    /**
239
     * @return bool
240
     */
241
    protected function canShowInfo(): bool
242
    {
243
        return $this->isFile();
244
    }
245
246
    /**
247
     * @return bool
248
     */
249
    protected function canCreateNew(): bool
250
    {
251
        return $this->isFolder() && $this->record->checkActionPermission('write');
252
    }
253
254
    /**
255
     * New filemounts can only be created for readable folders by admins
256
     *
257
     * @return bool
258
     */
259
    protected function canCreateNewFilemount(): bool
260
    {
261
        return $this->isFolder() && $this->record->checkActionPermission('read') && $this->backendUser->isAdmin();
262
    }
263
264
    /**
265
     * @return bool
266
     */
267
    protected function canBeCopied(): bool
268
    {
269
        return $this->record->checkActionPermission('read') && $this->record->checkActionPermission('copy') && !$this->isRecordInClipboard('copy');
270
    }
271
272
    /**
273
     * @return bool
274
     */
275
    protected function canBeCut(): bool
276
    {
277
        return $this->record->checkActionPermission('move') && !$this->isRecordInClipboard('cut');
278
    }
279
280
    /**
281
     * @return bool
282
     */
283
    protected function canBePastedInto(): bool
284
    {
285
        $elArr = $this->clipboard->elFromTable('_FILE');
286
        if (empty($elArr)) {
287
            return false;
288
        }
289
        $selItem = reset($elArr);
290
        $fileOrFolderInClipBoard = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($selItem);
291
292
        return $this->isFolder()
293
            && $this->record->checkActionPermission('write')
294
            && (
295
                !$fileOrFolderInClipBoard instanceof Folder
296
                || !$fileOrFolderInClipBoard->getStorage()->isWithinFolder($fileOrFolderInClipBoard, $this->record)
297
            )
298
            && $this->isFoldersAreInTheSameRoot($fileOrFolderInClipBoard);
299
    }
300
301
    /**
302
     * Checks if folder and record are in the same filemount
303
     * Cannot copy folders between filemounts
304
     *
305
     * @param File|Folder|null $fileOrFolderInClipBoard
306
     * @return bool
307
     */
308
    protected function isFoldersAreInTheSameRoot($fileOrFolderInClipBoard): bool
309
    {
310
        return (!$fileOrFolderInClipBoard instanceof Folder)
311
            || (
312
                $this->record->getStorage()->getRootLevelFolder()->getCombinedIdentifier()
313
                == $fileOrFolderInClipBoard->getStorage()->getRootLevelFolder()->getCombinedIdentifier()
314
            );
315
    }
316
317
    /**
318
     * Checks if a file record is in the "normal" pad of the clipboard
319
     *
320
     * @param string $mode "copy", "cut" or '' for any mode
321
     * @return bool
322
     */
323
    protected function isRecordInClipboard(string $mode = ''): bool
324
    {
325
        if ($mode !== '' && !$this->record->checkActionPermission($mode)) {
326
            return false;
327
        }
328
        $isSelected = '';
329
        // Pseudo table name for use in the clipboard.
330
        $table = '_FILE';
331
        $uid = GeneralUtility::shortMD5($this->record->getCombinedIdentifier());
332
        if ($this->clipboard->current === 'normal') {
333
            $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

333
            $isSelected = $this->clipboard->isSelected($table, /** @scrutinizer ignore-type */ $uid);
Loading history...
334
        }
335
        return $mode === '' ? !empty($isSelected) : $isSelected === $mode;
336
    }
337
338
    /**
339
     * @return bool
340
     */
341
    protected function isStorageRoot(): bool
342
    {
343
        return $this->record->getIdentifier() === $this->record->getStorage()->getRootLevelFolder()->getIdentifier();
344
    }
345
346
    /**
347
     * @return bool
348
     */
349
    protected function isFile(): bool
350
    {
351
        return $this->record instanceof File;
352
    }
353
354
    /**
355
     * @return bool
356
     */
357
    protected function isFolder(): bool
358
    {
359
        return $this->record instanceof Folder;
360
    }
361
362
    /**
363
     * @param string $itemName
364
     * @return array
365
     */
366
    protected function getAdditionalAttributes(string $itemName): array
367
    {
368
        $attributes = [
369
            'data-callback-module' => 'TYPO3/CMS/Filelist/ContextMenuActions'
370
        ];
371
        if ($itemName === 'delete' && $this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
372
            $recordTitle = GeneralUtility::fixed_lgd_cs($this->record->getName(), $this->backendUser->uc['titleLen']);
373
374
            $title = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:delete');
375
            $confirmMessage = sprintf(
376
                $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.delete'),
377
                $recordTitle
378
            );
379
            if ($this->isFolder()) {
380
                $confirmMessage .= BackendUtility::referenceCount(
381
                    '_FILE',
382
                    $this->record->getIdentifier(),
383
                    ' ' . $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFolder')
384
                );
385
            } else {
386
                $confirmMessage .= BackendUtility::referenceCount(
387
                    'sys_file',
388
                    (string)$this->record->getUid(),
389
                    ' ' . $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile')
390
                );
391
            }
392
            $closeText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.cancel');
393
            $deleteText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.delete');
394
            $attributes += [
395
                'data-title' => htmlspecialchars($title),
396
                'data-message' => htmlspecialchars($confirmMessage),
397
                'data-button-close-text' => htmlspecialchars($closeText),
398
                'data-button-ok-text' => htmlspecialchars($deleteText),
399
            ];
400
        }
401
        if ($itemName === 'pasteInto' && $this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
402
            $elArr = $this->clipboard->elFromTable('_FILE');
403
            $selItem = reset($elArr);
404
            $fileOrFolderInClipBoard = GeneralUtility::makeInstance(ResourceFactory::class)->retrieveFileOrFolderObject($selItem);
405
406
            $title = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste');
407
408
            $confirmMessage = sprintf(
409
                $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.'
410
                    . ($this->clipboard->currentMode() === 'copy' ? 'copy' : 'move') . '_into'),
411
                $fileOrFolderInClipBoard->getName(),
412
                $this->record->getName()
413
            );
414
            $closeText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.cancel');
415
            $okLabel = $this->clipboard->currentMode() === 'copy' ? 'copy' : 'pasteinto';
416
            $okText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . $okLabel);
417
            $attributes += [
418
                'data-title' => htmlspecialchars($title),
419
                'data-message' => htmlspecialchars($confirmMessage),
420
                'data-button-close-text' => htmlspecialchars($closeText),
421
                'data-button-ok-text' => htmlspecialchars($okText),
422
            ];
423
        }
424
        if ($itemName === 'editMetadata') {
425
            $attributes += [
426
                'data-metadata-uid' => htmlspecialchars((string)$this->record->getMetaData()->offsetGet('uid'))
0 ignored issues
show
Bug introduced by
The method getMetaData() does not exist on TYPO3\CMS\Core\Resource\Folder. ( Ignorable by Annotation )

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

426
                'data-metadata-uid' => htmlspecialchars((string)$this->record->/** @scrutinizer ignore-call */ getMetaData()->offsetGet('uid'))

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...
427
            ];
428
        }
429
430
        return $attributes;
431
    }
432
433
    /**
434
     * @return string
435
     */
436
    protected function getIdentifier(): string
437
    {
438
        return $this->record->getCombinedIdentifier();
439
    }
440
}
441