[TASK] Update php-cs-fixer to 2.5.0
[Packages/TYPO3.CMS.git] / typo3 / sysext / filelist / Classes / FileList.php
1 <?php
2 namespace TYPO3\CMS\Filelist;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Clipboard\Clipboard;
18 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
19 use TYPO3\CMS\Backend\RecordList\AbstractRecordList;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Database\ConnectionPool;
22 use TYPO3\CMS\Core\Imaging\Icon;
23 use TYPO3\CMS\Core\Imaging\IconFactory;
24 use TYPO3\CMS\Core\Messaging\FlashMessage;
25 use TYPO3\CMS\Core\Messaging\FlashMessageService;
26 use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
27 use TYPO3\CMS\Core\Resource\File;
28 use TYPO3\CMS\Core\Resource\Folder;
29 use TYPO3\CMS\Core\Resource\FolderInterface;
30 use TYPO3\CMS\Core\Resource\InaccessibleFolder;
31 use TYPO3\CMS\Core\Resource\ProcessedFile;
32 use TYPO3\CMS\Core\Resource\ResourceFactory;
33 use TYPO3\CMS\Core\Resource\Utility\ListUtility;
34 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
35 use TYPO3\CMS\Core\Utility\GeneralUtility;
36 use TYPO3\CMS\Core\Utility\MathUtility;
37 use TYPO3\CMS\Filelist\Controller\FileListController;
38
39 /**
40 * Class for rendering of File>Filelist
41 */
42 class FileList extends AbstractRecordList
43 {
44 /**
45 * Default Max items shown
46 *
47 * @var int
48 */
49 public $iLimit = 40;
50
51 /**
52 * Thumbnails on records containing files (pictures)
53 *
54 * @var bool
55 */
56 public $thumbs = false;
57
58 /**
59 * Space icon used for alignment when no button is available
60 *
61 * @var string
62 */
63 public $spaceIcon;
64
65 /**
66 * Max length of strings
67 *
68 * @var int
69 */
70 public $fixedL = 30;
71
72 /**
73 * The field to sort by
74 *
75 * @var string
76 */
77 public $sort = '';
78
79 /**
80 * Reverse sorting flag
81 *
82 * @var bool
83 */
84 public $sortRev = 1;
85
86 /**
87 * @var int
88 */
89 public $firstElementNumber = 0;
90
91 /**
92 * @var bool
93 */
94 public $clipBoard = 0;
95
96 /**
97 * @var bool
98 */
99 public $bigControlPanel = 0;
100
101 /**
102 * @var string
103 */
104 public $JScode = '';
105
106 /**
107 * @var string
108 */
109 public $HTMLcode = '';
110
111 /**
112 * @var int
113 */
114 public $totalbytes = 0;
115
116 /**
117 * @var array
118 */
119 public $dirs = [];
120
121 /**
122 * @var array
123 */
124 public $files = [];
125
126 /**
127 * @var string
128 */
129 public $path = '';
130
131 /**
132 * @var Folder
133 */
134 protected $folderObject;
135
136 /**
137 * Counting the elements no matter what
138 *
139 * @var int
140 */
141 public $eCounter = 0;
142
143 /**
144 * @var string
145 */
146 public $totalItems = '';
147
148 /**
149 * @var array
150 */
151 public $CBnames = [];
152
153 /**
154 * @var Clipboard $clipObj
155 */
156 public $clipObj;
157
158 /**
159 * @var ResourceFactory
160 */
161 protected $resourceFactory;
162
163 /**
164 * @param ResourceFactory $resourceFactory
165 */
166 public function injectResourceFactory(ResourceFactory $resourceFactory)
167 {
168 $this->resourceFactory = $resourceFactory;
169 }
170
171 /**
172 * @var IconFactory
173 */
174 protected $iconFactory;
175
176 /**
177 * @var FileListController
178 */
179 protected $fileListController;
180
181 /**
182 * Construct
183 *
184 * @param FileListController $fileListController
185 */
186 public function __construct(FileListController $fileListController)
187 {
188 parent::__construct();
189 $this->fileListController = $fileListController;
190 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
191
192 $modTSconfig = BackendUtility::getModTSconfig(0, 'options.file_list');
193 if (!empty($modTSconfig['properties']['filesPerPage'])) {
194 $this->iLimit = MathUtility::forceIntegerInRange($modTSconfig['properties']['filesPerPage'], 1);
195 }
196 }
197
198 /**
199 * Initialization of class
200 *
201 * @param Folder $folderObject The folder to work on
202 * @param int $pointer Pointer
203 * @param bool $sort Sorting column
204 * @param bool $sortRev Sorting direction
205 * @param bool $clipBoard
206 * @param bool $bigControlPanel Show clipboard flag
207 */
208 public function start(Folder $folderObject, $pointer, $sort, $sortRev, $clipBoard = false, $bigControlPanel = false)
209 {
210 $this->folderObject = $folderObject;
211 $this->counter = 0;
212 $this->totalbytes = 0;
213 $this->JScode = '';
214 $this->HTMLcode = '';
215 $this->path = $folderObject->getReadablePath();
216 $this->sort = $sort;
217 $this->sortRev = $sortRev;
218 $this->firstElementNumber = $pointer;
219 $this->clipBoard = $clipBoard;
220 $this->bigControlPanel = $bigControlPanel;
221 // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->fixedL)
222 $this->fixedL = max($this->fixedL, $this->getBackendUser()->uc['titleLen']);
223 $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_common.xlf');
224 $this->resourceFactory = ResourceFactory::getInstance();
225 }
226
227 /**
228 * Reading files and directories, counting elements and generating the list in ->HTMLcode
229 */
230 public function generateList()
231 {
232 $this->HTMLcode .= $this->getTable('fileext,tstamp,size,rw,_REF_');
233 }
234
235 /**
236 * Wrapping input string in a link with clipboard command.
237 *
238 * @param string $string String to be linked - must be htmlspecialchar'ed / prepared before.
239 * @param string $_ unused
240 * @param string $cmd "cmd" value
241 * @param string $warning Warning for JS confirm message
242 * @return string Linked string
243 */
244 public function linkClipboardHeaderIcon($string, $_, $cmd, $warning = '')
245 {
246 $jsCode = 'document.dblistForm.cmd.value=' . GeneralUtility::quoteJSvalue($cmd)
247 . ';document.dblistForm.submit();';
248
249 $attributes = [];
250 if ($warning) {
251 $attributes['class'] = 'btn btn-default t3js-modal-trigger';
252 $attributes['data-href'] = 'javascript:' . $jsCode;
253 $attributes['data-severity'] = 'warning';
254 $attributes['data-content'] = $warning;
255 } else {
256 $attributes['class'] = 'btn btn-default';
257 $attributes['onclick'] = $jsCode . 'return false;';
258 }
259
260 $attributesString = '';
261 foreach ($attributes as $key => $value) {
262 $attributesString .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
263 }
264 return '<a href="#" ' . $attributesString . '>' . $string . '</a>';
265 }
266
267 /**
268 * Returns a table with directories and files listed.
269 *
270 * @param array $rowlist Array of files from path
271 * @return string HTML-table
272 */
273 public function getTable($rowlist)
274 {
275 // prepare space icon
276 $this->spaceIcon = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
277
278 // @todo use folder methods directly when they support filters
279 $storage = $this->folderObject->getStorage();
280 $storage->resetFileAndFolderNameFiltersToDefault();
281
282 // Only render the contents of a browsable storage
283 if ($this->folderObject->getStorage()->isBrowsable()) {
284 try {
285 $foldersCount = $storage->countFoldersInFolder($this->folderObject);
286 $filesCount = $storage->countFilesInFolder($this->folderObject);
287 } catch (InsufficientFolderAccessPermissionsException $e) {
288 $foldersCount = 0;
289 $filesCount = 0;
290 }
291
292 if ($foldersCount <= $this->firstElementNumber) {
293 $foldersFrom = false;
294 $foldersNum = false;
295 } else {
296 $foldersFrom = $this->firstElementNumber;
297 if ($this->firstElementNumber + $this->iLimit > $foldersCount) {
298 $foldersNum = $foldersCount - $this->firstElementNumber;
299 } else {
300 $foldersNum = $this->iLimit;
301 }
302 }
303 if ($foldersCount >= $this->firstElementNumber + $this->iLimit) {
304 $filesFrom = false;
305 $filesNum = false;
306 } else {
307 if ($this->firstElementNumber <= $foldersCount) {
308 $filesFrom = 0;
309 $filesNum = $this->iLimit - $foldersNum;
310 } else {
311 $filesFrom = $this->firstElementNumber - $foldersCount;
312 if ($filesFrom + $this->iLimit > $filesCount) {
313 $filesNum = $filesCount - $filesFrom;
314 } else {
315 $filesNum = $this->iLimit;
316 }
317 }
318 }
319
320 $folders = $storage->getFoldersInFolder($this->folderObject, $foldersFrom, $foldersNum, true, false, trim($this->sort), (bool)$this->sortRev);
321 $files = $this->folderObject->getFiles($filesFrom, $filesNum, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, false, trim($this->sort), (bool)$this->sortRev);
322 $this->totalItems = $foldersCount + $filesCount;
323 // Adds the code of files/dirs
324 $out = '';
325 $titleCol = 'file';
326 // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
327 $rowlist = '_LOCALIZATION_,' . $rowlist;
328 $rowlist = GeneralUtility::rmFromList($titleCol, $rowlist);
329 $rowlist = GeneralUtility::uniqueList($rowlist);
330 $rowlist = $rowlist ? $titleCol . ',' . $rowlist : $titleCol;
331 if ($this->clipBoard) {
332 $rowlist = str_replace('_LOCALIZATION_,', '_LOCALIZATION_,_CLIPBOARD_,', $rowlist);
333 $this->addElement_tdCssClass['_CLIPBOARD_'] = 'col-clipboard';
334 }
335 if ($this->bigControlPanel) {
336 $rowlist = str_replace('_LOCALIZATION_,', '_LOCALIZATION_,_CONTROL_,', $rowlist);
337 $this->addElement_tdCssClass['_CONTROL_'] = 'col-control';
338 }
339 $this->fieldArray = explode(',', $rowlist);
340
341 // Add classes to table cells
342 $this->addElement_tdCssClass[$titleCol] = 'col-title col-responsive';
343 $this->addElement_tdCssClass['_LOCALIZATION_'] = 'col-localizationa';
344
345 $folders = ListUtility::resolveSpecialFolderNames($folders);
346
347 $iOut = '';
348 // Directories are added
349 $this->eCounter = $this->firstElementNumber;
350 list(, $code) = $this->fwd_rwd_nav();
351 $iOut .= $code;
352
353 $iOut .= $this->formatDirList($folders);
354 // Files are added
355 $iOut .= $this->formatFileList($files);
356
357 $this->eCounter = $this->firstElementNumber + $this->iLimit < $this->totalItems
358 ? $this->firstElementNumber + $this->iLimit
359 : -1;
360 list(, $code) = $this->fwd_rwd_nav();
361 $iOut .= $code;
362
363 // Header line is drawn
364 $theData = [];
365 foreach ($this->fieldArray as $v) {
366 if ($v === '_CLIPBOARD_' && $this->clipBoard) {
367 $cells = [];
368 $table = '_FILE';
369 $elFromTable = $this->clipObj->elFromTable($table);
370 if (!empty($elFromTable) && $this->folderObject->checkActionPermission('write')) {
371 $addPasteButton = true;
372 $elToConfirm = [];
373 foreach ($elFromTable as $key => $element) {
374 $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
375 if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $this->folderObject)) {
376 $addPasteButton = false;
377 }
378 $elToConfirm[$key] = $clipBoardElement->getName();
379 }
380 if ($addPasteButton) {
381 $cells[] = '<a class="btn btn-default t3js-modal-trigger"' .
382 ' href="' . htmlspecialchars($this->clipObj->pasteUrl(
383 '_FILE',
384 $this->folderObject->getCombinedIdentifier()
385 )) . '"'
386 . ' data-content="' . htmlspecialchars($this->clipObj->confirmMsgText(
387 '_FILE',
388 $this->path,
389 'into',
390 $elToConfirm
391 )) . '"'
392 . ' data-severity="warning"'
393 . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_paste')) . '"'
394 . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_paste')) . '">'
395 . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)
396 ->render()
397 . '</a>';
398 }
399 }
400 if ($this->clipObj->current !== 'normal' && $iOut) {
401 $cells[] = $this->linkClipboardHeaderIcon('<span title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_selectMarked')) . '">' . $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render() . '</span>', $table, 'setCB');
402 $cells[] = $this->linkClipboardHeaderIcon('<span title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_deleteMarked')) . '">' . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(), $table, 'delete', $this->getLanguageService()->getLL('clip_deleteMarkedWarning'));
403 $onClick = 'checkOffCB(' . GeneralUtility::quoteJSvalue(implode(',', $this->CBnames)) . ', this); return false;';
404 $cells[] = '<a class="btn btn-default" rel="" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_markRecords')) . '">' . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
405 }
406 $theData[$v] = implode('', $cells);
407 } else {
408 // Normal row:
409 $theT = $this->linkWrapSort(htmlspecialchars($this->getLanguageService()->getLL('c_' . $v)), $this->folderObject->getCombinedIdentifier(), $v);
410 $theData[$v] = $theT;
411 }
412 }
413
414 $out .= '<thead>' . $this->addElement(1, '', $theData, '', '', '', 'th') . '</thead>';
415 $out .= '<tbody>' . $iOut . '</tbody>';
416 // half line is drawn
417 // finish
418 $out = '
419 <!--
420 Filelist table:
421 -->
422 <div class="table-fit">
423 <table class="table table-striped table-hover" id="typo3-filelist">
424 ' . $out . '
425 </table>
426 </div>';
427 } else {
428 /** @var $flashMessage FlashMessage */
429 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $this->getLanguageService()->getLL('storageNotBrowsableMessage'), $this->getLanguageService()->getLL('storageNotBrowsableTitle'), FlashMessage::INFO);
430 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
431 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
432 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
433 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
434 $defaultFlashMessageQueue->enqueue($flashMessage);
435 $out = '';
436 }
437 return $out;
438 }
439
440 /**
441 * If there is a parent folder and user has access to it, return an icon
442 * which is linked to the filelist of the parent folder.
443 *
444 * @param Folder $currentFolder
445 * @return string
446 */
447 protected function getLinkToParentFolder(Folder $currentFolder)
448 {
449 $levelUp = '';
450 try {
451 $currentStorage = $currentFolder->getStorage();
452 $parentFolder = $currentFolder->getParentFolder();
453 if ($parentFolder->getIdentifier() !== $currentFolder->getIdentifier() && $currentStorage->isWithinFileMountBoundaries($parentFolder)) {
454 $levelUp = $this->linkWrapDir(
455 '<span title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.upOneLevel')) . '">'
456 . $this->iconFactory->getIcon('actions-view-go-up', Icon::SIZE_SMALL)->render()
457 . '</span>',
458 $parentFolder
459 );
460 }
461 } catch (\Exception $e) {
462 }
463 return $levelUp;
464 }
465
466 /**
467 * Gets the number of files and total size of a folder
468 *
469 * @return string
470 */
471 public function getFolderInfo()
472 {
473 if ($this->counter == 1) {
474 $fileLabel = htmlspecialchars($this->getLanguageService()->getLL('file'));
475 } else {
476 $fileLabel = htmlspecialchars($this->getLanguageService()->getLL('files'));
477 }
478 return $this->counter . ' ' . $fileLabel . ', ' . GeneralUtility::formatSize($this->totalbytes, htmlspecialchars($this->getLanguageService()->getLL('byteSizeUnits')));
479 }
480
481 /**
482 * This returns tablerows for the directories in the array $items['sorting'].
483 *
484 * @param Folder[] $folders Folders of \TYPO3\CMS\Core\Resource\Folder
485 * @return string HTML table rows.
486 */
487 public function formatDirList(array $folders)
488 {
489 $out = '';
490 foreach ($folders as $folderName => $folderObject) {
491 $role = $folderObject->getRole();
492 if ($role === FolderInterface::ROLE_PROCESSING) {
493 // don't show processing-folder
494 continue;
495 }
496 if ($role !== FolderInterface::ROLE_DEFAULT) {
497 $displayName = '<strong>' . htmlspecialchars($folderName) . '</strong>';
498 } else {
499 $displayName = htmlspecialchars($folderName);
500 }
501
502 $isLocked = $folderObject instanceof InaccessibleFolder;
503 $isWritable = $folderObject->checkActionPermission('write');
504
505 // Initialization
506 $this->counter++;
507
508 // The icon with link
509 $theIcon = '<span title="' . htmlspecialchars($folderName) . '">' . $this->iconFactory->getIconForResource($folderObject, Icon::SIZE_SMALL)->render() . '</span>';
510 if (!$isLocked) {
511 $theIcon = BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $folderObject->getCombinedIdentifier());
512 }
513
514 // Preparing and getting the data-array
515 $theData = [];
516 if ($isLocked) {
517 foreach ($this->fieldArray as $field) {
518 $theData[$field] = '';
519 }
520 $theData['file'] = $displayName;
521 } else {
522 foreach ($this->fieldArray as $field) {
523 switch ($field) {
524 case 'size':
525 try {
526 $numFiles = $folderObject->getFileCount();
527 } catch (InsufficientFolderAccessPermissionsException $e) {
528 $numFiles = 0;
529 }
530 $theData[$field] = $numFiles . ' ' . htmlspecialchars($this->getLanguageService()->getLL(($numFiles === 1 ? 'file' : 'files')));
531 break;
532 case 'rw':
533 $theData[$field] = '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('read')) . '</strong>' . (!$isWritable ? '' : '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('write')) . '</strong>');
534 break;
535 case 'fileext':
536 $theData[$field] = htmlspecialchars($this->getLanguageService()->getLL('folder'));
537 break;
538 case 'tstamp':
539 $tstamp = $folderObject->getModificationTime();
540 $theData[$field] = $tstamp ? BackendUtility::date($tstamp) : '-';
541 break;
542 case 'file':
543 $theData[$field] = $this->linkWrapDir($displayName, $folderObject);
544 break;
545 case '_CONTROL_':
546 $theData[$field] = $this->makeEdit($folderObject);
547 break;
548 case '_CLIPBOARD_':
549 $theData[$field] = $this->makeClip($folderObject);
550 break;
551 case '_REF_':
552 $theData[$field] = $this->makeRef($folderObject);
553 break;
554 default:
555 $theData[$field] = GeneralUtility::fixed_lgd_cs($theData[$field], $this->fixedL);
556 }
557 }
558 }
559 $out .= $this->addElement(1, $theIcon, $theData);
560 }
561 return $out;
562 }
563
564 /**
565 * Wraps the directory-titles
566 *
567 * @param string $title String to be wrapped in links
568 * @param Folder $folderObject Folder to work on
569 * @return string HTML
570 */
571 public function linkWrapDir($title, Folder $folderObject)
572 {
573 $href = BackendUtility::getModuleUrl('file_FilelistList', ['id' => $folderObject->getCombinedIdentifier()]);
574 $onclick = ' onclick="' . htmlspecialchars(('top.document.getElementsByName("navigation")[0].contentWindow.Tree.highlightActiveItem("file","folder' . GeneralUtility::md5int($folderObject->getCombinedIdentifier()) . '_"+top.fsMod.currentBank)')) . '"';
575 // Sometimes $code contains plain HTML tags. In such a case the string should not be modified!
576 if ((string)$title === strip_tags($title)) {
577 return '<a href="' . htmlspecialchars($href) . '"' . $onclick . ' title="' . htmlspecialchars($title) . '">' . $title . '</a>';
578 }
579 return '<a href="' . htmlspecialchars($href) . '"' . $onclick . '>' . $title . '</a>';
580 }
581
582 /**
583 * Wraps filenames in links which opens the metadata editor.
584 *
585 * @param string $code String to be wrapped in links
586 * @param File $fileObject File to be linked
587 * @return string HTML
588 */
589 public function linkWrapFile($code, File $fileObject)
590 {
591 try {
592 if ($fileObject instanceof File && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
593 $metaData = $fileObject->_getMetaData();
594 $urlParameters = [
595 'edit' => [
596 'sys_file_metadata' => [
597 $metaData['uid'] => 'edit'
598 ]
599 ],
600 'returnUrl' => $this->listURL()
601 ];
602 $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
603 $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.editMetadata'));
604 $code = '<a class="responsive-title" href="' . htmlspecialchars($url) . '" title="' . $title . '">' . $code . '</a>';
605 }
606 } catch (\Exception $e) {
607 // intentional fall-through
608 }
609 return $code;
610 }
611
612 /**
613 * Returns list URL; This is the URL of the current script with id and imagemode parameters, that's all.
614 * The URL however is not relative, otherwise GeneralUtility::sanitizeLocalUrl() would say that
615 * the URL would be invalid
616 *
617 * @param string $altId
618 * @param string $table Table name to display. Enter "-1" for the current table.
619 * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
620 *
621 * @return string URL
622 */
623 public function listURL($altId = '', $table = '-1', $exclList = '')
624 {
625 return GeneralUtility::linkThisScript([
626 'target' => rawurlencode($this->folderObject->getCombinedIdentifier()),
627 'imagemode' => $this->thumbs
628 ]);
629 }
630
631 /**
632 * This returns tablerows for the files in the array $items['sorting'].
633 *
634 * @param File[] $files File items
635 * @return string HTML table rows.
636 */
637 public function formatFileList(array $files)
638 {
639 $out = '';
640 // first two keys are "0" (default) and "-1" (multiple), after that comes the "other languages"
641 $allSystemLanguages = GeneralUtility::makeInstance(TranslationConfigurationProvider::class)->getSystemLanguages();
642 $systemLanguages = array_filter($allSystemLanguages, function ($languageRecord) {
643 if ($languageRecord['uid'] === -1 || $languageRecord['uid'] === 0 || !$this->getBackendUser()->checkLanguageAccess($languageRecord['uid'])) {
644 return false;
645 }
646 return true;
647 });
648
649 foreach ($files as $fileObject) {
650 // Initialization
651 $this->counter++;
652 $this->totalbytes += $fileObject->getSize();
653 $ext = $fileObject->getExtension();
654 $fileName = trim($fileObject->getName());
655 // The icon with link
656 $theIcon = '<span title="' . htmlspecialchars($fileName . ' [' . (int)$fileObject->getUid() . ']') . '">'
657 . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render() . '</span>';
658 $theIcon = BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $fileObject->getCombinedIdentifier());
659 // Preparing and getting the data-array
660 $theData = [];
661 foreach ($this->fieldArray as $field) {
662 switch ($field) {
663 case 'size':
664 $theData[$field] = GeneralUtility::formatSize($fileObject->getSize(), htmlspecialchars($this->getLanguageService()->getLL('byteSizeUnits')));
665 break;
666 case 'rw':
667 $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>');
668 break;
669 case 'fileext':
670 $theData[$field] = strtoupper($ext);
671 break;
672 case 'tstamp':
673 $theData[$field] = BackendUtility::date($fileObject->getModificationTime());
674 break;
675 case '_CONTROL_':
676 $theData[$field] = $this->makeEdit($fileObject);
677 break;
678 case '_CLIPBOARD_':
679 $theData[$field] = $this->makeClip($fileObject);
680 break;
681 case '_LOCALIZATION_':
682 if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
683 $metaDataRecord = $fileObject->_getMetaData();
684 $translations = $this->getTranslationsForMetaData($metaDataRecord);
685 $languageCode = '';
686
687 foreach ($systemLanguages as $language) {
688 $languageId = $language['uid'];
689 $flagIcon = $language['flagIcon'];
690 if (array_key_exists($languageId, $translations)) {
691 $title = htmlspecialchars(sprintf($this->getLanguageService()->getLL('editMetadataForLanguage'), $language['title']));
692 // @todo the overlay for the flag needs to be added ($flagIcon . '-overlay')
693 $urlParameters = [
694 'edit' => [
695 'sys_file_metadata' => [
696 $translations[$languageId]['uid'] => 'edit'
697 ]
698 ],
699 'returnUrl' => $this->listURL()
700 ];
701 $flagButtonIcon = $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-edit')->render();
702 $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
703 $languageCode .= '<a href="' . htmlspecialchars($url) . '" class="btn btn-default" title="' . $title . '">'
704 . $flagButtonIcon . '</a>';
705 } else {
706 $parameters = [
707 'justLocalized' => 'sys_file_metadata:' . $metaDataRecord['uid'] . ':' . $languageId,
708 'returnUrl' => $this->listURL()
709 ];
710 $returnUrl = BackendUtility::getModuleUrl('record_edit', $parameters);
711 $href = BackendUtility::getLinkToDataHandlerAction(
712 '&cmd[sys_file_metadata][' . $metaDataRecord['uid'] . '][localize]=' . $languageId,
713 $returnUrl
714 );
715 $flagButtonIcon = '<span title="' . htmlspecialchars(sprintf($this->getLanguageService()->getLL('createMetadataForLanguage'), $language['title'])) . '">' . $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-new')->render() . '</span>';
716 $languageCode .= '<a href="' . htmlspecialchars($href) . '" class="btn btn-default">' . $flagButtonIcon . '</a> ';
717 }
718 }
719
720 // Hide flag button bar when not translated yet
721 $theData[$field] = ' <div class="localisationData btn-group" data-fileid="' . $fileObject->getUid() . '"' .
722 (empty($translations) ? ' style="display: none;"' : '') . '>' . $languageCode . '</div>';
723 $theData[$field] .= '<a class="btn btn-default filelist-translationToggler" data-fileid="' . $fileObject->getUid() . '">' .
724 '<span title="' . htmlspecialchars($this->getLanguageService()->getLL('translateMetadata')) . '">'
725 . $this->iconFactory->getIcon('mimetypes-x-content-page-language-overlay', Icon::SIZE_SMALL)->render() . '</span>'
726 . '</a>';
727 }
728 break;
729 case '_REF_':
730 $theData[$field] = $this->makeRef($fileObject);
731 break;
732 case 'file':
733 // Edit metadata of file
734 $theData[$field] = $this->linkWrapFile(htmlspecialchars($fileName), $fileObject);
735
736 if ($fileObject->isMissing()) {
737 $theData[$field] .= '<span class="label label-danger label-space-left">'
738 . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
739 . '</span>';
740 // Thumbnails?
741 } elseif ($this->thumbs && ($this->isImage($ext) || $this->isMediaFile($ext))) {
742 $processedFile = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, []);
743 if ($processedFile) {
744 $thumbUrl = $processedFile->getPublicUrl(true);
745 $theData[$field] .= '<br /><img src="' . $thumbUrl . '" ' .
746 'width="' . $processedFile->getProperty('width') . '" ' .
747 'height="' . $processedFile->getProperty('height') . '" ' .
748 'title="' . htmlspecialchars($fileName) . '" alt="" />';
749 }
750 }
751 break;
752 default:
753 $theData[$field] = '';
754 if ($fileObject->hasProperty($field)) {
755 $theData[$field] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getProperty($field), $this->fixedL));
756 }
757 }
758 }
759 $out .= $this->addElement(1, $theIcon, $theData);
760 }
761 return $out;
762 }
763
764 /**
765 * Fetch the translations for a sys_file_metadata record
766 *
767 * @param $metaDataRecord
768 * @return array keys are the sys_language uids, values are the $rows
769 */
770 protected function getTranslationsForMetaData($metaDataRecord)
771 {
772 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_metadata');
773 $queryBuilder->getRestrictions()->removeAll();
774 $translationRecords = $queryBuilder->select('*')
775 ->from('sys_file_metadata')
776 ->where(
777 $queryBuilder->expr()->eq(
778 $GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'],
779 $queryBuilder->createNamedParameter($metaDataRecord['uid'], \PDO::PARAM_INT)
780 ),
781 $queryBuilder->expr()->gt(
782 $GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'],
783 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
784 )
785 )
786 ->execute()
787 ->fetchAll();
788
789 $translations = [];
790 foreach ($translationRecords as $record) {
791 $translations[$record[$GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField']]] = $record;
792 }
793 return $translations;
794 }
795
796 /**
797 * Returns TRUE if $ext is an image-extension according to $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
798 *
799 * @param string $ext File extension
800 * @return bool
801 */
802 public function isImage($ext)
803 {
804 return GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($ext));
805 }
806
807 /**
808 * Returns TRUE if $ext is an media-extension according to $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']
809 *
810 * @param string $ext File extension
811 * @return bool
812 */
813 public function isMediaFile($ext)
814 {
815 return GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'], strtolower($ext));
816 }
817
818 /**
819 * Wraps the directory-titles ($code) in a link to filelist/Modules/Filelist/index.php (id=$path) and sorting commands...
820 *
821 * @param string $code String to be wrapped
822 * @param string $folderIdentifier ID (path)
823 * @param string $col Sorting column
824 * @return string HTML
825 */
826 public function linkWrapSort($code, $folderIdentifier, $col)
827 {
828 $params = ['id' => $folderIdentifier, 'SET' => [ 'sort' => $col ]];
829
830 if ($this->sort === $col) {
831 // Check reverse sorting
832 $params['SET']['reverse'] = ($this->sortRev ? '0' : '1');
833 $sortArrow = $this->iconFactory->getIcon('status-status-sorting-light-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render();
834 } else {
835 $params['SET']['reverse'] = 0;
836 $sortArrow = '';
837 }
838 $href = BackendUtility::getModuleUrl('file_FilelistList', $params);
839 return '<a href="' . htmlspecialchars($href) . '">' . $code . ' ' . $sortArrow . '</a>';
840 }
841
842 /**
843 * Creates the clipboard control pad
844 *
845 * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the clipboard panel for the listing.
846 * @return string HTML-table
847 */
848 public function makeClip($fileOrFolderObject)
849 {
850 if (!$fileOrFolderObject->checkActionPermission('read')) {
851 return '';
852 }
853 $cells = [];
854 $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
855 $fullName = $fileOrFolderObject->getName();
856 $md5 = GeneralUtility::shortMD5($fullIdentifier);
857 // For normal clipboard, add copy/cut buttons:
858 if ($this->clipObj->current === 'normal') {
859 $isSel = $this->clipObj->isSelected('_FILE', $md5);
860 $copyTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy'));
861 $cutTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut'));
862 $copyIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render();
863 $cutIcon = $this->iconFactory->getIcon('actions-edit-cut', Icon::SIZE_SMALL)->render();
864
865 if ($isSel === 'copy') {
866 $copyIcon = $this->iconFactory->getIcon('actions-edit-copy-release', Icon::SIZE_SMALL)->render();
867 } elseif ($isSel === 'cut') {
868 $cutIcon = $this->iconFactory->getIcon('actions-edit-cut-release', Icon::SIZE_SMALL)->render();
869 }
870
871 $cells[] = '<a class="btn btn-default"" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 1, ($isSel === 'copy'))) . '" title="' . $copyTitle . '">' . $copyIcon . '</a>';
872 // we can only cut if file can be moved
873 if ($fileOrFolderObject->checkActionPermission('move')) {
874 $cells[] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 0, ($isSel === 'cut'))) . '" title="' . $cutTitle . '">' . $cutIcon . '</a>';
875 } else {
876 $cells[] = $this->spaceIcon;
877 }
878 } else {
879 // For numeric pads, add select checkboxes:
880 $n = '_FILE|' . $md5;
881 $this->CBnames[] = $n;
882 $checked = $this->clipObj->isSelected('_FILE', $md5) ? ' checked="checked"' : '';
883 $cells[] = '<input type="hidden" name="CBH[' . $n . ']" value="0" /><label class="btn btn-default btn-checkbox"><input type="checkbox" name="CBC[' . $n . ']" value="' . htmlspecialchars($fullIdentifier) . '" ' . $checked . ' /><span class="t3-icon fa"></span></label>';
884 }
885 // Display PASTE button, if directory:
886 $elFromTable = $this->clipObj->elFromTable('_FILE');
887 if ($fileOrFolderObject instanceof Folder && !empty($elFromTable) && $fileOrFolderObject->checkActionPermission('write')) {
888 $addPasteButton = true;
889 $elToConfirm = [];
890 foreach ($elFromTable as $key => $element) {
891 $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
892 if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $fileOrFolderObject)) {
893 $addPasteButton = false;
894 }
895 $elToConfirm[$key] = $clipBoardElement->getName();
896 }
897 if ($addPasteButton) {
898 $cells[] = '<a class="btn btn-default t3js-modal-trigger" '
899 . ' href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $fullIdentifier)) . '"'
900 . ' data-content="' . htmlspecialchars($this->clipObj->confirmMsgText('_FILE', $fullName, 'into', $elToConfirm)) . '"'
901 . ' data-severity="warning"'
902 . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteInto')) . '"'
903 . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteInto')) . '"'
904 . '>'
905 . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
906 . '</a>';
907 }
908 }
909 // Compile items into a DIV-element:
910 return ' <div class="btn-group" role="group">' . implode('', $cells) . '</div>';
911 }
912
913 /**
914 * Creates the edit control section
915 *
916 * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the edit control section for the listing.
917 * @return string HTML-table
918 */
919 public function makeEdit($fileOrFolderObject)
920 {
921 $cells = [];
922 $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
923 $md5 = GeneralUtility::shortMD5($fullIdentifier);
924 $isSel = $this->clipObj->isSelected('_FILE', $md5);
925
926 // Edit file content (if editable)
927 if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('write') && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], $fileOrFolderObject->getExtension())) {
928 $url = BackendUtility::getModuleUrl('file_edit', ['target' => $fullIdentifier]);
929 $editOnClick = 'top.list_frame.location.href=' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+top.rawurlencode(top.list_frame.document.location.pathname+top.list_frame.document.location.search);return false;';
930 $cells['edit'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($editOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.editcontent') . '">'
931 . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render()
932 . '</a>';
933 } else {
934 $cells['edit'] = $this->spaceIcon;
935 }
936
937 // Edit metadata of file
938 if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
939 $metaData = $fileOrFolderObject->_getMetaData();
940 $urlParameters = [
941 'edit' => [
942 'sys_file_metadata' => [
943 $metaData['uid'] => 'edit'
944 ]
945 ],
946 'returnUrl' => $this->listURL()
947 ];
948 $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
949 $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.editMetadata'));
950 $cells['metadata'] = '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" title="' . $title . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
951 }
952
953 // document view
954 if ($fileOrFolderObject instanceof File) {
955 $fileUrl = $fileOrFolderObject->getPublicUrl(true);
956 if ($fileUrl) {
957 $aOnClick = 'return top.openUrlInWindow(' . GeneralUtility::quoteJSvalue($fileUrl) . ', \'WebFile\');';
958 $cells['view'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($aOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.view') . '">' . $this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL)->render() . '</a>';
959 } else {
960 $cells['view'] = $this->spaceIcon;
961 }
962 } else {
963 $cells['view'] = $this->spaceIcon;
964 }
965
966 // replace file
967 if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('replace')) {
968 $url = BackendUtility::getModuleUrl('file_replace', ['target' => $fullIdentifier, 'uid' => $fileOrFolderObject->getUid()]);
969 $replaceOnClick = 'top.list_frame.location.href = ' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+top.rawurlencode(top.list_frame.document.location.pathname+top.list_frame.document.location.search);return false;';
970 $cells['replace'] = '<a href="#" class="btn btn-default" onclick="' . $replaceOnClick . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.replace') . '">' . $this->iconFactory->getIcon('actions-edit-replace', Icon::SIZE_SMALL)->render() . '</a>';
971 }
972
973 // rename the file
974 if ($fileOrFolderObject->checkActionPermission('rename')) {
975 $url = BackendUtility::getModuleUrl('file_rename', ['target' => $fullIdentifier]);
976 $renameOnClick = 'top.list_frame.location.href = ' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+top.rawurlencode(top.list_frame.document.location.pathname+top.list_frame.document.location.search);return false;';
977 $cells['rename'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($renameOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.rename') . '">' . $this->iconFactory->getIcon('actions-edit-rename', Icon::SIZE_SMALL)->render() . '</a>';
978 } else {
979 $cells['rename'] = $this->spaceIcon;
980 }
981 if ($fileOrFolderObject->checkActionPermission('read')) {
982 $infoOnClick = '';
983 if ($fileOrFolderObject instanceof Folder) {
984 $infoOnClick = 'top.launchView( \'_FOLDER\', ' . GeneralUtility::quoteJSvalue($fullIdentifier) . ');return false;';
985 } elseif ($fileOrFolderObject instanceof File) {
986 $infoOnClick = 'top.launchView( \'_FILE\', ' . GeneralUtility::quoteJSvalue($fullIdentifier) . ');return false;';
987 }
988 $cells['info'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($infoOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.info') . '">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>';
989 } else {
990 $cells['info'] = $this->spaceIcon;
991 }
992
993 // copy the file
994 if ($fileOrFolderObject->checkActionPermission('copy') && $this->clipObj->current === 'normal') {
995 $copyTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy'));
996 $copyIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render();
997
998 if ($isSel === 'copy') {
999 $copyIcon = $this->iconFactory->getIcon('actions-edit-copy-release', Icon::SIZE_SMALL)->render();
1000 }
1001
1002 $cells['copy'] = '<a class="btn btn-default"" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 1, ($isSel === 'copy'))) . '" title="' . $copyTitle . '">' . $copyIcon . '</a>';
1003 }
1004
1005 // cut the file
1006 if ($fileOrFolderObject->checkActionPermission('move') && $this->clipObj->current === 'normal') {
1007 $cutTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut'));
1008 $cutIcon = $this->iconFactory->getIcon('actions-edit-cut', Icon::SIZE_SMALL)->render();
1009
1010 if ($isSel === 'cut') {
1011 $cutIcon = $this->iconFactory->getIcon('actions-edit-cut-release', Icon::SIZE_SMALL)->render();
1012 }
1013
1014 $cells['cut'] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 0, ($isSel === 'cut'))) . '" title="' . $cutTitle . '">' . $cutIcon . '</a>';
1015 }
1016
1017 // delete the file
1018 if ($fileOrFolderObject->checkActionPermission('delete')) {
1019 $identifier = $fileOrFolderObject->getIdentifier();
1020 if ($fileOrFolderObject instanceof Folder) {
1021 $referenceCountText = BackendUtility::referenceCount('_FILE', $identifier, ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToFolder'));
1022 $deleteType = 'delete_folder';
1023 } else {
1024 $referenceCountText = BackendUtility::referenceCount('sys_file', $fileOrFolderObject->getUid(), ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile'));
1025 $deleteType = 'delete_file';
1026 }
1027
1028 if ($this->getBackendUser()->jsConfirmation(JsConfirmation::DELETE)) {
1029 $confirmationCheck = '1';
1030 } else {
1031 $confirmationCheck = '0';
1032 }
1033
1034 $deleteUrl = BackendUtility::getModuleUrl('tce_file');
1035 $confirmationMessage = sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.delete'), $fileOrFolderObject->getName()) . $referenceCountText;
1036 $title = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.delete');
1037 $cells['delete'] = '<a href="#" class="btn btn-default t3js-filelist-delete" data-content="' . htmlspecialchars($confirmationMessage)
1038 . '" data-check="' . $confirmationCheck
1039 . '" data-delete-url="' . htmlspecialchars($deleteUrl)
1040 . '" data-title="' . htmlspecialchars($title)
1041 . '" data-identifier="' . htmlspecialchars($fileOrFolderObject->getCombinedIdentifier())
1042 . '" data-delete-type="' . $deleteType
1043 . '" title="' . htmlspecialchars($title) . '">'
1044 . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
1045 } else {
1046 $cells['delete'] = $this->spaceIcon;
1047 }
1048
1049 // Hook for manipulating edit icons.
1050 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['fileList']['editIconsHook'])) {
1051 $cells['__fileOrFolderObject'] = $fileOrFolderObject;
1052 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['fileList']['editIconsHook'] as $className) {
1053 $hookObject = GeneralUtility::makeInstance($className);
1054 if (!$hookObject instanceof FileListEditIconHookInterface) {
1055 throw new \UnexpectedValueException(
1056 $className . ' must implement interface ' . FileListEditIconHookInterface::class,
1057 1235225797
1058 );
1059 }
1060 $hookObject->manipulateEditIcons($cells, $this);
1061 }
1062 unset($cells['__fileOrFolderObject']);
1063 }
1064 // Compile items into a DIV-element:
1065 return '<div class="btn-group">' . implode('', $cells) . '</div>';
1066 }
1067
1068 /**
1069 * Make reference count
1070 *
1071 * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the clipboard panel for the listing.
1072 * @return string HTML
1073 */
1074 public function makeRef($fileOrFolderObject)
1075 {
1076 if ($fileOrFolderObject instanceof FolderInterface) {
1077 return '-';
1078 }
1079 // Look up the file in the sys_refindex.
1080 // Exclude sys_file_metadata records as these are no use references
1081 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
1082 $referenceCount = $queryBuilder->count('*')
1083 ->from('sys_refindex')
1084 ->where(
1085 $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
1086 $queryBuilder->expr()->eq(
1087 'ref_table',
1088 $queryBuilder->createNamedParameter('sys_file', \PDO::PARAM_STR)
1089 ),
1090 $queryBuilder->expr()->eq(
1091 'ref_uid',
1092 $queryBuilder->createNamedParameter($fileOrFolderObject->getUid(), \PDO::PARAM_INT)
1093 ),
1094 $queryBuilder->expr()->neq(
1095 'tablename',
1096 $queryBuilder->createNamedParameter('sys_file_metadata', \PDO::PARAM_STR)
1097 )
1098 )
1099 ->execute()
1100 ->fetchColumn();
1101
1102 return $this->generateReferenceToolTip($referenceCount, '\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileOrFolderObject->getCombinedIdentifier()));
1103 }
1104
1105 /**
1106 * Returns an instance of LanguageService
1107 *
1108 * @return \TYPO3\CMS\Core\Localization\LanguageService
1109 */
1110 protected function getLanguageService()
1111 {
1112 return $GLOBALS['LANG'];
1113 }
1114
1115 /**
1116 * Returns the current BE user.
1117 *
1118 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1119 */
1120 protected function getBackendUser()
1121 {
1122 return $GLOBALS['BE_USER'];
1123 }
1124 }