Passed
Push — master ( 14d509...cdc6b7 )
by
unknown
15:15
created

FileList::getTable()   F

Complexity

Conditions 17
Paths 381

Size

Total Lines 120
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 73
c 0
b 0
f 0
dl 0
loc 120
rs 2.1208
cc 17
nc 381
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Filelist;
17
18
use TYPO3\CMS\Backend\Clipboard\Clipboard;
19
use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
20
use TYPO3\CMS\Backend\Routing\UriBuilder;
21
use TYPO3\CMS\Backend\Utility\BackendUtility;
22
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
23
use TYPO3\CMS\Core\Database\ConnectionPool;
24
use TYPO3\CMS\Core\Imaging\Icon;
25
use TYPO3\CMS\Core\Imaging\IconFactory;
26
use TYPO3\CMS\Core\Localization\LanguageService;
27
use TYPO3\CMS\Core\Resource\AbstractFile;
28
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
29
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
30
use TYPO3\CMS\Core\Resource\File;
31
use TYPO3\CMS\Core\Resource\FileInterface;
32
use TYPO3\CMS\Core\Resource\Folder;
33
use TYPO3\CMS\Core\Resource\FolderInterface;
34
use TYPO3\CMS\Core\Resource\InaccessibleFolder;
35
use TYPO3\CMS\Core\Resource\ProcessedFile;
36
use TYPO3\CMS\Core\Resource\ResourceFactory;
37
use TYPO3\CMS\Core\Resource\Search\FileSearchDemand;
38
use TYPO3\CMS\Core\Resource\Utility\ListUtility;
39
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
40
use TYPO3\CMS\Core\Utility\GeneralUtility;
41
use TYPO3\CMS\Core\Utility\MathUtility;
42
use TYPO3\CMS\Core\Utility\PathUtility;
43
use TYPO3\CMS\Filelist\Configuration\ThumbnailConfiguration;
44
45
/**
46
 * Class for rendering of File>Filelist (basically used in FileListController)
47
 * @see \TYPO3\CMS\Filelist\Controller\FileListController
48
 * @internal this is a concrete TYPO3 controller implementation and solely used for EXT:filelist and not part of TYPO3's Core API.
49
 */
50
class FileList
51
{
52
    /**
53
     * Default Max items shown
54
     *
55
     * @var int
56
     */
57
    public $iLimit = 40;
58
59
    /**
60
     * Thumbnails on records containing files (pictures)
61
     *
62
     * @var bool
63
     */
64
    public $thumbs = false;
65
66
    /**
67
     * Space icon used for alignment when no button is available
68
     *
69
     * @var string
70
     */
71
    public $spaceIcon;
72
73
    /**
74
     * Max length of strings
75
     *
76
     * @var int
77
     */
78
    public $fixedL = 30;
79
80
    /**
81
     * The field to sort by
82
     *
83
     * @var string
84
     */
85
    public $sort = '';
86
87
    /**
88
     * Reverse sorting flag
89
     *
90
     * @var bool
91
     */
92
    public $sortRev = true;
93
94
    /**
95
     * @var int
96
     */
97
    public $firstElementNumber = 0;
98
99
    /**
100
     * @var int
101
     */
102
    public $totalbytes = 0;
103
104
    /**
105
     * This could be set to the total number of items. Used by the fwd_rew_navigation...
106
     *
107
     * @var int
108
     */
109
    public $totalItems = 0;
110
111
    /**
112
     * Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column.
113
     *
114
     * @var array
115
     */
116
    public $fieldArray = [];
117
118
    /**
119
     * Counter increased for each element. Used to index elements for the JavaScript-code that transfers to the clipboard
120
     *
121
     * @var int
122
     */
123
    public $counter = 0;
124
125
    /**
126
     * Counting the elements no matter what
127
     *
128
     * @var int
129
     */
130
    public $eCounter = 0;
131
132
    /**
133
     * @var TranslationConfigurationProvider
134
     */
135
    public $translateTools;
136
137
    /**
138
     * Keys are fieldnames and values are td-css-classes to add in addElement();
139
     *
140
     * @var array
141
     */
142
    public $addElement_tdCssClass = [
143
        '_CONTROL_' => 'col-control',
144
        '_CLIPBOARD_' => 'col-clipboard',
145
        'file' => 'col-title col-responsive',
146
        '_LOCALIZATION_' => 'col-localizationa',
147
    ];
148
149
    /**
150
     * @var Folder
151
     */
152
    protected $folderObject;
153
154
    /**
155
     * @var array
156
     */
157
    public $CBnames = [];
158
159
    /**
160
     * @var Clipboard $clipObj
161
     */
162
    public $clipObj;
163
164
    /**
165
     * @var ResourceFactory
166
     */
167
    protected $resourceFactory;
168
169
    /**
170
     * @var IconFactory
171
     */
172
    protected $iconFactory;
173
174
    /**
175
     * @var int
176
     */
177
    protected $id = 0;
178
179
    /**
180
     * @var ThumbnailConfiguration
181
     */
182
    protected $thumbnailConfiguration;
183
184
    /**
185
     * @var UriBuilder
186
     */
187
    protected $uriBuilder;
188
189
    protected string $searchTerm = '';
190
191
    public function __construct()
192
    {
193
        // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->fixedL)
194
        $this->fixedL = max($this->fixedL, $this->getBackendUser()->uc['titleLen'] ?? 1);
195
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
196
        $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
197
        $this->thumbnailConfiguration = GeneralUtility::makeInstance(ThumbnailConfiguration::class);
198
        $this->iLimit = MathUtility::forceIntegerInRange(
199
            $this->getBackendUser()->getTSConfig()['options.']['file_list.']['filesPerPage'] ?? $this->iLimit,
200
            1
201
        );
202
        // Create clipboard object and initialize that
203
        $this->clipObj = GeneralUtility::makeInstance(Clipboard::class);
204
        $this->clipObj->fileMode = true;
205
        $this->clipObj->initializeClipboard();
206
        $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
207
        $this->getLanguageService()->includeLLFile('EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf');
208
        $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_common.xlf');
209
        $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
210
        $this->spaceIcon = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
211
    }
212
213
    /**
214
     * Initialization of class
215
     *
216
     * @param Folder $folderObject The folder to work on
217
     * @param int $pointer Pointer
218
     * @param string $sort Sorting column
219
     * @param bool $sortRev Sorting direction
220
     * @param bool $clipBoard
221
     */
222
    public function start(Folder $folderObject, $pointer, $sort, $sortRev, $clipBoard = false)
223
    {
224
        $this->folderObject = $folderObject;
225
        $this->counter = 0;
226
        $this->totalbytes = 0;
227
        $this->sort = $sort;
228
        $this->sortRev = $sortRev;
229
        $this->firstElementNumber = $pointer;
230
        // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
231
        $rowlist = 'file,_LOCALIZATION_,_CONTROL_,fileext,tstamp,size,rw,_REF_';
232
        if ($clipBoard) {
233
            $rowlist = str_replace('_CONTROL_,', '_CONTROL_,_CLIPBOARD_,', $rowlist);
234
        }
235
        $this->fieldArray = explode(',', $rowlist);
236
    }
237
238
    /**
239
     * Wrapping input string in a link with clipboard command.
240
     *
241
     * @param string $string String to be linked - must be htmlspecialchar'ed / prepared before.
242
     * @param string $cmd "cmd" value
243
     * @param string $warning Warning for JS confirm message
244
     * @return string Linked string
245
     */
246
    public function linkClipboardHeaderIcon($string, $cmd, $warning = '')
247
    {
248
        if ($warning) {
249
            $attributes['class'] = 'btn btn-default t3js-modal-trigger';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributes = array(); before regardless.
Loading history...
250
            $attributes['data-severity'] = 'warning';
251
            $attributes['data-bs-content'] = $warning;
252
            $attributes['data-event-name'] = 'filelist:clipboard:cmd';
253
            $attributes['data-event-payload'] = $cmd;
254
        } else {
255
            $attributes['class'] = 'btn btn-default';
256
            $attributes['data-filelist-clipboard-cmd'] = $cmd;
257
        }
258
259
        return '<a href="#" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $string . '</a>';
260
    }
261
262
    /**
263
     * Returns a table with directories and files listed.
264
     *
265
     * @param FileSearchDemand|null $searchDemand
266
     * @return string HTML-table
267
     */
268
    public function getTable(?FileSearchDemand $searchDemand = null): string
269
    {
270
        if ($searchDemand !== null) {
271
            // Search currently only works for files
272
            $folders = [];
273
            // Find files by the given search demand
274
            $files = iterator_to_array($this->folderObject->searchFiles($searchDemand));
275
            // @todo Currently files, which got deleted in the file system, are still found.
276
            //       Therefore we have to ask their parent folder if it still contains the file.
277
            $files = array_filter($files, static function (FileInterface $file): bool {
278
                try {
279
                    if ($file->getParentFolder()->hasFile($file->getName())) {
280
                        return true;
281
                    }
282
                } catch (ResourceDoesNotExistException $e) {
283
                    // Nothing to do, file does not longer exist in folder
284
                }
285
                return false;
286
            });
287
288
            // @todo We have to manually slice the search result, since it may
289
            //       contain invalid files, which were manually filtered out above.
290
            //       This should be fixed, so we can use the $firstResult and $maxResults
291
            //       properties of the search demand directly.
292
            $this->totalItems = count($files);
293
            $filesNum = $this->firstElementNumber + $this->iLimit > $this->totalItems
294
                ? $this->totalItems - $this->firstElementNumber
295
                : $this->iLimit;
296
            $files = array_slice($files, $this->firstElementNumber, $filesNum);
297
298
            // Add special "Path" field for the search result
299
            array_unshift($this->fieldArray, '_PATH_');
300
            // Add search term so it can be added to return urls
301
            $this->searchTerm = $searchDemand->getSearchTerm() ?? '';
302
        } else {
303
            // @todo use folder methods directly when they support filters
304
            $storage = $this->folderObject->getStorage();
305
            $storage->resetFileAndFolderNameFiltersToDefault();
306
307
            // Only render the contents of a browsable storage
308
            if (!$this->folderObject->getStorage()->isBrowsable()) {
309
                return '';
310
            }
311
            try {
312
                $foldersCount = $storage->countFoldersInFolder($this->folderObject);
313
                $filesCount = $storage->countFilesInFolder($this->folderObject);
314
            } catch (InsufficientFolderAccessPermissionsException $e) {
315
                $foldersCount = 0;
316
                $filesCount = 0;
317
            }
318
319
            if ($foldersCount <= $this->firstElementNumber) {
320
                $foldersFrom = false;
321
                $foldersNum = false;
322
            } else {
323
                $foldersFrom = $this->firstElementNumber;
324
                if ($this->firstElementNumber + $this->iLimit > $foldersCount) {
325
                    $foldersNum = $foldersCount - $this->firstElementNumber;
326
                } else {
327
                    $foldersNum = $this->iLimit;
328
                }
329
            }
330
            if ($foldersCount >= $this->firstElementNumber + $this->iLimit) {
331
                $filesFrom = false;
332
                $filesNum  = false;
333
            } elseif ($this->firstElementNumber <= $foldersCount) {
334
                $filesFrom = 0;
335
                $filesNum  = $this->iLimit - $foldersNum;
336
            } else {
337
                $filesFrom = $this->firstElementNumber - $foldersCount;
338
                if ($filesFrom + $this->iLimit > $filesCount) {
339
                    $filesNum = $filesCount - $filesFrom;
340
                } else {
341
                    $filesNum = $this->iLimit;
342
                }
343
            }
344
345
            $folders = $storage->getFoldersInFolder($this->folderObject, $foldersFrom, $foldersNum, true, false, trim($this->sort), (bool)$this->sortRev);
0 ignored issues
show
Bug introduced by
It seems like $foldersFrom can also be of type false; however, parameter $start of TYPO3\CMS\Core\Resource\...e::getFoldersInFolder() does only seem to accept integer, 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

345
            $folders = $storage->getFoldersInFolder($this->folderObject, /** @scrutinizer ignore-type */ $foldersFrom, $foldersNum, true, false, trim($this->sort), (bool)$this->sortRev);
Loading history...
Bug introduced by
It seems like $foldersNum can also be of type false; however, parameter $maxNumberOfItems of TYPO3\CMS\Core\Resource\...e::getFoldersInFolder() does only seem to accept integer, 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

345
            $folders = $storage->getFoldersInFolder($this->folderObject, $foldersFrom, /** @scrutinizer ignore-type */ $foldersNum, true, false, trim($this->sort), (bool)$this->sortRev);
Loading history...
346
            $files = $this->folderObject->getFiles($filesFrom, $filesNum, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, false, trim($this->sort), (bool)$this->sortRev);
0 ignored issues
show
Bug introduced by
It seems like $filesFrom can also be of type false; however, parameter $start of TYPO3\CMS\Core\Resource\Folder::getFiles() does only seem to accept integer, 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

346
            $files = $this->folderObject->getFiles(/** @scrutinizer ignore-type */ $filesFrom, $filesNum, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, false, trim($this->sort), (bool)$this->sortRev);
Loading history...
Bug introduced by
It seems like $filesNum can also be of type false; however, parameter $numberOfItems of TYPO3\CMS\Core\Resource\Folder::getFiles() does only seem to accept integer, 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

346
            $files = $this->folderObject->getFiles($filesFrom, /** @scrutinizer ignore-type */ $filesNum, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, false, trim($this->sort), (bool)$this->sortRev);
Loading history...
347
            $this->totalItems = $foldersCount + $filesCount;
348
349
            // Adds the code of files/dirs
350
            $folders = ListUtility::resolveSpecialFolderNames($folders);
351
        }
352
353
        $iOut = '';
354
        // Directories are added
355
        $this->eCounter = $this->firstElementNumber;
356
        $iOut .= $this->fwd_rwd_nav();
357
358
        $iOut .= $this->formatDirList($folders);
359
        // Files are added
360
        $iOut .= $this->formatFileList($files);
361
362
        $this->eCounter = $this->firstElementNumber + $this->iLimit < $this->totalItems
363
            ? $this->firstElementNumber + $this->iLimit
364
            : -1;
365
        $iOut .= $this->fwd_rwd_nav();
366
367
        // Header line is drawn
368
        $theData = [];
369
        foreach ($this->fieldArray as $v) {
370
            if ($v === '_CLIPBOARD_') {
371
                $theData[$v] = $this->renderClipboardHeaderRow(!empty($iOut));
372
            } elseif ($v === '_REF_') {
373
                $theData[$v] = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._REF_'));
374
            } elseif ($v === '_PATH_') {
375
                $theData[$v] = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._PATH_'));
376
            } else {
377
                // Normal row
378
                $theData[$v]  = $this->linkWrapSort($this->folderObject->getCombinedIdentifier(), $v);
379
            }
380
        }
381
382
        return '
383
            <div class="mb-4 mt-4">
384
                <div class="table-fit mb-0">
385
                    <table class="table table-striped table-hover" id="typo3-filelist">
386
                        <thead>' . $this->addElement('', $theData, 'th') . '</thead>
387
                        <tbody>' . $iOut . '</tbody>
388
                    </table>
389
                </div>
390
            </div>';
391
    }
392
393
    protected function renderClipboardHeaderRow(bool $hasContent): string
394
    {
395
        $cells = [];
396
        $elFromTable = $this->clipObj->elFromTable('_FILE');
397
        if (!empty($elFromTable) && $this->folderObject->checkActionPermission('write')) {
398
            $clipboardMode = $this->clipObj->clipData[$this->clipObj->current]['mode'] ?? '';
399
            $permission = $clipboardMode === 'copy' ? 'copy' : 'move';
400
            $addPasteButton = $this->folderObject->checkActionPermission($permission);
401
            $elToConfirm = [];
402
            foreach ($elFromTable as $key => $element) {
403
                $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
404
                if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $this->folderObject)) {
405
                    $addPasteButton = false;
406
                }
407
                $elToConfirm[$key] = $clipBoardElement->getName();
408
            }
409
            if ($addPasteButton) {
410
                $cells[] = '<a class="btn btn-default t3js-modal-trigger"' .
411
                    ' href="' . htmlspecialchars($this->clipObj->pasteUrl(
412
                        '_FILE',
413
                        $this->folderObject->getCombinedIdentifier()
414
                    )) . '"'
415
                    . ' data-bs-content="' . htmlspecialchars($this->clipObj->confirmMsgText(
416
                        '_FILE',
417
                        $this->folderObject->getReadablePath(),
418
                        'into',
419
                        $elToConfirm
420
                    )) . '"'
421
                    . ' data-severity="warning"'
422
                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_paste')) . '"'
423
                    . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_paste')) . '">'
424
                    . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)
425
                        ->render()
426
                    . '</a>';
427
            } else {
428
                $cells[] = $this->spaceIcon;
429
            }
430
        }
431
        if ($this->clipObj->current !== 'normal' && $hasContent) {
432
            $cells[] = $this->linkClipboardHeaderIcon('<span title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_selectMarked')) . '">' . $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render() . '</span>', 'setCB');
433
            $cells[] = $this->linkClipboardHeaderIcon('<span title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_deleteMarked')) . '">' . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</span>', 'delete', $this->getLanguageService()->getLL('clip_deleteMarkedWarning'));
434
            $cells[] = '<a class="btn btn-default t3js-toggle-all-checkboxes" data-checkboxes-names="' . htmlspecialchars(implode(',', $this->CBnames)) . '" rel="" href="#" title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_markRecords')) . '">' . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
435
        }
436
        return implode('', $cells);
437
    }
438
439
    /**
440
     * Returns a table-row with the content from the fields in the input data array.
441
     * OBS: $this->fieldArray MUST be set! (represents the list of fields to display)
442
     *
443
     * @param string $icon Is the <img>+<a> of the record. If not supplied the first 'join'-icon will be a 'line' instead
444
     * @param array $data Is the data array, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in <td>-tags
445
     * @param string $colType Defines the tag being used for the columns. Default is td.
446
     *
447
     * @return string HTML content for the table row
448
     */
449
    public function addElement($icon, $data, $colType = 'td')
450
    {
451
        $colType = ($colType === 'th') ? 'th' : 'td';
452
        // Start up:
453
        $l10nParent = (int)($data['_l10nparent_'] ?? 0);
454
        $out = '
455
		<tr data-uid="' . (int)($data['uid'] ?? 0) . '" data-l10nparent="' . $l10nParent . '">';
456
        $out .= '
457
        <' . $colType . ' class="col-icon nowrap">';
458
        if ($icon) {
459
            $out .= $icon;
460
        }
461
        $out .= '</' . $colType . '>';
462
        // Init rendering.
463
        $colsp = '';
464
        $lastKey = '';
465
        $c = 0;
466
        $ccount = 0;
467
        // __label is used as the label key to circumvent problems with uid used as label (see #67756)
468
        // as it was introduced later on, check if it really exists before using it
469
        $fields = $this->fieldArray;
470
        if ($colType === 'td' && array_key_exists('__label', $data)) {
471
            $fields[0] = '__label';
472
        }
473
        // Traverse field array which contains the data to present:
474
        foreach ($fields as $vKey) {
475
            if (isset($data[$vKey])) {
476
                if ($lastKey && isset($data[$lastKey])) {
477
                    $cssClass = $this->addElement_tdCssClass[$lastKey] ?? '';
478
                    $out .= '
479
						<' . $colType . ' class="' . $cssClass . '"' . $colsp . '>' . $data[$lastKey] . '</' . $colType . '>';
480
                }
481
                $lastKey = $vKey;
482
                $c = 1;
483
                $ccount++;
484
            } else {
485
                if (!$lastKey) {
486
                    $lastKey = $vKey;
487
                }
488
                $c++;
489
            }
490
            if ($c > 1) {
491
                $colsp = ' colspan="' . $c . '"';
492
            } else {
493
                $colsp = '';
494
            }
495
        }
496
        if ($lastKey) {
497
            $cssClass = $this->addElement_tdCssClass[$lastKey] ?? '';
498
            $out .= '
499
				<' . $colType . ' class="' . $cssClass . '"' . $colsp . '>' . $data[$lastKey] . '</' . $colType . '>';
500
        }
501
        $out .= '
502
		</tr>';
503
        return $out;
504
    }
505
506
    /**
507
     * Creates a forward/reverse button based on the status of ->eCounter, ->firstElementNumber, ->iLimit
508
     *
509
     * @return string the table-row code for the element
510
     */
511
    public function fwd_rwd_nav()
512
    {
513
        $code = '';
514
        if ($this->eCounter >= $this->firstElementNumber && $this->eCounter < $this->firstElementNumber + $this->iLimit) {
515
            if ($this->firstElementNumber && $this->eCounter == $this->firstElementNumber) {
516
                // 	Reverse
517
                $theData = [];
518
                $theData['file'] = $this->fwd_rwd_HTML('fwd', $this->eCounter);
519
                $code = $this->addElement('', $theData);
520
            }
521
            return $code;
522
        }
523
        if ($this->eCounter == $this->firstElementNumber + $this->iLimit) {
524
            // 	Forward
525
            $theData = [];
526
            $theData['file'] = $this->fwd_rwd_HTML('rwd', $this->eCounter);
527
            $code = $this->addElement('', $theData);
528
        }
529
        return $code;
530
    }
531
532
    /**
533
     * Creates the button with link to either forward or reverse
534
     *
535
     * @param string $type Type: "fwd" or "rwd
536
     * @param int $pointer Pointer
537
     * @return string
538
     * @internal
539
     */
540
    public function fwd_rwd_HTML($type, $pointer)
541
    {
542
        $content = '';
543
        switch ($type) {
544
            case 'fwd':
545
                $href = $this->listURL() . '&pointer=' . ($pointer - $this->iLimit);
546
                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
547
                    'actions-move-up',
548
                    Icon::SIZE_SMALL
549
                )->render() . ' <i>[' . (max(0, $pointer - $this->iLimit) + 1) . ' - ' . $pointer . ']</i></a>';
550
                break;
551
            case 'rwd':
552
                $href = $this->listURL() . '&pointer=' . $pointer;
553
                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
554
                    'actions-move-down',
555
                    Icon::SIZE_SMALL
556
                )->render() . ' <i>[' . ($pointer + 1) . ' - ' . $this->totalItems . ']</i></a>';
557
                break;
558
        }
559
        return $content;
560
    }
561
562
    /**
563
     * Gets the number of files and total size of a folder
564
     *
565
     * @return string
566
     */
567
    public function getFolderInfo()
568
    {
569
        if ($this->counter == 1) {
570
            $fileLabel = htmlspecialchars($this->getLanguageService()->getLL('file'));
571
        } else {
572
            $fileLabel = htmlspecialchars($this->getLanguageService()->getLL('files'));
573
        }
574
        return $this->counter . ' ' . $fileLabel . ', ' . GeneralUtility::formatSize($this->totalbytes, htmlspecialchars($this->getLanguageService()->getLL('byteSizeUnits')));
575
    }
576
577
    /**
578
     * This returns tablerows for the directories in the array $items['sorting'].
579
     *
580
     * @param Folder[] $folders Folders of \TYPO3\CMS\Core\Resource\Folder
581
     * @return string HTML table rows.
582
     */
583
    public function formatDirList(array $folders)
584
    {
585
        $out = '';
586
        foreach ($folders as $folderName => $folderObject) {
587
            $role = $folderObject->getRole();
588
            if ($role === FolderInterface::ROLE_PROCESSING) {
589
                // don't show processing-folder
590
                continue;
591
            }
592
            if ($role !== FolderInterface::ROLE_DEFAULT) {
593
                $displayName = '<strong>' . htmlspecialchars($folderName) . '</strong>';
594
            } else {
595
                $displayName = htmlspecialchars($folderName);
596
            }
597
598
            $isLocked = $folderObject instanceof InaccessibleFolder;
599
            $isWritable = $folderObject->checkActionPermission('write');
600
601
            // Initialization
602
            $this->counter++;
603
604
            // The icon with link
605
            $theIcon = '<span title="' . htmlspecialchars($folderName) . '">' . $this->iconFactory->getIconForResource($folderObject, Icon::SIZE_SMALL)->render() . '</span>';
606
            if (!$isLocked) {
607
                $theIcon = (string)BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $folderObject->getCombinedIdentifier());
608
            }
609
610
            // Preparing and getting the data-array
611
            $theData = [];
612
            if ($isLocked) {
613
                foreach ($this->fieldArray as $field) {
614
                    $theData[$field] = '';
615
                }
616
                $theData['file'] = $displayName;
617
            } else {
618
                foreach ($this->fieldArray as $field) {
619
                    switch ($field) {
620
                        case 'size':
621
                            try {
622
                                $numFiles = $folderObject->getFileCount();
623
                            } catch (InsufficientFolderAccessPermissionsException $e) {
624
                                $numFiles = 0;
625
                            }
626
                            $theData[$field] = $numFiles . ' ' . htmlspecialchars($this->getLanguageService()->getLL(($numFiles === 1 ? 'file' : 'files')));
627
                            break;
628
                        case 'rw':
629
                            $theData[$field] = '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('read')) . '</strong>' . (!$isWritable ? '' : '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('write')) . '</strong>');
630
                            break;
631
                        case 'fileext':
632
                            $theData[$field] = htmlspecialchars($this->getLanguageService()->getLL('folder'));
633
                            break;
634
                        case 'tstamp':
635
                            $tstamp = $folderObject->getModificationTime();
636
                            $theData[$field] = $tstamp ? BackendUtility::date($tstamp) : '-';
637
                            break;
638
                        case 'file':
639
                            $theData[$field] = $this->linkWrapDir($displayName, $folderObject);
640
                            break;
641
                        case '_CONTROL_':
642
                            $theData[$field] = $this->makeEdit($folderObject);
643
                            break;
644
                        case '_CLIPBOARD_':
645
                            $theData[$field] = $this->makeClip($folderObject);
646
                            break;
647
                        case '_REF_':
648
                            $theData[$field] = $this->makeRef($folderObject);
649
                            break;
650
                        default:
651
                            $theData[$field] = GeneralUtility::fixed_lgd_cs($theData[$field] ?? '', $this->fixedL);
652
                    }
653
                }
654
            }
655
            $out .= $this->addElement($theIcon, $theData);
656
        }
657
        return $out;
658
    }
659
660
    /**
661
     * Wraps the directory-titles
662
     *
663
     * @param string $title String to be wrapped in links
664
     * @param Folder $folderObject Folder to work on
665
     * @return string HTML
666
     */
667
    public function linkWrapDir($title, Folder $folderObject)
668
    {
669
        $href = (string)$this->uriBuilder->buildUriFromRoute('file_FilelistList', ['id' => $folderObject->getCombinedIdentifier()]);
670
        $triggerTreeUpdateAttribute = sprintf(
671
            ' data-tree-update-request="%s"',
672
            htmlspecialchars($folderObject->getCombinedIdentifier())
673
        );
674
        // Sometimes $code contains plain HTML tags. In such a case the string should not be modified!
675
        if ((string)$title === strip_tags($title)) {
676
            return '<a href="' . htmlspecialchars($href) . '"' . $triggerTreeUpdateAttribute . ' title="' . htmlspecialchars($title) . '">' . $title . '</a>';
677
        }
678
        return '<a href="' . htmlspecialchars($href) . '"' . $triggerTreeUpdateAttribute . '>' . $title . '</a>';
679
    }
680
681
    /**
682
     * Wraps filenames in links which opens the metadata editor.
683
     *
684
     * @param string $code String to be wrapped in links
685
     * @param File $fileObject File to be linked
686
     * @return string HTML
687
     */
688
    public function linkWrapFile($code, File $fileObject)
689
    {
690
        try {
691
            if ($fileObject instanceof File && $fileObject->isIndexed() && $fileObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
692
                $metaData = $fileObject->getMetaData()->get();
693
                $urlParameters = [
694
                    'edit' => [
695
                        'sys_file_metadata' => [
696
                            $metaData['uid'] => 'edit'
697
                        ]
698
                    ],
699
                    'returnUrl' => $this->listURL()
700
                ];
701
                $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
702
                $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editMetadata'));
703
                $code = '<a class="responsive-title" href="' . htmlspecialchars($url) . '" title="' . $title . '">' . $code . '</a>';
704
            }
705
        } catch (\Exception $e) {
706
            // intentional fall-through
707
        }
708
        return $code;
709
    }
710
711
    /**
712
     * Returns list URL; This is the URL of the current script with id and imagemode parameters, that's all.
713
     * The URL however is not relative, otherwise GeneralUtility::sanitizeLocalUrl() would say that
714
     * the URL would be invalid.
715
     *
716
     * @return string URL
717
     */
718
    public function listURL(): string
719
    {
720
        return GeneralUtility::linkThisScript(array_filter([
721
            'target' => rawurlencode($this->folderObject->getCombinedIdentifier()),
722
            'searchTerm' => rawurlencode($this->searchTerm)
723
        ]));
724
    }
725
726
    protected function getAvailableSystemLanguages(): array
727
    {
728
        // first two keys are "0" (default) and "-1" (multiple), after that comes the "other languages"
729
        $allSystemLanguages = $this->translateTools->getSystemLanguages();
730
        return array_filter($allSystemLanguages, function ($languageRecord) {
731
            if ($languageRecord['uid'] === -1 || $languageRecord['uid'] === 0 || !$this->getBackendUser()->checkLanguageAccess($languageRecord['uid'])) {
732
                return false;
733
            }
734
            return true;
735
        });
736
    }
737
    /**
738
     * This returns tablerows for the files in the array $items['sorting'].
739
     *
740
     * @param File[] $files File items
741
     * @return string HTML table rows.
742
     */
743
    public function formatFileList(array $files)
744
    {
745
        $out = '';
746
        $systemLanguages = $this->getAvailableSystemLanguages();
747
        foreach ($files as $fileObject) {
748
            // Initialization
749
            $this->counter++;
750
            $this->totalbytes += $fileObject->getSize();
751
            $ext = $fileObject->getExtension();
752
            $fileName = trim($fileObject->getName());
753
            // The icon with link
754
            $theIcon = '<span title="' . htmlspecialchars($fileName . ' [' . (int)$fileObject->getUid() . ']') . '">'
755
                . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render() . '</span>';
756
            $theIcon = (string)BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $fileObject->getCombinedIdentifier());
757
            // Preparing and getting the data-array
758
            $theData = [];
759
            foreach ($this->fieldArray as $field) {
760
                switch ($field) {
761
                    case 'size':
762
                        $theData[$field] = GeneralUtility::formatSize((int)$fileObject->getSize(), htmlspecialchars($this->getLanguageService()->getLL('byteSizeUnits')));
763
                        break;
764
                    case 'rw':
765
                        $theData[$field] = '' . (!$fileObject->checkActionPermission('read') ? ' ' : '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('read')) . '</strong>') . (!$fileObject->checkActionPermission('write') ? '' : '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('write')) . '</strong>');
766
                        break;
767
                    case 'fileext':
768
                        $theData[$field] = htmlspecialchars(strtoupper($ext));
769
                        break;
770
                    case 'tstamp':
771
                        $theData[$field] = BackendUtility::date($fileObject->getModificationTime());
772
                        break;
773
                    case '_CONTROL_':
774
                        $theData[$field] = $this->makeEdit($fileObject);
775
                        break;
776
                    case '_CLIPBOARD_':
777
                        $theData[$field] = $this->makeClip($fileObject);
778
                        break;
779
                    case '_LOCALIZATION_':
780
                        if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata') && !empty($GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'] ?? null)) {
781
                            $metaDataRecord = $fileObject->getMetaData()->get();
782
                            $translations = $this->getTranslationsForMetaData($metaDataRecord);
783
                            $languageCode = '';
784
785
                            foreach ($systemLanguages as $language) {
786
                                $languageId = $language['uid'];
787
                                $flagIcon = $language['flagIcon'];
788
                                if (array_key_exists($languageId, $translations)) {
789
                                    $title = htmlspecialchars(sprintf($this->getLanguageService()->getLL('editMetadataForLanguage'), $language['title']));
790
                                    $urlParameters = [
791
                                        'edit' => [
792
                                            'sys_file_metadata' => [
793
                                                $translations[$languageId]['uid'] => 'edit'
794
                                            ]
795
                                        ],
796
                                        'returnUrl' => $this->listURL()
797
                                    ];
798
                                    $flagButtonIcon = $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-edit')->render();
799
                                    $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
800
                                    $languageCode .= '<a href="' . htmlspecialchars($url) . '" class="btn btn-default" title="' . $title . '">'
801
                                        . $flagButtonIcon . '</a>';
802
                                } else {
803
                                    $parameters = [
804
                                        'justLocalized' => 'sys_file_metadata:' . $metaDataRecord['uid'] . ':' . $languageId,
805
                                        'returnUrl' => $this->listURL()
806
                                    ];
807
                                    $returnUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $parameters);
808
                                    $href = BackendUtility::getLinkToDataHandlerAction(
809
                                        '&cmd[sys_file_metadata][' . $metaDataRecord['uid'] . '][localize]=' . $languageId,
810
                                        $returnUrl
811
                                    );
812
                                    $flagButtonIcon = '<span title="' . htmlspecialchars(sprintf($this->getLanguageService()->getLL('createMetadataForLanguage'), $language['title'])) . '">' . $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-new')->render() . '</span>';
813
                                    $languageCode .= '<a href="' . htmlspecialchars($href) . '" class="btn btn-default">' . $flagButtonIcon . '</a> ';
814
                                }
815
                            }
816
817
                            // Hide flag button bar when not translated yet
818
                            $theData[$field] = ' <div class="localisationData btn-group' . (empty($translations) ? ' hidden' : '') . '" data-fileid="' . $fileObject->getUid() . '">'
819
                                . $languageCode . '</div>';
820
                            $theData[$field] .= '<a class="btn btn-default filelist-translationToggler" data-fileid="' . $fileObject->getUid() . '">' .
821
                                '<span title="' . htmlspecialchars($this->getLanguageService()->getLL('translateMetadata')) . '">'
822
                                . $this->iconFactory->getIcon('mimetypes-x-content-page-language-overlay', Icon::SIZE_SMALL)->render() . '</span>'
823
                                . '</a>';
824
                        }
825
                        break;
826
                    case '_REF_':
827
                        $theData[$field] = $this->makeRef($fileObject);
828
                        break;
829
                    case '_PATH_':
830
                        $theData[$field] = '';
831
                        $method = 'getReadablePath';
832
                        if (is_callable([$fileObject->getParentFolder(), $method])) {
833
                            $theData[$field] = htmlspecialchars($fileObject->getParentFolder()->$method());
834
                        }
835
                        break;
836
                    case 'file':
837
                        // Edit metadata of file
838
                        $theData[$field] = $this->linkWrapFile(htmlspecialchars($fileName), $fileObject);
839
840
                        if ($fileObject->isMissing()) {
841
                            $theData[$field] .= '<span class="label label-danger label-space-left">'
842
                                . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
843
                                . '</span>';
844
                        // Thumbnails?
845
                        } elseif ($this->thumbs && ($fileObject->isImage() || $fileObject->isMediaFile())) {
846
                            $processedFile = $fileObject->process(
847
                                ProcessedFile::CONTEXT_IMAGEPREVIEW,
848
                                [
849
                                    'width' => $this->thumbnailConfiguration->getWidth(),
850
                                    'height' => $this->thumbnailConfiguration->getHeight(),
851
                                ]
852
                            );
853
                            $theData[$field] .= '<br /><img src="' . htmlspecialchars(PathUtility::getAbsoluteWebPath($processedFile->getPublicUrl() ?? '')) . '" ' .
854
                                'width="' . htmlspecialchars($processedFile->getProperty('width')) . '" ' .
0 ignored issues
show
Bug introduced by
It seems like $processedFile->getProperty('width') can also be of type null; however, parameter $string of htmlspecialchars() does only seem to accept string, 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

854
                                'width="' . htmlspecialchars(/** @scrutinizer ignore-type */ $processedFile->getProperty('width')) . '" ' .
Loading history...
855
                                'height="' . htmlspecialchars($processedFile->getProperty('height')) . '" ' .
856
                                'title="' . htmlspecialchars($fileName) . '" alt="" />';
857
                        }
858
                        break;
859
                    default:
860
                        $theData[$field] = '';
861
                        if ($fileObject->hasProperty($field)) {
862
                            $theData[$field] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getProperty($field), $this->fixedL));
863
                        }
864
                }
865
            }
866
            $out .= $this->addElement($theIcon, $theData);
867
        }
868
        return $out;
869
    }
870
871
    /**
872
     * Fetch the translations for a sys_file_metadata record
873
     *
874
     * @param array $metaDataRecord
875
     * @return array keys are the sys_language uids, values are the $rows
876
     */
877
    protected function getTranslationsForMetaData($metaDataRecord)
878
    {
879
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_metadata');
880
        $queryBuilder->getRestrictions()->removeAll();
881
        $translationRecords = $queryBuilder->select('*')
882
            ->from('sys_file_metadata')
883
            ->where(
884
                $queryBuilder->expr()->eq(
885
                    $GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'],
886
                    $queryBuilder->createNamedParameter($metaDataRecord['uid'], \PDO::PARAM_INT)
887
                ),
888
                $queryBuilder->expr()->gt(
889
                    $GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'],
890
                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
891
                )
892
            )
893
            ->execute()
894
            ->fetchAll();
895
896
        $translations = [];
897
        foreach ($translationRecords as $record) {
898
            $translations[$record[$GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField']]] = $record;
899
        }
900
        return $translations;
901
    }
902
903
    /**
904
     * Wraps the directory-titles ($code) in a link to filelist/Modules/Filelist/index.php (id=$path) and sorting commands...
905
     *
906
     * @param string $folderIdentifier ID (path)
907
     * @param string $col Sorting column
908
     * @return string HTML
909
     */
910
    public function linkWrapSort($folderIdentifier, $col)
911
    {
912
        $code = htmlspecialchars($this->getLanguageService()->getLL('c_' . $col));
913
        $params = ['id' => $folderIdentifier, 'SET' => ['sort' => $col]];
914
915
        if ($this->sort === $col) {
916
            // Check reverse sorting
917
            $params['SET']['reverse'] = ($this->sortRev ? '0' : '1');
918
            $sortArrow = $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render();
919
        } else {
920
            $params['SET']['reverse'] = 0;
921
            $sortArrow = '';
922
        }
923
        $href = (string)$this->uriBuilder->buildUriFromRoute('file_FilelistList', $params);
924
        return '<a href="' . htmlspecialchars($href) . '">' . $code . ' ' . $sortArrow . '</a>';
925
    }
926
927
    /**
928
     * Creates the clipboard control pad
929
     *
930
     * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the clipboard panel for the listing.
931
     * @return string HTML-table
932
     */
933
    public function makeClip($fileOrFolderObject)
934
    {
935
        if (!$fileOrFolderObject->checkActionPermission('read')) {
936
            return '';
937
        }
938
        $cells = [];
939
        $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
940
        $fullName = $fileOrFolderObject->getName();
941
        $md5 = GeneralUtility::shortMD5($fullIdentifier);
942
        // For normal clipboard, add copy/cut buttons:
943
        if ($this->clipObj->current === 'normal') {
944
            $isSel = $this->clipObj->isSelected('_FILE', $md5);
0 ignored issues
show
Bug introduced by
$md5 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

944
            $isSel = $this->clipObj->isSelected('_FILE', /** @scrutinizer ignore-type */ $md5);
Loading history...
945
            $copyTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy'));
946
            $cutTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cut'));
947
            $copyIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render();
948
            $cutIcon = $this->iconFactory->getIcon('actions-edit-cut', Icon::SIZE_SMALL)->render();
949
950
            if ($isSel === 'copy') {
951
                $copyIcon = $this->iconFactory->getIcon('actions-edit-copy-release', Icon::SIZE_SMALL)->render();
952
            } elseif ($isSel === 'cut') {
953
                $cutIcon = $this->iconFactory->getIcon('actions-edit-cut-release', Icon::SIZE_SMALL)->render();
954
            }
955
956
            if ($fileOrFolderObject->checkActionPermission('copy')) {
957
                $cells[] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->selUrlFile(
958
                    $fullIdentifier,
959
                    1,
960
                    $isSel === 'copy'
961
                )) . '" title="' . $copyTitle . '">' . $copyIcon . '</a>';
962
            } else {
963
                $cells[] = $this->spaceIcon;
964
            }
965
            // we can only cut if file can be moved
966
            if ($fileOrFolderObject->checkActionPermission('move')) {
967
                $cells[] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->selUrlFile(
968
                    $fullIdentifier,
969
                    0,
970
                    $isSel === 'cut'
971
                )) . '" title="' . $cutTitle . '">' . $cutIcon . '</a>';
972
            } else {
973
                $cells[] = $this->spaceIcon;
974
            }
975
        } else {
976
            // For numeric pads, add select checkboxes:
977
            $n = '_FILE|' . $md5;
978
            $this->CBnames[] = $n;
979
            $checked = $this->clipObj->isSelected('_FILE', $md5) ? ' checked="checked"' : '';
980
            $cells[] = '<label class="btn btn-default btn-checkbox"><input type="checkbox" name="CBC[' . $n . ']" value="' . htmlspecialchars($fullIdentifier) . '" ' . $checked . ' /><span class="t3-icon fa"></span><input type="hidden" name="CBH[' . $n . ']" value="0" /></label>';
981
        }
982
        // Display PASTE button, if directory:
983
        $elFromTable = $this->clipObj->elFromTable('_FILE');
984
        if ($fileOrFolderObject instanceof Folder && !empty($elFromTable) && $fileOrFolderObject->checkActionPermission('write')) {
985
            $clipboardMode = $this->clipObj->clipData[$this->clipObj->current]['mode'] ?? '';
986
            $permission = $clipboardMode === 'copy' ? 'copy' : 'move';
987
            $addPasteButton = $this->folderObject->checkActionPermission($permission);
988
            $elToConfirm = [];
989
            foreach ($elFromTable as $key => $element) {
990
                $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
991
                if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $fileOrFolderObject)) {
992
                    $addPasteButton = false;
993
                }
994
                $elToConfirm[$key] = $clipBoardElement->getName();
995
            }
996
            if ($addPasteButton) {
997
                $cells[] = '<a class="btn btn-default t3js-modal-trigger" '
998
                    . ' href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $fullIdentifier)) . '"'
999
                    . ' data-bs-content="' . htmlspecialchars($this->clipObj->confirmMsgText('_FILE', $fullName, 'into', $elToConfirm)) . '"'
1000
                    . ' data-severity="warning"'
1001
                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteInto')) . '"'
1002
                    . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteInto')) . '"'
1003
                    . '>'
1004
                    . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
1005
                    . '</a>';
1006
            } else {
1007
                $cells[] = $this->spaceIcon;
1008
            }
1009
        }
1010
        // Compile items into a DIV-element:
1011
        return ' <div class="btn-group" role="group">' . implode('', $cells) . '</div>';
1012
    }
1013
1014
    /**
1015
     * Creates the edit control section
1016
     *
1017
     * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the edit control section for the listing.
1018
     * @return string HTML-table
1019
     */
1020
    public function makeEdit($fileOrFolderObject)
1021
    {
1022
        $cells = [];
1023
        $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
1024
1025
        // Edit file content (if editable)
1026
        if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('write') && $fileOrFolderObject->isTextFile()) {
1027
            $attributes = [
1028
                'href' => (string)$this->uriBuilder->buildUriFromRoute('file_edit', ['target' => $fullIdentifier, 'returnUrl' => $this->listURL()]),
1029
                'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editcontent'),
1030
            ];
1031
            $cells['edit'] = '<a class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>'
1032
                . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render()
1033
                . '</a>';
1034
        } else {
1035
            $cells['edit'] = $this->spaceIcon;
1036
        }
1037
1038
        // Edit metadata of file
1039
        if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
1040
            $metaData = $fileOrFolderObject->getMetaData()->get();
1041
            $urlParameters = [
1042
                'edit' => [
1043
                    'sys_file_metadata' => [
1044
                        $metaData['uid'] => 'edit'
1045
                    ]
1046
                ],
1047
                'returnUrl' => $this->listURL()
1048
            ];
1049
            $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1050
            $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editMetadata'));
1051
            $cells['metadata'] = '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" title="' . $title . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
1052
        }
1053
1054
        // document view
1055
        if ($fileOrFolderObject instanceof File) {
1056
            $fileUrl = $fileOrFolderObject->getPublicUrl();
1057
            if ($fileUrl) {
1058
                $cells['view'] = '<a href="' . htmlspecialchars(PathUtility::getAbsoluteWebPath($fileUrl)) . '" target="_blank" class="btn btn-default" title="' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.view') . '">' . $this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL)->render() . '</a>';
1059
            } else {
1060
                $cells['view'] = $this->spaceIcon;
1061
            }
1062
        } else {
1063
            $cells['view'] = $this->spaceIcon;
1064
        }
1065
1066
        // replace file
1067
        if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('replace')) {
1068
            $attributes = [
1069
                'href' => (string)$this->uriBuilder->buildUriFromRoute('file_replace', ['target' => $fullIdentifier, 'uid' => $fileOrFolderObject->getUid(), 'returnUrl' => $this->listURL()]),
1070
                'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.replace'),
1071
            ];
1072
            $cells['replace'] = '<a class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $this->iconFactory->getIcon('actions-edit-replace', Icon::SIZE_SMALL)->render() . '</a>';
1073
        }
1074
1075
        // rename the file
1076
        if ($fileOrFolderObject->checkActionPermission('rename')) {
1077
            $attributes = [
1078
                'href' => (string)$this->uriBuilder->buildUriFromRoute('file_rename', ['target' => $fullIdentifier, 'returnUrl' => $this->listURL()]),
1079
                'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.rename'),
1080
            ];
1081
            $cells['rename'] = '<a class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $this->iconFactory->getIcon('actions-edit-rename', Icon::SIZE_SMALL)->render() . '</a>';
1082
        } else {
1083
            $cells['rename'] = $this->spaceIcon;
1084
        }
1085
1086
        // upload files
1087
        if ($fileOrFolderObject->getStorage()->checkUserActionPermission('add', 'File') && $fileOrFolderObject->checkActionPermission('write')) {
1088
            if ($fileOrFolderObject instanceof Folder) {
1089
                $attributes = [
1090
                    'href' => (string)$this->uriBuilder->buildUriFromRoute('file_upload', ['target' => $fullIdentifier, 'returnUrl' => $this->listURL()]),
1091
                    'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.upload'),
1092
                ];
1093
                $cells['upload'] = '<a class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $this->iconFactory->getIcon('actions-edit-upload', Icon::SIZE_SMALL)->render() . '</a>';
1094
            }
1095
        }
1096
1097
        if ($fileOrFolderObject->checkActionPermission('read')) {
1098
            $attributes = [
1099
                'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info'),
1100
            ];
1101
            if ($fileOrFolderObject instanceof Folder || $fileOrFolderObject instanceof File) {
0 ignored issues
show
introduced by
$fileOrFolderObject is always a sub-type of TYPO3\CMS\Core\Resource\File.
Loading history...
1102
                $attributes['data-filelist-show-item-type'] = $fileOrFolderObject instanceof File ? '_FILE' : '_FOLDER';
1103
                $attributes['data-filelist-show-item-identifier'] = $fullIdentifier;
1104
            }
1105
            $cells['info'] = '<a href="#" class="btn btn-default" ' . GeneralUtility::implodeAttributes($attributes, true) . '>'
1106
                . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>';
1107
        } else {
1108
            $cells['info'] = $this->spaceIcon;
1109
        }
1110
1111
        // delete the file
1112
        if ($fileOrFolderObject->checkActionPermission('delete')) {
1113
            $identifier = $fileOrFolderObject->getIdentifier();
1114
            if ($fileOrFolderObject instanceof Folder) {
1115
                $referenceCountText = BackendUtility::referenceCount('_FILE', $identifier, ' ' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFolder'));
1116
                $deleteType = 'delete_folder';
1117
            } else {
1118
                $referenceCountText = BackendUtility::referenceCount('sys_file', (string)$fileOrFolderObject->getUid(), ' ' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile'));
1119
                $deleteType = 'delete_file';
1120
            }
1121
1122
            if ($this->getBackendUser()->jsConfirmation(JsConfirmation::DELETE)) {
1123
                $confirmationCheck = '1';
1124
            } else {
1125
                $confirmationCheck = '0';
1126
            }
1127
1128
            $deleteUrl = (string)$this->uriBuilder->buildUriFromRoute('tce_file');
1129
            $confirmationMessage = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.delete'), $fileOrFolderObject->getName()) . $referenceCountText;
1130
            $title = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete');
1131
            $cells['delete'] = '<a href="#" class="btn btn-default t3js-filelist-delete" data-bs-content="' . htmlspecialchars($confirmationMessage)
1132
                . '" data-check="' . $confirmationCheck
1133
                . '" data-delete-url="' . htmlspecialchars($deleteUrl)
1134
                . '" data-title="' . htmlspecialchars($title)
1135
                . '" data-identifier="' . htmlspecialchars($fileOrFolderObject->getCombinedIdentifier())
1136
                . '" data-delete-type="' . $deleteType
1137
                . '" title="' . htmlspecialchars($title) . '">'
1138
                . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
1139
        } else {
1140
            $cells['delete'] = $this->spaceIcon;
1141
        }
1142
1143
        // Hook for manipulating edit icons.
1144
        $cells['__fileOrFolderObject'] = $fileOrFolderObject;
1145
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['fileList']['editIconsHook'] ?? [] as $className) {
1146
            $hookObject = GeneralUtility::makeInstance($className);
1147
            if (!$hookObject instanceof FileListEditIconHookInterface) {
1148
                throw new \UnexpectedValueException(
1149
                    $className . ' must implement interface ' . FileListEditIconHookInterface::class,
1150
                    1235225797
1151
                );
1152
            }
1153
            $hookObject->manipulateEditIcons($cells, $this);
1154
        }
1155
        unset($cells['__fileOrFolderObject']);
1156
        // Compile items into a dropdown
1157
        $cellOutput = '';
1158
        $output = '';
1159
        foreach ($cells as $key => $action) {
1160
            if (in_array($key, ['view', 'metadata', 'delete'])) {
1161
                $output .= $action;
1162
                continue;
1163
            }
1164
            if ($action === $this->spaceIcon) {
1165
                continue;
1166
            }
1167
            // This is a backwards-compat layer for the existing hook items, which will be removed in TYPO3 v12.
1168
            $action = str_replace('btn btn-default', 'dropdown-item', $action);
1169
            $title = [];
1170
            preg_match('/title="([^"]*)"/', $action, $title);
1171
            if (empty($title)) {
1172
                preg_match('/aria-label="([^"]*)"/', $action, $title);
1173
            }
1174
            if (!empty($title[1] ?? '')) {
1175
                $action = str_replace('</a>', ' ' . $title[1] . '</a>', $action);
1176
                $action = str_replace('</button>', ' ' . $title[1] . '</button>', $action);
1177
            }
1178
            $cellOutput .= '<li>' . $action . '</li>';
1179
        }
1180
        $icon = $this->iconFactory->getIcon('actions-menu-alternative', Icon::SIZE_SMALL);
1181
        $output .= '<div class="btn-group dropdown position-static">' .
1182
            '<a href="#actions_' . $fileOrFolderObject->getHashedIdentifier() . '" class="btn btn-default dropdown-toggle dropdown-toggle-no-chevron" data-bs-toggle="dropdown" data-bs-boundary="window" aria-expanded="false">' . $icon->render() . '</a>' .
1183
            '<ul id="actions_' . $fileOrFolderObject->getHashedIdentifier() . '" class="dropdown-menu dropdown-list">' . $cellOutput . '</ul>' .
1184
            '</div>';
1185
        return '<div class="btn-group position-static">' . $output . '</div>';
1186
    }
1187
1188
    /**
1189
     * Make reference count
1190
     *
1191
     * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the clipboard panel for the listing.
1192
     * @return string HTML
1193
     */
1194
    public function makeRef($fileOrFolderObject)
1195
    {
1196
        if ($fileOrFolderObject instanceof FolderInterface) {
1197
            return '-';
1198
        }
1199
        // Look up the file in the sys_refindex.
1200
        // Exclude sys_file_metadata records as these are no use references
1201
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
1202
        $referenceCount = $queryBuilder->count('*')
1203
            ->from('sys_refindex')
1204
            ->where(
1205
                $queryBuilder->expr()->eq(
1206
                    'ref_table',
1207
                    $queryBuilder->createNamedParameter('sys_file', \PDO::PARAM_STR)
1208
                ),
1209
                $queryBuilder->expr()->eq(
1210
                    'ref_uid',
1211
                    $queryBuilder->createNamedParameter($fileOrFolderObject->getUid(), \PDO::PARAM_INT)
1212
                ),
1213
                $queryBuilder->expr()->neq(
1214
                    'tablename',
1215
                    $queryBuilder->createNamedParameter('sys_file_metadata', \PDO::PARAM_STR)
1216
                )
1217
            )
1218
            ->execute()
1219
            ->fetchColumn();
1220
1221
        return $this->generateReferenceToolTip($referenceCount, $fileOrFolderObject);
1222
    }
1223
1224
    /**
1225
     * Returns an instance of LanguageService
1226
     *
1227
     * @return LanguageService
1228
     */
1229
    protected function getLanguageService()
1230
    {
1231
        return $GLOBALS['LANG'];
1232
    }
1233
1234
    /**
1235
     * Returns the current BE user.
1236
     *
1237
     * @return BackendUserAuthentication
1238
     */
1239
    protected function getBackendUser()
1240
    {
1241
        return $GLOBALS['BE_USER'];
1242
    }
1243
1244
    /**
1245
     * Generates HTML code for a Reference tooltip out of
1246
     * sys_refindex records you hand over
1247
     *
1248
     * @param int $references number of records from sys_refindex table
1249
     * @param AbstractFile $fileObject
1250
     * @return string
1251
     */
1252
    protected function generateReferenceToolTip($references, $fileObject)
1253
    {
1254
        if (!$references) {
1255
            return '-';
1256
        }
1257
        $attributes = [
1258
            'data-filelist-show-item-type' => '_FILE',
1259
            'data-filelist-show-item-identifier' => $fileObject->getCombinedIdentifier(),
1260
            'title' => $this->getLanguageService()
1261
                ->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references')
1262
                . ' (' . $references . ')'
1263
        ];
1264
        $htmlCode = '<a href="#" ' . GeneralUtility::implodeAttributes($attributes, true) . '">';
1265
        $htmlCode .= $references;
1266
        $htmlCode .= '</a>';
1267
        return $htmlCode;
1268
    }
1269
}
1270