[TASK] Use BE Routing / PSR-7 instead of BackendUtility::getModuleUrl
[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\Routing\UriBuilder;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Database\ConnectionPool;
22 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
23 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24 use TYPO3\CMS\Core\Imaging\Icon;
25 use TYPO3\CMS\Core\Imaging\IconFactory;
26 use TYPO3\CMS\Core\Messaging\FlashMessage;
27 use TYPO3\CMS\Core\Messaging\FlashMessageService;
28 use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
29 use TYPO3\CMS\Core\Resource\File;
30 use TYPO3\CMS\Core\Resource\Folder;
31 use TYPO3\CMS\Core\Resource\FolderInterface;
32 use TYPO3\CMS\Core\Resource\InaccessibleFolder;
33 use TYPO3\CMS\Core\Resource\ProcessedFile;
34 use TYPO3\CMS\Core\Resource\ResourceFactory;
35 use TYPO3\CMS\Core\Resource\Utility\ListUtility;
36 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
37 use TYPO3\CMS\Core\Utility\GeneralUtility;
38 use TYPO3\CMS\Core\Utility\MathUtility;
39 use TYPO3\CMS\Filelist\Configuration\ThumbnailConfiguration;
40 use TYPO3\CMS\Filelist\Controller\FileListController;
41
42 /**
43 * Class for rendering of File>Filelist
44 */
45 class FileList
46 {
47 /**
48 * Default Max items shown
49 *
50 * @var int
51 */
52 public $iLimit = 40;
53
54 /**
55 * Thumbnails on records containing files (pictures)
56 *
57 * @var bool
58 */
59 public $thumbs = false;
60
61 /**
62 * Space icon used for alignment when no button is available
63 *
64 * @var string
65 */
66 public $spaceIcon;
67
68 /**
69 * Max length of strings
70 *
71 * @var int
72 */
73 public $fixedL = 30;
74
75 /**
76 * The field to sort by
77 *
78 * @var string
79 */
80 public $sort = '';
81
82 /**
83 * Reverse sorting flag
84 *
85 * @var bool
86 */
87 public $sortRev = 1;
88
89 /**
90 * @var int
91 */
92 public $firstElementNumber = 0;
93
94 /**
95 * @var bool
96 */
97 public $clipBoard = 0;
98
99 /**
100 * @var bool
101 */
102 public $bigControlPanel = 0;
103
104 /**
105 * @var string
106 */
107 public $JScode = '';
108
109 /**
110 * String with accumulated HTML content
111 *
112 * @var string
113 */
114 public $HTMLcode = '';
115
116 /**
117 * @var int
118 */
119 public $totalbytes = 0;
120
121 /**
122 * @var array
123 */
124 public $dirs = [];
125
126 /**
127 * @var array
128 */
129 public $files = [];
130
131 /**
132 * @var string
133 */
134 public $path = '';
135
136 /**
137 * OBSOLETE - NOT USED ANYMORE. leftMargin
138 *
139 * @var int
140 */
141 public $leftMargin = 0;
142
143 /**
144 * This could be set to the total number of items. Used by the fwd_rew_navigation...
145 *
146 * @var string
147 */
148 public $totalItems = '';
149
150 /**
151 * Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column.
152 *
153 * @var array
154 */
155 public $fieldArray = [];
156
157 /**
158 * Set to zero, if you don't want a left-margin with addElement function
159 *
160 * @var int
161 */
162 public $setLMargin = 1;
163
164 /**
165 * Contains page translation languages
166 *
167 * @var array
168 */
169 public $pageOverlays = [];
170
171 /**
172 * Counter increased for each element. Used to index elements for the JavaScript-code that transfers to the clipboard
173 *
174 * @var int
175 */
176 public $counter = 0;
177
178 /**
179 * Contains sys language icons and titles
180 *
181 * @var array
182 */
183 public $languageIconTitles = [];
184
185 /**
186 * Script URL
187 *
188 * @var string
189 */
190 public $thisScript = '';
191
192 /**
193 * If set this is <td> CSS-classname for odd columns in addElement. Used with db_layout / pages section
194 *
195 * @var string
196 */
197 public $oddColumnsCssClass = '';
198
199 /**
200 * Counting the elements no matter what
201 *
202 * @var int
203 */
204 public $eCounter = 0;
205
206 /**
207 * @var TranslationConfigurationProvider
208 */
209 public $translateTools;
210
211 /**
212 * Keys are fieldnames and values are td-parameters to add in addElement(), please use $addElement_tdCSSClass for CSS-classes;
213 *
214 * @var array
215 */
216 public $addElement_tdParams = [];
217
218 /**
219 * @var int
220 */
221 public $no_noWrap = 0;
222
223 /**
224 * @var int
225 */
226 public $showIcon = 1;
227
228 /**
229 * Keys are fieldnames and values are td-css-classes to add in addElement();
230 *
231 * @var array
232 */
233 public $addElement_tdCssClass = [];
234
235 /**
236 * @var Folder
237 */
238 protected $folderObject;
239
240 /**
241 * @var array
242 */
243 public $CBnames = [];
244
245 /**
246 * @var Clipboard $clipObj
247 */
248 public $clipObj;
249
250 /**
251 * @var ResourceFactory
252 */
253 protected $resourceFactory;
254
255 /**
256 * @var IconFactory
257 */
258 protected $iconFactory;
259
260 /**
261 * @var int
262 */
263 protected $id = 0;
264
265 /**
266 * @var FileListController
267 */
268 protected $fileListController;
269
270 /**
271 * @var ThumbnailConfiguration
272 */
273 protected $thumbnailConfiguration;
274
275 /**
276 * Construct
277 *
278 * @param FileListController $fileListController
279 */
280 public function __construct(FileListController $fileListController)
281 {
282 if (isset($GLOBALS['BE_USER']->uc['titleLen']) && $GLOBALS['BE_USER']->uc['titleLen'] > 0) {
283 $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
284 }
285 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
286 $this->getTranslateTools();
287 $this->determineScriptUrl();
288 $this->fileListController = $fileListController;
289 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
290 $this->thumbnailConfiguration = GeneralUtility::makeInstance(ThumbnailConfiguration::class);
291
292 $modTSconfig = BackendUtility::getModTSconfig(0, 'options.file_list');
293 if (!empty($modTSconfig['properties']['filesPerPage'])) {
294 $this->iLimit = MathUtility::forceIntegerInRange($modTSconfig['properties']['filesPerPage'], 1);
295 }
296 }
297
298 /**
299 * @param ResourceFactory $resourceFactory
300 */
301 public function injectResourceFactory(ResourceFactory $resourceFactory)
302 {
303 $this->resourceFactory = $resourceFactory;
304 }
305
306 /**
307 * Initialization of class
308 *
309 * @param Folder $folderObject The folder to work on
310 * @param int $pointer Pointer
311 * @param bool $sort Sorting column
312 * @param bool $sortRev Sorting direction
313 * @param bool $clipBoard
314 * @param bool $bigControlPanel Show clipboard flag
315 */
316 public function start(Folder $folderObject, $pointer, $sort, $sortRev, $clipBoard = false, $bigControlPanel = false)
317 {
318 $this->folderObject = $folderObject;
319 $this->counter = 0;
320 $this->totalbytes = 0;
321 $this->JScode = '';
322 $this->HTMLcode = '';
323 $this->path = $folderObject->getReadablePath();
324 $this->sort = $sort;
325 $this->sortRev = $sortRev;
326 $this->firstElementNumber = $pointer;
327 $this->clipBoard = $clipBoard;
328 $this->bigControlPanel = $bigControlPanel;
329 // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->fixedL)
330 $this->fixedL = max($this->fixedL, $this->getBackendUser()->uc['titleLen']);
331 $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_common.xlf');
332 $this->resourceFactory = ResourceFactory::getInstance();
333 }
334
335 /**
336 * Reading files and directories, counting elements and generating the list in ->HTMLcode
337 */
338 public function generateList()
339 {
340 $this->HTMLcode .= $this->getTable('fileext,tstamp,size,rw,_REF_');
341 }
342
343 /**
344 * Wrapping input string in a link with clipboard command.
345 *
346 * @param string $string String to be linked - must be htmlspecialchar'ed / prepared before.
347 * @param string $_ unused
348 * @param string $cmd "cmd" value
349 * @param string $warning Warning for JS confirm message
350 * @return string Linked string
351 */
352 public function linkClipboardHeaderIcon($string, $_, $cmd, $warning = '')
353 {
354 $jsCode = 'document.dblistForm.cmd.value=' . GeneralUtility::quoteJSvalue($cmd)
355 . ';document.dblistForm.submit();';
356
357 $attributes = [];
358 if ($warning) {
359 $attributes['class'] = 'btn btn-default t3js-modal-trigger';
360 $attributes['data-href'] = 'javascript:' . $jsCode;
361 $attributes['data-severity'] = 'warning';
362 $attributes['data-content'] = $warning;
363 } else {
364 $attributes['class'] = 'btn btn-default';
365 $attributes['onclick'] = $jsCode . 'return false;';
366 }
367
368 $attributesString = '';
369 foreach ($attributes as $key => $value) {
370 $attributesString .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
371 }
372 return '<a href="#" ' . $attributesString . '>' . $string . '</a>';
373 }
374
375 /**
376 * Returns a table with directories and files listed.
377 *
378 * @param array $rowlist Array of files from path
379 * @return string HTML-table
380 */
381 public function getTable($rowlist)
382 {
383 // prepare space icon
384 $this->spaceIcon = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
385
386 // @todo use folder methods directly when they support filters
387 $storage = $this->folderObject->getStorage();
388 $storage->resetFileAndFolderNameFiltersToDefault();
389
390 // Only render the contents of a browsable storage
391 if ($this->folderObject->getStorage()->isBrowsable()) {
392 try {
393 $foldersCount = $storage->countFoldersInFolder($this->folderObject);
394 $filesCount = $storage->countFilesInFolder($this->folderObject);
395 } catch (InsufficientFolderAccessPermissionsException $e) {
396 $foldersCount = 0;
397 $filesCount = 0;
398 }
399
400 if ($foldersCount <= $this->firstElementNumber) {
401 $foldersFrom = false;
402 $foldersNum = false;
403 } else {
404 $foldersFrom = $this->firstElementNumber;
405 if ($this->firstElementNumber + $this->iLimit > $foldersCount) {
406 $foldersNum = $foldersCount - $this->firstElementNumber;
407 } else {
408 $foldersNum = $this->iLimit;
409 }
410 }
411 if ($foldersCount >= $this->firstElementNumber + $this->iLimit) {
412 $filesFrom = false;
413 $filesNum = false;
414 } else {
415 if ($this->firstElementNumber <= $foldersCount) {
416 $filesFrom = 0;
417 $filesNum = $this->iLimit - $foldersNum;
418 } else {
419 $filesFrom = $this->firstElementNumber - $foldersCount;
420 if ($filesFrom + $this->iLimit > $filesCount) {
421 $filesNum = $filesCount - $filesFrom;
422 } else {
423 $filesNum = $this->iLimit;
424 }
425 }
426 }
427
428 $folders = $storage->getFoldersInFolder($this->folderObject, $foldersFrom, $foldersNum, true, false, trim($this->sort), (bool)$this->sortRev);
429 $files = $this->folderObject->getFiles($filesFrom, $filesNum, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, false, trim($this->sort), (bool)$this->sortRev);
430 $this->totalItems = $foldersCount + $filesCount;
431 // Adds the code of files/dirs
432 $out = '';
433 $titleCol = 'file';
434 // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
435 $rowlist = '_LOCALIZATION_,' . $rowlist;
436 $rowlist = GeneralUtility::rmFromList($titleCol, $rowlist);
437 $rowlist = GeneralUtility::uniqueList($rowlist);
438 $rowlist = $rowlist ? $titleCol . ',' . $rowlist : $titleCol;
439 if ($this->clipBoard) {
440 $rowlist = str_replace('_LOCALIZATION_,', '_LOCALIZATION_,_CLIPBOARD_,', $rowlist);
441 $this->addElement_tdCssClass['_CLIPBOARD_'] = 'col-clipboard';
442 }
443 if ($this->bigControlPanel) {
444 $rowlist = str_replace('_LOCALIZATION_,', '_LOCALIZATION_,_CONTROL_,', $rowlist);
445 $this->addElement_tdCssClass['_CONTROL_'] = 'col-control';
446 }
447 $this->fieldArray = explode(',', $rowlist);
448
449 // Add classes to table cells
450 $this->addElement_tdCssClass[$titleCol] = 'col-title col-responsive';
451 $this->addElement_tdCssClass['_LOCALIZATION_'] = 'col-localizationa';
452
453 $folders = ListUtility::resolveSpecialFolderNames($folders);
454
455 $iOut = '';
456 // Directories are added
457 $this->eCounter = $this->firstElementNumber;
458 list(, $code) = $this->fwd_rwd_nav();
459 $iOut .= $code;
460
461 $iOut .= $this->formatDirList($folders);
462 // Files are added
463 $iOut .= $this->formatFileList($files);
464
465 $this->eCounter = $this->firstElementNumber + $this->iLimit < $this->totalItems
466 ? $this->firstElementNumber + $this->iLimit
467 : -1;
468 list(, $code) = $this->fwd_rwd_nav();
469 $iOut .= $code;
470
471 // Header line is drawn
472 $theData = [];
473 foreach ($this->fieldArray as $v) {
474 if ($v === '_CLIPBOARD_' && $this->clipBoard) {
475 $cells = [];
476 $table = '_FILE';
477 $elFromTable = $this->clipObj->elFromTable($table);
478 if (!empty($elFromTable) && $this->folderObject->checkActionPermission('write')) {
479 $addPasteButton = true;
480 $elToConfirm = [];
481 foreach ($elFromTable as $key => $element) {
482 $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
483 if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $this->folderObject)) {
484 $addPasteButton = false;
485 }
486 $elToConfirm[$key] = $clipBoardElement->getName();
487 }
488 if ($addPasteButton) {
489 $cells[] = '<a class="btn btn-default t3js-modal-trigger"' .
490 ' href="' . htmlspecialchars($this->clipObj->pasteUrl(
491 '_FILE',
492 $this->folderObject->getCombinedIdentifier()
493 )) . '"'
494 . ' data-content="' . htmlspecialchars($this->clipObj->confirmMsgText(
495 '_FILE',
496 $this->path,
497 'into',
498 $elToConfirm
499 )) . '"'
500 . ' data-severity="warning"'
501 . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_paste')) . '"'
502 . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_paste')) . '">'
503 . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)
504 ->render()
505 . '</a>';
506 }
507 }
508 if ($this->clipObj->current !== 'normal' && $iOut) {
509 $cells[] = $this->linkClipboardHeaderIcon('<span title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_selectMarked')) . '">' . $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render() . '</span>', $table, 'setCB');
510 $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'));
511 $onClick = 'checkOffCB(' . GeneralUtility::quoteJSvalue(implode(',', $this->CBnames)) . ', this); return false;';
512 $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>';
513 }
514 $theData[$v] = implode('', $cells);
515 } else {
516 // Normal row:
517 $theT = $this->linkWrapSort(htmlspecialchars($this->getLanguageService()->getLL('c_' . $v)), $this->folderObject->getCombinedIdentifier(), $v);
518 $theData[$v] = $theT;
519 }
520 }
521
522 $out .= '<thead>' . $this->addElement(1, '', $theData, '', '', '', 'th') . '</thead>';
523 $out .= '<tbody>' . $iOut . '</tbody>';
524 // half line is drawn
525 // finish
526 $out = '
527 <!--
528 Filelist table:
529 -->
530 <div class="table-fit">
531 <table class="table table-striped table-hover" id="typo3-filelist">
532 ' . $out . '
533 </table>
534 </div>';
535 } else {
536 /** @var $flashMessage FlashMessage */
537 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $this->getLanguageService()->getLL('storageNotBrowsableMessage'), $this->getLanguageService()->getLL('storageNotBrowsableTitle'), FlashMessage::INFO);
538 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
539 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
540 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
541 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
542 $defaultFlashMessageQueue->enqueue($flashMessage);
543 $out = '';
544 }
545 return $out;
546 }
547
548 /**
549 * Returns a table-row with the content from the fields in the input data array.
550 * OBS: $this->fieldArray MUST be set! (represents the list of fields to display)
551 *
552 * @param int $h Is an integer >=0 and denotes how tall an element is. Set to '0' makes a halv line, -1 = full line, set to 1 makes a 'join' and above makes 'line'
553 * @param string $icon Is the <img>+<a> of the record. If not supplied the first 'join'-icon will be a 'line' instead
554 * @param array $data Is the dataarray, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in <td>-tags
555 * @param string $rowParams Is insert in the <tr>-tags. Must carry a ' ' as first character
556 * @param string $_ OBSOLETE - NOT USED ANYMORE. $lMargin is the leftMargin (int)
557 * @param string $_2 OBSOLETE - NOT USED ANYMORE. Is the HTML <img>-tag for an alternative 'gfx/ol/line.gif'-icon (used in the top)
558 * @param string $colType Defines the tag being used for the columns. Default is td.
559 *
560 * @return string HTML content for the table row
561 */
562 public function addElement($h, $icon, $data, $rowParams = '', $_ = '', $_2 = '', $colType = 'td')
563 {
564 $colType = ($colType === 'th') ? 'th' : 'td';
565 $noWrap = $this->no_noWrap ? '' : ' nowrap';
566 // Start up:
567 $l10nParent = (int)($data['_l10nparent_'] ?? 0);
568 $out = '
569 <!-- Element, begin: -->
570 <tr ' . $rowParams . ' data-uid="' . (int)($data['uid'] ?? 0) . '" data-l10nparent="' . $l10nParent . '">';
571 // Show icon and lines
572 if ($this->showIcon) {
573 $out .= '
574 <' . $colType . ' class="col-icon nowrap">';
575 if (!$h) {
576 $out .= '&nbsp;';
577 } else {
578 for ($a = 0; $a < $h; $a++) {
579 if (!$a) {
580 if ($icon) {
581 $out .= $icon;
582 }
583 }
584 }
585 }
586 $out .= '</' . $colType . '>
587 ';
588 }
589 // Init rendering.
590 $colsp = '';
591 $lastKey = '';
592 $c = 0;
593 $ccount = 0;
594 // __label is used as the label key to circumvent problems with uid used as label (see #67756)
595 // as it was introduced later on, check if it really exists before using it
596 $fields = $this->fieldArray;
597 if ($colType === 'td' && array_key_exists('__label', $data)) {
598 $fields[0] = '__label';
599 }
600 // Traverse field array which contains the data to present:
601 foreach ($fields as $vKey) {
602 if (isset($data[$vKey])) {
603 if ($lastKey) {
604 $cssClass = $this->addElement_tdCssClass[$lastKey] ?? '';
605 if ($this->oddColumnsCssClass && $ccount % 2 == 0) {
606 $cssClass = implode(' ', [$cssClass, $this->oddColumnsCssClass]);
607 }
608 $out .= '
609 <' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . ($this->addElement_tdParams[$lastKey] ?? '') . '>' . $data[$lastKey] . '</' . $colType . '>';
610 }
611 $lastKey = $vKey;
612 $c = 1;
613 $ccount++;
614 } else {
615 if (!$lastKey) {
616 $lastKey = $vKey;
617 }
618 $c++;
619 }
620 if ($c > 1) {
621 $colsp = ' colspan="' . $c . '"';
622 } else {
623 $colsp = '';
624 }
625 }
626 if ($lastKey) {
627 $cssClass = $this->addElement_tdCssClass[$lastKey] ?? '';
628 if ($this->oddColumnsCssClass) {
629 $cssClass = implode(' ', [$cssClass, $this->oddColumnsCssClass]);
630 }
631 $out .= '
632 <' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . ($this->addElement_tdParams[$lastKey] ?? '') . '>' . $data[$lastKey] . '</' . $colType . '>';
633 }
634 // End row
635 $out .= '
636 </tr>';
637 // Return row.
638 return $out;
639 }
640
641 /**
642 * Dummy function, used to write the top of a table listing.
643 */
644 public function writeTop()
645 {
646 }
647
648 /**
649 * Creates a forward/reverse button based on the status of ->eCounter, ->firstElementNumber, ->iLimit
650 *
651 * @param string $table Table name
652 * @return array array([boolean], [HTML]) where [boolean] is 1 for reverse element, [HTML] is the table-row code for the element
653 */
654 public function fwd_rwd_nav($table = '')
655 {
656 $code = '';
657 if ($this->eCounter >= $this->firstElementNumber && $this->eCounter < $this->firstElementNumber + $this->iLimit) {
658 if ($this->firstElementNumber && $this->eCounter == $this->firstElementNumber) {
659 // Reverse
660 $theData = [];
661 $titleCol = $this->fieldArray[0];
662 $theData[$titleCol] = $this->fwd_rwd_HTML('fwd', $this->eCounter, $table);
663 $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
664 }
665 return [1, $code];
666 }
667 if ($this->eCounter == $this->firstElementNumber + $this->iLimit) {
668 // Forward
669 $theData = [];
670 $titleCol = $this->fieldArray[0];
671 $theData[$titleCol] = $this->fwd_rwd_HTML('rwd', $this->eCounter, $table);
672 $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
673 }
674 return [0, $code];
675 }
676
677 /**
678 * Creates the button with link to either forward or reverse
679 *
680 * @param string $type Type: "fwd" or "rwd
681 * @param int $pointer Pointer
682 * @param string $table Table name
683 * @return string
684 * @access private
685 */
686 public function fwd_rwd_HTML($type, $pointer, $table = '')
687 {
688 $content = '';
689 $tParam = $table ? '&table=' . rawurlencode($table) : '';
690 switch ($type) {
691 case 'fwd':
692 $href = $this->listURL() . '&pointer=' . ($pointer - $this->iLimit) . $tParam;
693 $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
694 'actions-move-up',
695 Icon::SIZE_SMALL
696 )->render() . '</a> <i>[1 - ' . $pointer . ']</i>';
697 break;
698 case 'rwd':
699 $href = $this->listURL() . '&pointer=' . $pointer . $tParam;
700 $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
701 'actions-move-down',
702 Icon::SIZE_SMALL
703 )->render() . '</a> <i>[' . ($pointer + 1) . ' - ' . $this->totalItems . ']</i>';
704 break;
705 }
706 return $content;
707 }
708
709 /**
710 * Returning JavaScript for ClipBoard functionality.
711 *
712 * @return string
713 */
714 public function CBfunctions()
715 {
716 return '
717 // checkOffCB()
718 function checkOffCB(listOfCBnames, link) { //
719 var checkBoxes, flag, i;
720 var checkBoxes = listOfCBnames.split(",");
721 if (link.rel === "") {
722 link.rel = "allChecked";
723 flag = true;
724 } else {
725 link.rel = "";
726 flag = false;
727 }
728 for (i = 0; i < checkBoxes.length; i++) {
729 setcbValue(checkBoxes[i], flag);
730 }
731 }
732 // cbValue()
733 function cbValue(CBname) { //
734 var CBfullName = "CBC["+CBname+"]";
735 return (document.dblistForm[CBfullName] && document.dblistForm[CBfullName].checked ? 1 : 0);
736 }
737 // setcbValue()
738 function setcbValue(CBname,flag) { //
739 CBfullName = "CBC["+CBname+"]";
740 if(document.dblistForm[CBfullName]) {
741 document.dblistForm[CBfullName].checked = flag ? "on" : 0;
742 }
743 }
744
745 ';
746 }
747
748 /**
749 * Initializes page languages and icons
750 */
751 public function initializeLanguages()
752 {
753 // Look up page overlays:
754 $localizationParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
755 $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'];
756 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
757 ->getQueryBuilderForTable('pages');
758 $queryBuilder->getRestrictions()
759 ->removeAll()
760 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
761 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
762 $result = $queryBuilder
763 ->select('*')
764 ->from('pages')
765 ->where(
766 $queryBuilder->expr()->andX(
767 $queryBuilder->expr()->eq(
768 $localizationParentField,
769 $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
770 ),
771 $queryBuilder->expr()->gt(
772 $languageField,
773 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
774 )
775 )
776 )
777 ->execute();
778
779 $this->pageOverlays = [];
780 while ($row = $result->fetch()) {
781 $this->pageOverlays[$row[$languageField]] = $row;
782 }
783
784 $this->languageIconTitles = $this->getTranslateTools()->getSystemLanguages($this->id);
785 }
786
787 /**
788 * Return the icon for the language
789 *
790 * @param int $sys_language_uid Sys language uid
791 * @param bool $addAsAdditionalText If set to true, only the flag is returned
792 * @return string Language icon
793 */
794 public function languageFlag($sys_language_uid, $addAsAdditionalText = true)
795 {
796 $out = '';
797 $title = htmlspecialchars($this->languageIconTitles[$sys_language_uid]['title']);
798 if ($this->languageIconTitles[$sys_language_uid]['flagIcon']) {
799 $out .= '<span title="' . $title . '">' . $this->iconFactory->getIcon(
800 $this->languageIconTitles[$sys_language_uid]['flagIcon'],
801 Icon::SIZE_SMALL
802 )->render() . '</span>';
803 if (!$addAsAdditionalText) {
804 return $out;
805 }
806 $out .= '&nbsp;';
807 }
808 $out .= $title;
809 return $out;
810 }
811
812 /**
813 * If there is a parent folder and user has access to it, return an icon
814 * which is linked to the filelist of the parent folder.
815 *
816 * @param Folder $currentFolder
817 * @return string
818 */
819 protected function getLinkToParentFolder(Folder $currentFolder)
820 {
821 $levelUp = '';
822 try {
823 $currentStorage = $currentFolder->getStorage();
824 $parentFolder = $currentFolder->getParentFolder();
825 if ($parentFolder->getIdentifier() !== $currentFolder->getIdentifier() && $currentStorage->isWithinFileMountBoundaries($parentFolder)) {
826 $levelUp = $this->linkWrapDir(
827 '<span title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.upOneLevel')) . '">'
828 . $this->iconFactory->getIcon('actions-view-go-up', Icon::SIZE_SMALL)->render()
829 . '</span>',
830 $parentFolder
831 );
832 }
833 } catch (\Exception $e) {
834 }
835 return $levelUp;
836 }
837
838 /**
839 * Gets the number of files and total size of a folder
840 *
841 * @return string
842 */
843 public function getFolderInfo()
844 {
845 if ($this->counter == 1) {
846 $fileLabel = htmlspecialchars($this->getLanguageService()->getLL('file'));
847 } else {
848 $fileLabel = htmlspecialchars($this->getLanguageService()->getLL('files'));
849 }
850 return $this->counter . ' ' . $fileLabel . ', ' . GeneralUtility::formatSize($this->totalbytes, htmlspecialchars($this->getLanguageService()->getLL('byteSizeUnits')));
851 }
852
853 /**
854 * This returns tablerows for the directories in the array $items['sorting'].
855 *
856 * @param Folder[] $folders Folders of \TYPO3\CMS\Core\Resource\Folder
857 * @return string HTML table rows.
858 */
859 public function formatDirList(array $folders)
860 {
861 $out = '';
862 foreach ($folders as $folderName => $folderObject) {
863 $role = $folderObject->getRole();
864 if ($role === FolderInterface::ROLE_PROCESSING) {
865 // don't show processing-folder
866 continue;
867 }
868 if ($role !== FolderInterface::ROLE_DEFAULT) {
869 $displayName = '<strong>' . htmlspecialchars($folderName) . '</strong>';
870 } else {
871 $displayName = htmlspecialchars($folderName);
872 }
873
874 $isLocked = $folderObject instanceof InaccessibleFolder;
875 $isWritable = $folderObject->checkActionPermission('write');
876
877 // Initialization
878 $this->counter++;
879
880 // The icon with link
881 $theIcon = '<span title="' . htmlspecialchars($folderName) . '">' . $this->iconFactory->getIconForResource($folderObject, Icon::SIZE_SMALL)->render() . '</span>';
882 if (!$isLocked) {
883 $theIcon = BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $folderObject->getCombinedIdentifier());
884 }
885
886 // Preparing and getting the data-array
887 $theData = [];
888 if ($isLocked) {
889 foreach ($this->fieldArray as $field) {
890 $theData[$field] = '';
891 }
892 $theData['file'] = $displayName;
893 } else {
894 foreach ($this->fieldArray as $field) {
895 switch ($field) {
896 case 'size':
897 try {
898 $numFiles = $folderObject->getFileCount();
899 } catch (InsufficientFolderAccessPermissionsException $e) {
900 $numFiles = 0;
901 }
902 $theData[$field] = $numFiles . ' ' . htmlspecialchars($this->getLanguageService()->getLL(($numFiles === 1 ? 'file' : 'files')));
903 break;
904 case 'rw':
905 $theData[$field] = '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('read')) . '</strong>' . (!$isWritable ? '' : '<strong class="text-danger">' . htmlspecialchars($this->getLanguageService()->getLL('write')) . '</strong>');
906 break;
907 case 'fileext':
908 $theData[$field] = htmlspecialchars($this->getLanguageService()->getLL('folder'));
909 break;
910 case 'tstamp':
911 $tstamp = $folderObject->getModificationTime();
912 $theData[$field] = $tstamp ? BackendUtility::date($tstamp) : '-';
913 break;
914 case 'file':
915 $theData[$field] = $this->linkWrapDir($displayName, $folderObject);
916 break;
917 case '_CONTROL_':
918 $theData[$field] = $this->makeEdit($folderObject);
919 break;
920 case '_CLIPBOARD_':
921 $theData[$field] = $this->makeClip($folderObject);
922 break;
923 case '_REF_':
924 $theData[$field] = $this->makeRef($folderObject);
925 break;
926 default:
927 $theData[$field] = GeneralUtility::fixed_lgd_cs($theData[$field] ?? '', $this->fixedL);
928 }
929 }
930 }
931 $out .= $this->addElement(1, $theIcon, $theData);
932 }
933 return $out;
934 }
935
936 /**
937 * Wraps the directory-titles
938 *
939 * @param string $title String to be wrapped in links
940 * @param Folder $folderObject Folder to work on
941 * @return string HTML
942 */
943 public function linkWrapDir($title, Folder $folderObject)
944 {
945 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
946 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
947 $href = (string)$uriBuilder->buildUriFromRoute('file_FilelistList', ['id' => $folderObject->getCombinedIdentifier()]);
948 $onclick = ' onclick="' . htmlspecialchars(('top.document.getElementsByName("navigation")[0].contentWindow.Tree.highlightActiveItem("file","folder' . GeneralUtility::md5int($folderObject->getCombinedIdentifier()) . '_"+top.fsMod.currentBank)')) . '"';
949 // Sometimes $code contains plain HTML tags. In such a case the string should not be modified!
950 if ((string)$title === strip_tags($title)) {
951 return '<a href="' . htmlspecialchars($href) . '"' . $onclick . ' title="' . htmlspecialchars($title) . '">' . $title . '</a>';
952 }
953 return '<a href="' . htmlspecialchars($href) . '"' . $onclick . '>' . $title . '</a>';
954 }
955
956 /**
957 * Wraps filenames in links which opens the metadata editor.
958 *
959 * @param string $code String to be wrapped in links
960 * @param File $fileObject File to be linked
961 * @return string HTML
962 */
963 public function linkWrapFile($code, File $fileObject)
964 {
965 try {
966 if ($fileObject instanceof File && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
967 $metaData = $fileObject->_getMetaData();
968 $urlParameters = [
969 'edit' => [
970 'sys_file_metadata' => [
971 $metaData['uid'] => 'edit'
972 ]
973 ],
974 'returnUrl' => $this->listURL()
975 ];
976 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
977 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
978 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
979 $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.editMetadata'));
980 $code = '<a class="responsive-title" href="' . htmlspecialchars($url) . '" title="' . $title . '">' . $code . '</a>';
981 }
982 } catch (\Exception $e) {
983 // intentional fall-through
984 }
985 return $code;
986 }
987
988 /**
989 * Returns list URL; This is the URL of the current script with id and imagemode parameters, that's all.
990 * The URL however is not relative, otherwise GeneralUtility::sanitizeLocalUrl() would say that
991 * the URL would be invalid
992 *
993 * @param string $altId
994 * @param string $table Table name to display. Enter "-1" for the current table.
995 * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
996 *
997 * @return string URL
998 */
999 public function listURL($altId = '', $table = '-1', $exclList = '')
1000 {
1001 return GeneralUtility::linkThisScript([
1002 'target' => rawurlencode($this->folderObject->getCombinedIdentifier()),
1003 'imagemode' => $this->thumbs
1004 ]);
1005 }
1006
1007 /**
1008 * This returns tablerows for the files in the array $items['sorting'].
1009 *
1010 * @param File[] $files File items
1011 * @return string HTML table rows.
1012 */
1013 public function formatFileList(array $files)
1014 {
1015 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
1016 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
1017 $out = '';
1018 // first two keys are "0" (default) and "-1" (multiple), after that comes the "other languages"
1019 $allSystemLanguages = GeneralUtility::makeInstance(TranslationConfigurationProvider::class)->getSystemLanguages();
1020 $systemLanguages = array_filter($allSystemLanguages, function ($languageRecord) {
1021 if ($languageRecord['uid'] === -1 || $languageRecord['uid'] === 0 || !$this->getBackendUser()->checkLanguageAccess($languageRecord['uid'])) {
1022 return false;
1023 }
1024 return true;
1025 });
1026
1027 foreach ($files as $fileObject) {
1028 // Initialization
1029 $this->counter++;
1030 $this->totalbytes += $fileObject->getSize();
1031 $ext = $fileObject->getExtension();
1032 $fileName = trim($fileObject->getName());
1033 // The icon with link
1034 $theIcon = '<span title="' . htmlspecialchars($fileName . ' [' . (int)$fileObject->getUid() . ']') . '">'
1035 . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render() . '</span>';
1036 $theIcon = BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $fileObject->getCombinedIdentifier());
1037 // Preparing and getting the data-array
1038 $theData = [];
1039 foreach ($this->fieldArray as $field) {
1040 switch ($field) {
1041 case 'size':
1042 $theData[$field] = GeneralUtility::formatSize($fileObject->getSize(), htmlspecialchars($this->getLanguageService()->getLL('byteSizeUnits')));
1043 break;
1044 case 'rw':
1045 $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>');
1046 break;
1047 case 'fileext':
1048 $theData[$field] = strtoupper($ext);
1049 break;
1050 case 'tstamp':
1051 $theData[$field] = BackendUtility::date($fileObject->getModificationTime());
1052 break;
1053 case '_CONTROL_':
1054 $theData[$field] = $this->makeEdit($fileObject);
1055 break;
1056 case '_CLIPBOARD_':
1057 $theData[$field] = $this->makeClip($fileObject);
1058 break;
1059 case '_LOCALIZATION_':
1060 if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
1061 $metaDataRecord = $fileObject->_getMetaData();
1062 $translations = $this->getTranslationsForMetaData($metaDataRecord);
1063 $languageCode = '';
1064
1065 foreach ($systemLanguages as $language) {
1066 $languageId = $language['uid'];
1067 $flagIcon = $language['flagIcon'];
1068 if (array_key_exists($languageId, $translations)) {
1069 $title = htmlspecialchars(sprintf($this->getLanguageService()->getLL('editMetadataForLanguage'), $language['title']));
1070 // @todo the overlay for the flag needs to be added ($flagIcon . '-overlay')
1071 $urlParameters = [
1072 'edit' => [
1073 'sys_file_metadata' => [
1074 $translations[$languageId]['uid'] => 'edit'
1075 ]
1076 ],
1077 'returnUrl' => $this->listURL()
1078 ];
1079 $flagButtonIcon = $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-edit')->render();
1080 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1081 $languageCode .= '<a href="' . htmlspecialchars($url) . '" class="btn btn-default" title="' . $title . '">'
1082 . $flagButtonIcon . '</a>';
1083 } else {
1084 $parameters = [
1085 'justLocalized' => 'sys_file_metadata:' . $metaDataRecord['uid'] . ':' . $languageId,
1086 'returnUrl' => $this->listURL()
1087 ];
1088 $returnUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $parameters);
1089 $href = BackendUtility::getLinkToDataHandlerAction(
1090 '&cmd[sys_file_metadata][' . $metaDataRecord['uid'] . '][localize]=' . $languageId,
1091 $returnUrl
1092 );
1093 $flagButtonIcon = '<span title="' . htmlspecialchars(sprintf($this->getLanguageService()->getLL('createMetadataForLanguage'), $language['title'])) . '">' . $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-new')->render() . '</span>';
1094 $languageCode .= '<a href="' . htmlspecialchars($href) . '" class="btn btn-default">' . $flagButtonIcon . '</a> ';
1095 }
1096 }
1097
1098 // Hide flag button bar when not translated yet
1099 $theData[$field] = ' <div class="localisationData btn-group" data-fileid="' . $fileObject->getUid() . '"' .
1100 (empty($translations) ? ' style="display: none;"' : '') . '>' . $languageCode . '</div>';
1101 $theData[$field] .= '<a class="btn btn-default filelist-translationToggler" data-fileid="' . $fileObject->getUid() . '">' .
1102 '<span title="' . htmlspecialchars($this->getLanguageService()->getLL('translateMetadata')) . '">'
1103 . $this->iconFactory->getIcon('mimetypes-x-content-page-language-overlay', Icon::SIZE_SMALL)->render() . '</span>'
1104 . '</a>';
1105 }
1106 break;
1107 case '_REF_':
1108 $theData[$field] = $this->makeRef($fileObject);
1109 break;
1110 case 'file':
1111 // Edit metadata of file
1112 $theData[$field] = $this->linkWrapFile(htmlspecialchars($fileName), $fileObject);
1113
1114 if ($fileObject->isMissing()) {
1115 $theData[$field] .= '<span class="label label-danger label-space-left">'
1116 . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
1117 . '</span>';
1118 // Thumbnails?
1119 } elseif ($this->thumbs && ($this->isImage($ext) || $this->isMediaFile($ext))) {
1120 $processedFile = $fileObject->process(
1121 ProcessedFile::CONTEXT_IMAGEPREVIEW,
1122 [
1123 'width' => $this->thumbnailConfiguration->getWidth(),
1124 'height' => $this->thumbnailConfiguration->getHeight()
1125 ]
1126 );
1127 if ($processedFile) {
1128 $thumbUrl = $processedFile->getPublicUrl(true);
1129 $theData[$field] .= '<br /><img src="' . $thumbUrl . '" ' .
1130 'width="' . $processedFile->getProperty('width') . '" ' .
1131 'height="' . $processedFile->getProperty('height') . '" ' .
1132 'title="' . htmlspecialchars($fileName) . '" alt="" />';
1133 }
1134 }
1135 break;
1136 default:
1137 $theData[$field] = '';
1138 if ($fileObject->hasProperty($field)) {
1139 $theData[$field] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getProperty($field), $this->fixedL));
1140 }
1141 }
1142 }
1143 $out .= $this->addElement(1, $theIcon, $theData);
1144 }
1145 return $out;
1146 }
1147
1148 /**
1149 * Fetch the translations for a sys_file_metadata record
1150 *
1151 * @param $metaDataRecord
1152 * @return array keys are the sys_language uids, values are the $rows
1153 */
1154 protected function getTranslationsForMetaData($metaDataRecord)
1155 {
1156 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_metadata');
1157 $queryBuilder->getRestrictions()->removeAll();
1158 $translationRecords = $queryBuilder->select('*')
1159 ->from('sys_file_metadata')
1160 ->where(
1161 $queryBuilder->expr()->eq(
1162 $GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'],
1163 $queryBuilder->createNamedParameter($metaDataRecord['uid'], \PDO::PARAM_INT)
1164 ),
1165 $queryBuilder->expr()->gt(
1166 $GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'],
1167 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1168 )
1169 )
1170 ->execute()
1171 ->fetchAll();
1172
1173 $translations = [];
1174 foreach ($translationRecords as $record) {
1175 $translations[$record[$GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField']]] = $record;
1176 }
1177 return $translations;
1178 }
1179
1180 /**
1181 * Returns TRUE if $ext is an image-extension according to $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
1182 *
1183 * @param string $ext File extension
1184 * @return bool
1185 */
1186 public function isImage($ext)
1187 {
1188 return GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($ext));
1189 }
1190
1191 /**
1192 * Returns TRUE if $ext is an media-extension according to $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']
1193 *
1194 * @param string $ext File extension
1195 * @return bool
1196 */
1197 public function isMediaFile($ext)
1198 {
1199 return GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'], strtolower($ext));
1200 }
1201
1202 /**
1203 * Wraps the directory-titles ($code) in a link to filelist/Modules/Filelist/index.php (id=$path) and sorting commands...
1204 *
1205 * @param string $code String to be wrapped
1206 * @param string $folderIdentifier ID (path)
1207 * @param string $col Sorting column
1208 * @return string HTML
1209 */
1210 public function linkWrapSort($code, $folderIdentifier, $col)
1211 {
1212 $params = ['id' => $folderIdentifier, 'SET' => [ 'sort' => $col ]];
1213
1214 if ($this->sort === $col) {
1215 // Check reverse sorting
1216 $params['SET']['reverse'] = ($this->sortRev ? '0' : '1');
1217 $sortArrow = $this->iconFactory->getIcon('status-status-sorting-light-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render();
1218 } else {
1219 $params['SET']['reverse'] = 0;
1220 $sortArrow = '';
1221 }
1222 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
1223 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
1224 $href = (string)$uriBuilder->buildUriFromRoute('file_FilelistList', $params);
1225 return '<a href="' . htmlspecialchars($href) . '">' . $code . ' ' . $sortArrow . '</a>';
1226 }
1227
1228 /**
1229 * Creates the clipboard control pad
1230 *
1231 * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the clipboard panel for the listing.
1232 * @return string HTML-table
1233 */
1234 public function makeClip($fileOrFolderObject)
1235 {
1236 if (!$fileOrFolderObject->checkActionPermission('read')) {
1237 return '';
1238 }
1239 $cells = [];
1240 $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
1241 $fullName = $fileOrFolderObject->getName();
1242 $md5 = GeneralUtility::shortMD5($fullIdentifier);
1243 // For normal clipboard, add copy/cut buttons:
1244 if ($this->clipObj->current === 'normal') {
1245 $isSel = $this->clipObj->isSelected('_FILE', $md5);
1246 $copyTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy'));
1247 $cutTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut'));
1248 $copyIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render();
1249 $cutIcon = $this->iconFactory->getIcon('actions-edit-cut', Icon::SIZE_SMALL)->render();
1250
1251 if ($isSel === 'copy') {
1252 $copyIcon = $this->iconFactory->getIcon('actions-edit-copy-release', Icon::SIZE_SMALL)->render();
1253 } elseif ($isSel === 'cut') {
1254 $cutIcon = $this->iconFactory->getIcon('actions-edit-cut-release', Icon::SIZE_SMALL)->render();
1255 }
1256
1257 $cells[] = '<a class="btn btn-default"" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 1, ($isSel === 'copy'))) . '" title="' . $copyTitle . '">' . $copyIcon . '</a>';
1258 // we can only cut if file can be moved
1259 if ($fileOrFolderObject->checkActionPermission('move')) {
1260 $cells[] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 0, ($isSel === 'cut'))) . '" title="' . $cutTitle . '">' . $cutIcon . '</a>';
1261 } else {
1262 $cells[] = $this->spaceIcon;
1263 }
1264 } else {
1265 // For numeric pads, add select checkboxes:
1266 $n = '_FILE|' . $md5;
1267 $this->CBnames[] = $n;
1268 $checked = $this->clipObj->isSelected('_FILE', $md5) ? ' checked="checked"' : '';
1269 $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>';
1270 }
1271 // Display PASTE button, if directory:
1272 $elFromTable = $this->clipObj->elFromTable('_FILE');
1273 if ($fileOrFolderObject instanceof Folder && !empty($elFromTable) && $fileOrFolderObject->checkActionPermission('write')) {
1274 $addPasteButton = true;
1275 $elToConfirm = [];
1276 foreach ($elFromTable as $key => $element) {
1277 $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
1278 if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $fileOrFolderObject)) {
1279 $addPasteButton = false;
1280 }
1281 $elToConfirm[$key] = $clipBoardElement->getName();
1282 }
1283 if ($addPasteButton) {
1284 $cells[] = '<a class="btn btn-default t3js-modal-trigger" '
1285 . ' href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $fullIdentifier)) . '"'
1286 . ' data-content="' . htmlspecialchars($this->clipObj->confirmMsgText('_FILE', $fullName, 'into', $elToConfirm)) . '"'
1287 . ' data-severity="warning"'
1288 . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteInto')) . '"'
1289 . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteInto')) . '"'
1290 . '>'
1291 . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
1292 . '</a>';
1293 }
1294 }
1295 // Compile items into a DIV-element:
1296 return ' <div class="btn-group" role="group">' . implode('', $cells) . '</div>';
1297 }
1298
1299 /**
1300 * Creates the edit control section
1301 *
1302 * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the edit control section for the listing.
1303 * @return string HTML-table
1304 */
1305 public function makeEdit($fileOrFolderObject)
1306 {
1307 $cells = [];
1308 $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
1309 $md5 = GeneralUtility::shortMD5($fullIdentifier);
1310 $isSel = $this->clipObj->isSelected('_FILE', $md5);
1311 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
1312 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
1313
1314 // Edit file content (if editable)
1315 if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('write') && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], $fileOrFolderObject->getExtension())) {
1316 $url = (string)$uriBuilder->buildUriFromRoute('file_edit', ['target' => $fullIdentifier]);
1317 $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;';
1318 $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') . '">'
1319 . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render()
1320 . '</a>';
1321 } else {
1322 $cells['edit'] = $this->spaceIcon;
1323 }
1324
1325 // Edit metadata of file
1326 if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
1327 $metaData = $fileOrFolderObject->_getMetaData();
1328 $urlParameters = [
1329 'edit' => [
1330 'sys_file_metadata' => [
1331 $metaData['uid'] => 'edit'
1332 ]
1333 ],
1334 'returnUrl' => $this->listURL()
1335 ];
1336 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1337 $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.editMetadata'));
1338 $cells['metadata'] = '<a class="btn btn-default" href="' . htmlspecialchars($url) . '" title="' . $title . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
1339 }
1340
1341 // document view
1342 if ($fileOrFolderObject instanceof File) {
1343 $fileUrl = $fileOrFolderObject->getPublicUrl(true);
1344 if ($fileUrl) {
1345 $aOnClick = 'return top.openUrlInWindow(' . GeneralUtility::quoteJSvalue($fileUrl) . ', \'WebFile\');';
1346 $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>';
1347 } else {
1348 $cells['view'] = $this->spaceIcon;
1349 }
1350 } else {
1351 $cells['view'] = $this->spaceIcon;
1352 }
1353
1354 // replace file
1355 if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('replace')) {
1356 $url = (string)$uriBuilder->buildUriFromRoute('file_replace', ['target' => $fullIdentifier, 'uid' => $fileOrFolderObject->getUid()]);
1357 $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;';
1358 $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>';
1359 }
1360
1361 // rename the file
1362 if ($fileOrFolderObject->checkActionPermission('rename')) {
1363 $url = (string)$uriBuilder->buildUriFromRoute('file_rename', ['target' => $fullIdentifier]);
1364 $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;';
1365 $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>';
1366 } else {
1367 $cells['rename'] = $this->spaceIcon;
1368 }
1369
1370 // upload files
1371 if ($fileOrFolderObject->getStorage()->checkUserActionPermission('add', 'File') && $fileOrFolderObject->checkActionPermission('write')) {
1372 if ($fileOrFolderObject instanceof Folder) {
1373 $url = (string)$uriBuilder->buildUriFromRoute('file_upload', ['target' => $fullIdentifier]);
1374 $uploadOnClick = '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;';
1375 $cells['upload'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($uploadOnClick) . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.upload')) . '">' . $this->iconFactory->getIcon('actions-edit-upload', Icon::SIZE_SMALL)->render() . '</a>';
1376 }
1377 }
1378
1379 if ($fileOrFolderObject->checkActionPermission('read')) {
1380 $infoOnClick = '';
1381 if ($fileOrFolderObject instanceof Folder) {
1382 $infoOnClick = 'top.launchView( \'_FOLDER\', ' . GeneralUtility::quoteJSvalue($fullIdentifier) . ');return false;';
1383 } elseif ($fileOrFolderObject instanceof File) {
1384 $infoOnClick = 'top.launchView( \'_FILE\', ' . GeneralUtility::quoteJSvalue($fullIdentifier) . ');return false;';
1385 }
1386 $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>';
1387 } else {
1388 $cells['info'] = $this->spaceIcon;
1389 }
1390
1391 // copy the file
1392 if ($fileOrFolderObject->checkActionPermission('copy') && $this->clipObj->current === 'normal') {
1393 $copyTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy'));
1394 $copyIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render();
1395
1396 if ($isSel === 'copy') {
1397 $copyIcon = $this->iconFactory->getIcon('actions-edit-copy-release', Icon::SIZE_SMALL)->render();
1398 }
1399
1400 $cells['copy'] = '<a class="btn btn-default"" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 1, ($isSel === 'copy'))) . '" title="' . $copyTitle . '">' . $copyIcon . '</a>';
1401 }
1402
1403 // cut the file
1404 if ($fileOrFolderObject->checkActionPermission('move') && $this->clipObj->current === 'normal') {
1405 $cutTitle = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut'));
1406 $cutIcon = $this->iconFactory->getIcon('actions-edit-cut', Icon::SIZE_SMALL)->render();
1407
1408 if ($isSel === 'cut') {
1409 $cutIcon = $this->iconFactory->getIcon('actions-edit-cut-release', Icon::SIZE_SMALL)->render();
1410 }
1411
1412 $cells['cut'] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 0, ($isSel === 'cut'))) . '" title="' . $cutTitle . '">' . $cutIcon . '</a>';
1413 }
1414
1415 // delete the file
1416 if ($fileOrFolderObject->checkActionPermission('delete')) {
1417 $identifier = $fileOrFolderObject->getIdentifier();
1418 if ($fileOrFolderObject instanceof Folder) {
1419 $referenceCountText = BackendUtility::referenceCount('_FILE', $identifier, ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToFolder'));
1420 $deleteType = 'delete_folder';
1421 } else {
1422 $referenceCountText = BackendUtility::referenceCount('sys_file', $fileOrFolderObject->getUid(), ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile'));
1423 $deleteType = 'delete_file';
1424 }
1425
1426 if ($this->getBackendUser()->jsConfirmation(JsConfirmation::DELETE)) {
1427 $confirmationCheck = '1';
1428 } else {
1429 $confirmationCheck = '0';
1430 }
1431
1432 $deleteUrl = (string)$uriBuilder->buildUriFromRoute('tce_file');
1433 $confirmationMessage = sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.delete'), $fileOrFolderObject->getName()) . $referenceCountText;
1434 $title = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.delete');
1435 $cells['delete'] = '<a href="#" class="btn btn-default t3js-filelist-delete" data-content="' . htmlspecialchars($confirmationMessage)
1436 . '" data-check="' . $confirmationCheck
1437 . '" data-delete-url="' . htmlspecialchars($deleteUrl)
1438 . '" data-title="' . htmlspecialchars($title)
1439 . '" data-identifier="' . htmlspecialchars($fileOrFolderObject->getCombinedIdentifier())
1440 . '" data-delete-type="' . $deleteType
1441 . '" title="' . htmlspecialchars($title) . '">'
1442 . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
1443 } else {
1444 $cells['delete'] = $this->spaceIcon;
1445 }
1446
1447 // Hook for manipulating edit icons.
1448 $cells['__fileOrFolderObject'] = $fileOrFolderObject;
1449 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['fileList']['editIconsHook'] ?? [] as $className) {
1450 $hookObject = GeneralUtility::makeInstance($className);
1451 if (!$hookObject instanceof FileListEditIconHookInterface) {
1452 throw new \UnexpectedValueException(
1453 $className . ' must implement interface ' . FileListEditIconHookInterface::class,
1454 1235225797
1455 );
1456 }
1457 $hookObject->manipulateEditIcons($cells, $this);
1458 }
1459 unset($cells['__fileOrFolderObject']);
1460 // Compile items into a DIV-element:
1461 return '<div class="btn-group">' . implode('', $cells) . '</div>';
1462 }
1463
1464 /**
1465 * Make reference count
1466 *
1467 * @param File|Folder $fileOrFolderObject Array with information about the file/directory for which to make the clipboard panel for the listing.
1468 * @return string HTML
1469 */
1470 public function makeRef($fileOrFolderObject)
1471 {
1472 if ($fileOrFolderObject instanceof FolderInterface) {
1473 return '-';
1474 }
1475 // Look up the file in the sys_refindex.
1476 // Exclude sys_file_metadata records as these are no use references
1477 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
1478 $referenceCount = $queryBuilder->count('*')
1479 ->from('sys_refindex')
1480 ->where(
1481 $queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
1482 $queryBuilder->expr()->eq(
1483 'ref_table',
1484 $queryBuilder->createNamedParameter('sys_file', \PDO::PARAM_STR)
1485 ),
1486 $queryBuilder->expr()->eq(
1487 'ref_uid',
1488 $queryBuilder->createNamedParameter($fileOrFolderObject->getUid(), \PDO::PARAM_INT)
1489 ),
1490 $queryBuilder->expr()->neq(
1491 'tablename',
1492 $queryBuilder->createNamedParameter('sys_file_metadata', \PDO::PARAM_STR)
1493 )
1494 )
1495 ->execute()
1496 ->fetchColumn();
1497
1498 return $this->generateReferenceToolTip($referenceCount, '\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileOrFolderObject->getCombinedIdentifier()));
1499 }
1500
1501 /**
1502 * Returns an instance of LanguageService
1503 *
1504 * @return \TYPO3\CMS\Core\Localization\LanguageService
1505 */
1506 protected function getLanguageService()
1507 {
1508 return $GLOBALS['LANG'];
1509 }
1510
1511 /**
1512 * Returns the current BE user.
1513 *
1514 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1515 */
1516 protected function getBackendUser()
1517 {
1518 return $GLOBALS['BE_USER'];
1519 }
1520
1521 /**
1522 * Sets the script url depending on being a module or script request
1523 */
1524 protected function determineScriptUrl()
1525 {
1526 if ($routePath = GeneralUtility::_GP('route')) {
1527 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1528 $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
1529 } else {
1530 $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
1531 }
1532 }
1533
1534 /**
1535 * @return string
1536 */
1537 protected function getThisScript()
1538 {
1539 return strpos($this->thisScript, '?') === false ? $this->thisScript . '?' : $this->thisScript . '&';
1540 }
1541
1542 /**
1543 * Gets an instance of TranslationConfigurationProvider
1544 *
1545 * @return TranslationConfigurationProvider
1546 */
1547 protected function getTranslateTools()
1548 {
1549 if (!isset($this->translateTools)) {
1550 $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
1551 }
1552 return $this->translateTools;
1553 }
1554
1555 /**
1556 * Generates HTML code for a Reference tooltip out of
1557 * sys_refindex records you hand over
1558 *
1559 * @param int $references number of records from sys_refindex table
1560 * @param string $launchViewParameter JavaScript String, which will be passed as parameters to top.launchView
1561 * @return string
1562 */
1563 protected function generateReferenceToolTip($references, $launchViewParameter = '')
1564 {
1565 if (!$references) {
1566 $htmlCode = '-';
1567 } else {
1568 $htmlCode = '<a href="#"';
1569 if ($launchViewParameter !== '') {
1570 $htmlCode .= ' onclick="' . htmlspecialchars(
1571 ('top.launchView(' . $launchViewParameter . '); return false;')
1572 ) . '"';
1573 }
1574 $htmlCode .= ' title="' . htmlspecialchars(
1575 $this->getLanguageService()->sL(
1576 'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references'
1577 ) . ' (' . $references . ')'
1578 ) . '">';
1579 $htmlCode .= $references;
1580 $htmlCode .= '</a>';
1581 }
1582 return $htmlCode;
1583 }
1584 }