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