FileList::getTable()   F
last analyzed

Complexity

Conditions 17
Paths 381

Size

Total Lines 119
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 72
dl 0
loc 119
rs 2.1208
c 0
b 0
f 0
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\ResourceInterface;
38
use TYPO3\CMS\Core\Resource\Search\FileSearchDemand;
39
use TYPO3\CMS\Core\Resource\Utility\ListUtility;
40
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
41
use TYPO3\CMS\Core\Utility\GeneralUtility;
42
use TYPO3\CMS\Core\Utility\MathUtility;
43
use TYPO3\CMS\Core\Utility\PathUtility;
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
     * @var TranslationConfigurationProvider
127
     */
128
    public $translateTools;
129
130
    /**
131
     * Keys are fieldnames and values are td-css-classes to add in addElement();
132
     *
133
     * @var array
134
     */
135
    public $addElement_tdCssClass = [
136
        '_CONTROL_' => 'col-control',
137
        '_CLIPBOARD_' => 'col-clipboard',
138
        'file' => 'col-title col-responsive',
139
        '_LOCALIZATION_' => 'col-localizationa',
140
    ];
141
142
    /**
143
     * @var Folder
144
     */
145
    protected $folderObject;
146
147
    /**
148
     * @var array
149
     */
150
    public $CBnames = [];
151
152
    /**
153
     * @var Clipboard $clipObj
154
     */
155
    public $clipObj;
156
157
    /**
158
     * @var ResourceFactory
159
     */
160
    protected $resourceFactory;
161
162
    /**
163
     * @var IconFactory
164
     */
165
    protected $iconFactory;
166
167
    /**
168
     * @var int
169
     */
170
    protected $id = 0;
171
172
    /**
173
     * @var UriBuilder
174
     */
175
    protected $uriBuilder;
176
177
    protected ?FileSearchDemand $searchDemand = null;
178
179
    public function __construct()
180
    {
181
        // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->fixedL)
182
        $this->fixedL = max($this->fixedL, $this->getBackendUser()->uc['titleLen'] ?? 1);
183
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
184
        $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
185
        $this->iLimit = MathUtility::forceIntegerInRange(
186
            $this->getBackendUser()->getTSConfig()['options.']['file_list.']['filesPerPage'] ?? $this->iLimit,
187
            1
188
        );
189
        // Create clipboard object and initialize that
190
        $this->clipObj = GeneralUtility::makeInstance(Clipboard::class);
191
        $this->clipObj->fileMode = true;
192
        $this->clipObj->initializeClipboard();
193
        $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
194
        $this->getLanguageService()->includeLLFile('EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf');
195
        $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_common.xlf');
196
        $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
197
        $this->spaceIcon = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
198
    }
199
200
    /**
201
     * Initialization of class
202
     *
203
     * @param Folder $folderObject The folder to work on
204
     * @param int $pointer Pointer
205
     * @param string $sort Sorting column
206
     * @param bool $sortRev Sorting direction
207
     * @param bool $clipBoard
208
     */
209
    public function start(Folder $folderObject, $pointer, $sort, $sortRev, $clipBoard = false)
210
    {
211
        $this->folderObject = $folderObject;
212
        $this->counter = 0;
213
        $this->totalbytes = 0;
214
        $this->sort = $sort;
215
        $this->sortRev = $sortRev;
216
        $this->firstElementNumber = $pointer;
217
        // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
218
        $rowlist = 'file,_LOCALIZATION_,_CONTROL_,fileext,tstamp,size,rw,_REF_';
219
        if ($clipBoard) {
220
            $rowlist = str_replace('_CONTROL_,', '_CONTROL_,_CLIPBOARD_,', $rowlist);
221
        }
222
        $this->fieldArray = explode(',', $rowlist);
223
    }
224
225
    /**
226
     * Wrapping input string in a link with clipboard command.
227
     *
228
     * @param string $string String to be linked - must be htmlspecialchar'ed / prepared before.
229
     * @param string $cmd "cmd" value
230
     * @param string $warning Warning for JS confirm message
231
     * @return string Linked string
232
     */
233
    public function linkClipboardHeaderIcon($string, $cmd, $warning = '')
234
    {
235
        if ($warning) {
236
            $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...
237
            $attributes['data-severity'] = 'warning';
238
            $attributes['data-bs-content'] = $warning;
239
            $attributes['data-event-name'] = 'filelist:clipboard:cmd';
240
            $attributes['data-event-payload'] = $cmd;
241
        } else {
242
            $attributes['class'] = 'btn btn-default';
243
            $attributes['data-filelist-clipboard-cmd'] = $cmd;
244
        }
245
246
        return '<button type="button" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $string . '</button>';
247
    }
248
249
    /**
250
     * Returns a table with directories and files listed.
251
     *
252
     * @param FileSearchDemand|null $searchDemand
253
     * @return string HTML-table
254
     */
255
    public function getTable(?FileSearchDemand $searchDemand = null): string
256
    {
257
        if ($searchDemand !== null) {
258
            // Store given search demand
259
            $this->searchDemand = $searchDemand;
260
            // Search currently only works for files
261
            $folders = [];
262
            // Find files by the given search demand
263
            $files = iterator_to_array($this->folderObject->searchFiles($this->searchDemand));
264
            // @todo Currently files, which got deleted in the file system, are still found.
265
            //       Therefore we have to ask their parent folder if it still contains the file.
266
            $files = array_filter($files, static function (FileInterface $file): bool {
267
                try {
268
                    if ($file->getParentFolder()->hasFile($file->getName())) {
269
                        return true;
270
                    }
271
                } catch (ResourceDoesNotExistException $e) {
272
                    // Nothing to do, file does not longer exist in folder
273
                }
274
                return false;
275
            });
276
277
            // @todo We have to manually slice the search result, since it may
278
            //       contain invalid files, which were manually filtered out above.
279
            //       This should be fixed, so we can use the $firstResult and $maxResults
280
            //       properties of the search demand directly.
281
            $this->totalItems = count($files);
282
            $filesNum = $this->firstElementNumber + $this->iLimit > $this->totalItems
283
                ? $this->totalItems - $this->firstElementNumber
284
                : $this->iLimit;
285
            $files = array_slice($files, $this->firstElementNumber, $filesNum);
286
287
            // Add special "Path" field for the search result
288
            array_unshift($this->fieldArray, '_PATH_');
289
        } else {
290
            // @todo use folder methods directly when they support filters
291
            $storage = $this->folderObject->getStorage();
292
            $storage->resetFileAndFolderNameFiltersToDefault();
293
294
            // Only render the contents of a browsable storage
295
            if (!$this->folderObject->getStorage()->isBrowsable()) {
296
                return '';
297
            }
298
            try {
299
                $foldersCount = $storage->countFoldersInFolder($this->folderObject);
300
                $filesCount = $storage->countFilesInFolder($this->folderObject);
301
            } catch (InsufficientFolderAccessPermissionsException $e) {
302
                $foldersCount = 0;
303
                $filesCount = 0;
304
            }
305
306
            if ($foldersCount <= $this->firstElementNumber) {
307
                $foldersFrom = false;
308
                $foldersNum = false;
309
            } else {
310
                $foldersFrom = $this->firstElementNumber;
311
                if ($this->firstElementNumber + $this->iLimit > $foldersCount) {
312
                    $foldersNum = $foldersCount - $this->firstElementNumber;
313
                } else {
314
                    $foldersNum = $this->iLimit;
315
                }
316
            }
317
            if ($foldersCount >= $this->firstElementNumber + $this->iLimit) {
318
                $filesFrom = false;
319
                $filesNum  = false;
320
            } elseif ($this->firstElementNumber <= $foldersCount) {
321
                $filesFrom = 0;
322
                $filesNum  = $this->iLimit - $foldersNum;
323
            } else {
324
                $filesFrom = $this->firstElementNumber - $foldersCount;
325
                if ($filesFrom + $this->iLimit > $filesCount) {
326
                    $filesNum = $filesCount - $filesFrom;
327
                } else {
328
                    $filesNum = $this->iLimit;
329
                }
330
            }
331
332
            $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 $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

332
            $folders = $storage->getFoldersInFolder($this->folderObject, $foldersFrom, /** @scrutinizer ignore-type */ $foldersNum, true, false, trim($this->sort), (bool)$this->sortRev);
Loading history...
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

332
            $folders = $storage->getFoldersInFolder($this->folderObject, /** @scrutinizer ignore-type */ $foldersFrom, $foldersNum, true, false, trim($this->sort), (bool)$this->sortRev);
Loading history...
333
            $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 $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

333
            $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...
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

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

828
                                'width="' . htmlspecialchars(/** @scrutinizer ignore-type */ $processedFile->getProperty('width')) . '" ' .
Loading history...
829
                                'height="' . htmlspecialchars($processedFile->getProperty('height')) . '" ' .
830
                                'title="' . htmlspecialchars($fileName) . '" alt="" />';
831
                        }
832
                        break;
833
                    default:
834
                        $theData[$field] = '';
835
                        if ($fileObject->hasProperty($field)) {
836
                            $theData[$field] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getProperty($field), $this->fixedL));
837
                        }
838
                }
839
            }
840
            $out .= $this->addElement($theIcon, $theData);
841
        }
842
        return $out;
843
    }
844
845
    /**
846
     * Fetch the translations for a sys_file_metadata record
847
     *
848
     * @param array $metaDataRecord
849
     * @return array keys are the sys_language uids, values are the $rows
850
     */
851
    protected function getTranslationsForMetaData($metaDataRecord)
852
    {
853
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_metadata');
854
        $queryBuilder->getRestrictions()->removeAll();
855
        $translationRecords = $queryBuilder->select('*')
856
            ->from('sys_file_metadata')
857
            ->where(
858
                $queryBuilder->expr()->eq(
859
                    $GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'],
860
                    $queryBuilder->createNamedParameter($metaDataRecord['uid'] ?? 0, \PDO::PARAM_INT)
861
                ),
862
                $queryBuilder->expr()->gt(
863
                    $GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'],
864
                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
865
                )
866
            )
867
            ->execute()
868
            ->fetchAll();
869
870
        $translations = [];
871
        foreach ($translationRecords as $record) {
872
            $translations[$record[$GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField']]] = $record;
873
        }
874
        return $translations;
875
    }
876
877
    /**
878
     * Wraps the directory-titles ($code) in a link to filelist/Modules/Filelist/index.php (id=$path) and sorting commands...
879
     *
880
     * @param string $col Sorting column
881
     * @return string HTML
882
     */
883
    public function linkWrapSort($col)
884
    {
885
        $code = htmlspecialchars($this->getLanguageService()->getLL('c_' . $col));
886
        $params = ['SET' => ['sort' => $col], 'pointer' => 0];
887
888
        if ($this->sort === $col) {
889
            // Check reverse sorting
890
            $params['SET']['reverse'] = ($this->sortRev ? '0' : '1');
891
            $sortArrow = $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render();
892
        } else {
893
            $params['SET']['reverse'] = 0;
894
            $sortArrow = '';
895
        }
896
        $href = $this->listURL($params);
897
        return '<a href="' . htmlspecialchars($href) . '">' . $code . ' ' . $sortArrow . '</a>';
898
    }
899
900
    /**
901
     * Creates the clipboard control pad
902
     *
903
     * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the clipboard panel for the listing.
904
     * @return string HTML-table
905
     */
906
    public function makeClip($fileOrFolderObject)
907
    {
908
        if (!$fileOrFolderObject->checkActionPermission('read')) {
909
            return '';
910
        }
911
        $cells = [];
912
        $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
913
        $fullName = $fileOrFolderObject->getName();
914
        $md5 = GeneralUtility::shortMD5($fullIdentifier);
915
        // For normal clipboard, add copy/cut buttons:
916
        if ($this->clipObj->current === 'normal') {
917
            $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

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