TYPO3  7.6
FileList.php
Go to the documentation of this file.
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 
25 use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
36 
41 {
47  public $iLimit = 40;
48 
54  public $thumbs = false;
55 
61  public $spaceIcon;
62 
68  public $fixedL = 30;
69 
75  public $clickMenus = 1;
76 
82  public $sort = '';
83 
89  public $sortRev = 1;
90 
94  public $firstElementNumber = 0;
95 
99  public $clipBoard = 0;
100 
104  public $bigControlPanel = 0;
105 
109  public $JScode = '';
110 
114  public $HTMLcode = '';
115 
119  public $totalbytes = 0;
120 
124  public $dirs = array();
125 
129  public $files = array();
130 
134  public $path = '';
135 
139  protected $folderObject;
140 
146  public $eCounter = 0;
147 
151  public $totalItems = '';
152 
156  public $CBnames = array();
157 
161  public $clipObj;
162 
166  protected $resourceFactory;
167 
172  {
173  $this->resourceFactory = $resourceFactory;
174  }
175 
179  protected $iconFactory;
180 
185 
192  {
193  parent::__construct();
194  $this->fileListController = $fileListController;
195  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
196  }
197 
209  public function start(Folder $folderObject, $pointer, $sort, $sortRev, $clipBoard = false, $bigControlPanel = false)
210  {
211  $this->folderObject = $folderObject;
212  $this->counter = 0;
213  $this->totalbytes = 0;
214  $this->JScode = '';
215  $this->HTMLcode = '';
216  $this->path = $folderObject->getReadablePath();
217  $this->sort = $sort;
218  $this->sortRev = $sortRev;
219  $this->firstElementNumber = $pointer;
220  $this->clipBoard = $clipBoard;
221  $this->bigControlPanel = $bigControlPanel;
222  // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->fixedL)
223  $this->fixedL = max($this->fixedL, $this->getBackendUser()->uc['titleLen']);
224  $this->getLanguageService()->includeLLFile('EXT:lang/locallang_common.xlf');
225  $this->resourceFactory = ResourceFactory::getInstance();
226  }
227 
233  public function generateList()
234  {
235  $this->HTMLcode .= $this->getTable('fileext,tstamp,size,rw,_REF_');
236  }
237 
246  {
248  $otherMarkers = array(
249  'PAGE_ICON' => '',
250  'TITLE' => ''
251  );
252  $buttons = array(
253  'level_up' => $this->getLinkToParentFolder($folderObject),
254  'refresh' => '',
255  'title' => '',
256  'page_icon' => '',
257  'PASTE' => ''
258  );
259  // Makes the code for the folder icon in the top
260  if ($folderObject) {
261  $title = $folderObject->getReadablePath();
262  // Start compiling the HTML
263  // If this is some subFolder under the mount root....
264  if ($folderObject->getStorage()->isWithinFileMountBoundaries($folderObject)) {
265  // The icon with link
266  $otherMarkers['PAGE_ICON'] = '<span title="' . htmlspecialchars($title) . '">' . $this->iconFactory->getIconForResource($folderObject, Icon::SIZE_SMALL)->render() . '</span>';
267  } else {
268  // This is the root folder
269  $otherMarkers['PAGE_ICON'] = '<span title="' . htmlspecialchars($title) . '">' . $this->iconFactory->getIconForResource($folderObject, Icon::SIZE_SMALL, null, array('mount-root' => true))->render() . '</span>';
270  }
271  $otherMarkers['TITLE'] .= htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, -($this->fixedL + 20)));
272 
273  if ($this->clickMenus) {
274  $otherMarkers['PAGE_ICON'] = BackendUtility::wrapClickMenuOnIcon($otherMarkers['PAGE_ICON'], $folderObject->getCombinedIdentifier());
275  }
276  // Add paste button if clipboard is initialized
277  if ($this->clipObj instanceof Clipboard && $folderObject->checkActionPermission('write')) {
278  $elFromTable = $this->clipObj->elFromTable('_FILE');
279  if (!empty($elFromTable)) {
280  $addPasteButton = true;
281  $elToConfirm = array();
282  foreach ($elFromTable as $key => $element) {
283  $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
284  if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $folderObject)) {
285  $addPasteButton = false;
286  }
287  $elToConfirm[$key] = $clipBoardElement->getName();
288  }
289  if ($addPasteButton) {
290  $buttons['PASTE'] = '<a href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $folderObject->getCombinedIdentifier())) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $this->path, 'into', $elToConfirm)) . '" title="' . $this->getLanguageService()->getLL('clip_paste', true) . '">' . $this->iconFactory->getIcon('actions-document-paste-after', Icon::SIZE_SMALL)->render() . '</a>';
291  }
292  }
293  }
294  }
295  $buttons['refresh'] = '<a href="' . htmlspecialchars($this->listURL()) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.reload', true) . '">' . $this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render() . '</a>';
296  return array($buttons, $otherMarkers);
297  }
298 
308  public function linkClipboardHeaderIcon($string, $_, $cmd, $warning = '')
309  {
310  $onClickEvent = 'document.dblistForm.cmd.value=\'' . $cmd . '\';document.dblistForm.submit();';
311  if ($warning) {
312  $onClickEvent = 'if (confirm(' . GeneralUtility::quoteJSvalue($warning) . ')){' . $onClickEvent . '}';
313  }
314  return '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onClickEvent) . 'return false;">' . $string . '</a>';
315  }
316 
323  public function getTable($rowlist)
324  {
325  // prepare space icon
326  $this->spaceIcon = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
327 
328  // @todo use folder methods directly when they support filters
329  $storage = $this->folderObject->getStorage();
330  $storage->resetFileAndFolderNameFiltersToDefault();
331 
332  // Only render the contents of a browsable storage
333  if ($this->folderObject->getStorage()->isBrowsable()) {
334  try {
335  $foldersCount = $storage->countFoldersInFolder($this->folderObject);
336  $filesCount = $storage->countFilesInFolder($this->folderObject);
337  } catch (InsufficientFolderAccessPermissionsException $e) {
338  $foldersCount = 0;
339  $filesCount = 0;
340  }
341 
342  if ($foldersCount <= $this->firstElementNumber) {
343  $foldersFrom = false;
344  $foldersNum = false;
345  } else {
346  $foldersFrom = $this->firstElementNumber;
347  if ($this->firstElementNumber + $this->iLimit > $foldersCount) {
348  $foldersNum = $foldersCount - $this->firstElementNumber;
349  } else {
350  $foldersNum = $this->iLimit;
351  }
352  }
353  if ($foldersCount >= $this->firstElementNumber + $this->iLimit) {
354  $filesFrom = false;
355  $filesNum = false;
356  } else {
357  if ($this->firstElementNumber <= $foldersCount) {
358  $filesFrom = 0;
359  $filesNum = $this->iLimit - $foldersNum;
360  } else {
361  $filesFrom = $this->firstElementNumber - $foldersCount;
362  if ($filesFrom + $this->iLimit > $filesCount) {
363  $filesNum = $filesCount - $filesFrom;
364  } else {
365  $filesNum = $this->iLimit;
366  }
367  }
368  }
369  $folders = $storage->getFoldersInFolder($this->folderObject, $foldersFrom, $foldersNum, true, false, trim($this->sort), (bool)$this->sortRev);
370  $files = $this->folderObject->getFiles($filesFrom, $filesNum, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, false, trim($this->sort), (bool)$this->sortRev);
371  $this->totalItems = $foldersCount + $filesCount;
372  // Adds the code of files/dirs
373  $out = '';
374  $titleCol = 'file';
375  // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
376  $rowlist = '_LOCALIZATION_,' . $rowlist;
377  $rowlist = GeneralUtility::rmFromList($titleCol, $rowlist);
378  $rowlist = GeneralUtility::uniqueList($rowlist);
379  $rowlist = $rowlist ? $titleCol . ',' . $rowlist : $titleCol;
380  if ($this->clipBoard) {
381  $rowlist = str_replace('_LOCALIZATION_,', '_LOCALIZATION_,_CLIPBOARD_,', $rowlist);
382  $this->addElement_tdCssClass['_CLIPBOARD_'] = 'col-clipboard';
383  }
384  if ($this->bigControlPanel) {
385  $rowlist = str_replace('_LOCALIZATION_,', '_LOCALIZATION_,_CONTROL_,', $rowlist);
386  $this->addElement_tdCssClass['_CONTROL_'] = 'col-control';
387  }
388  $this->fieldArray = explode(',', $rowlist);
389 
390  // Add classes to table cells
391  $this->addElement_tdCssClass[$titleCol] = 'col-title';
392  $this->addElement_tdCssClass['_LOCALIZATION_'] = 'col-localizationa';
393 
394  $folders = ListUtility::resolveSpecialFolderNames($folders);
395 
396  $iOut = '';
397  // Directories are added
398  $this->eCounter = $this->firstElementNumber;
399  list(, $code) = $this->fwd_rwd_nav();
400  $iOut .= $code;
401 
402  $iOut .= $this->formatDirList($folders);
403  // Files are added
404  $iOut .= $this->formatFileList($files);
405 
406  $this->eCounter = $this->firstElementNumber + $this->iLimit <= $this->totalItems
407  ? $this->firstElementNumber + $this->iLimit
408  : $this->totalItems;
409  list(, $code) = $this->fwd_rwd_nav();
410  $iOut .= $code;
411 
412  // Header line is drawn
413  $theData = array();
414  foreach ($this->fieldArray as $v) {
415  if ($v == '_CLIPBOARD_' && $this->clipBoard) {
416  $cells = array();
417  $table = '_FILE';
418  $elFromTable = $this->clipObj->elFromTable($table);
419  if (!empty($elFromTable) && $this->folderObject->checkActionPermission('write')) {
420  $addPasteButton = true;
421  $elToConfirm = array();
422  foreach ($elFromTable as $key => $element) {
423  $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
424  if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $this->folderObject)) {
425  $addPasteButton = false;
426  }
427  $elToConfirm[$key] = $clipBoardElement->getName();
428  }
429  if ($addPasteButton) {
430  $cells[] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $this->folderObject->getCombinedIdentifier())) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $this->path, 'into', $elToConfirm)) . '" title="' . $this->getLanguageService()->getLL('clip_paste', 1) . '">' . $this->iconFactory->getIcon('actions-document-paste-after', Icon::SIZE_SMALL)->render() . '</a>';
431  }
432  }
433  if ($this->clipObj->current !== 'normal' && $iOut) {
434  $cells[] = $this->linkClipboardHeaderIcon('<span title="' . $this->getLanguageService()->getLL('clip_selectMarked', true) . '">' . $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render() . '</span>', $table, 'setCB');
435  $cells[] = $this->linkClipboardHeaderIcon('<span title="' . $this->getLanguageService()->getLL('clip_deleteMarked', true) . '">' . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(), $table, 'delete', $this->getLanguageService()->getLL('clip_deleteMarkedWarning'));
436  $onClick = 'checkOffCB(\'' . implode(',', $this->CBnames) . '\', this); return false;';
437  $cells[] = '<a class="btn btn-default" rel="" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $this->getLanguageService()->getLL('clip_markRecords', true) . '">' . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
438  }
439  $theData[$v] = implode('', $cells);
440  } else {
441  // Normal row:
442  $theT = $this->linkWrapSort($this->getLanguageService()->getLL('c_' . $v, true), $this->folderObject->getCombinedIdentifier(), $v);
443  $theData[$v] = $theT;
444  }
445  }
446 
447  $out .= '<thead>' . $this->addelement(1, '', $theData, '', '', '', 'th') . '</thead>';
448  $out .= '<tbody>' . $iOut . '</tbody>';
449  // half line is drawn
450  // finish
451  $out = '
452  <!--
453  Filelist table:
454  -->
455  <div class="table-fit">
456  <table class="table table-striped table-hover" id="typo3-filelist">
457  ' . $out . '
458  </table>
459  </div>';
460  } else {
462  $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $this->getLanguageService()->getLL('storageNotBrowsableMessage'), $this->getLanguageService()->getLL('storageNotBrowsableTitle'), FlashMessage::INFO);
463  $out = $flashMessage->render();
464  }
465  return $out;
466  }
467 
475  protected function getLinkToParentFolder(Folder $currentFolder)
476  {
477  $levelUp = '';
478  try {
479  $currentStorage = $currentFolder->getStorage();
480  $parentFolder = $currentFolder->getParentFolder();
481  if ($parentFolder->getIdentifier() !== $currentFolder->getIdentifier() && $currentStorage->isWithinFileMountBoundaries($parentFolder)) {
482  $levelUp = $this->linkWrapDir(
483  '<span title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.php:labels.upOneLevel', true) . '">'
484  . $this->iconFactory->getIcon('actions-view-go-up', Icon::SIZE_SMALL)->render()
485  . '</span>',
486  $parentFolder
487  );
488  }
489  } catch (\Exception $e) {
490  }
491  return $levelUp;
492  }
493 
499  public function getFolderInfo()
500  {
501  if ($this->counter == 1) {
502  $fileLabel = $this->getLanguageService()->getLL('file', true);
503  } else {
504  $fileLabel = $this->getLanguageService()->getLL('files', true);
505  }
506  return $this->counter . ' ' . $fileLabel . ', ' . GeneralUtility::formatSize($this->totalbytes, $this->getLanguageService()->getLL('byteSizeUnits', true));
507  }
508 
515  public function formatDirList(array $folders)
516  {
517  $out = '';
518  foreach ($folders as $folderName => $folderObject) {
519  $role = $folderObject->getRole();
520  if ($role === FolderInterface::ROLE_PROCESSING) {
521  // don't show processing-folder
522  continue;
523  }
524  if ($role !== FolderInterface::ROLE_DEFAULT) {
525  $displayName = '<strong>' . htmlspecialchars($folderName) . '</strong>';
526  } else {
527  $displayName = htmlspecialchars($folderName);
528  }
529 
530  $isLocked = $folderObject instanceof InaccessibleFolder;
531  $isWritable = $folderObject->checkActionPermission('write');
532 
533  // Initialization
534  $this->counter++;
535 
536  // The icon with link
537  $theIcon = '<span title="' . htmlspecialchars($folderName) . '">' . $this->iconFactory->getIconForResource($folderObject, Icon::SIZE_SMALL)->render() . '</span>';
538  if (!$isLocked && $this->clickMenus) {
539  $theIcon = BackendUtility::wrapClickMenuOnIcon($theIcon, $folderObject->getCombinedIdentifier());
540  }
541 
542  // Preparing and getting the data-array
543  $theData = array();
544  if ($isLocked) {
545  foreach ($this->fieldArray as $field) {
546  $theData[$field] = '';
547  }
548  $theData['file'] = $displayName;
549  } else {
550  foreach ($this->fieldArray as $field) {
551  switch ($field) {
552  case 'size':
553  try {
554  $numFiles = $folderObject->getFileCount();
555  } catch (InsufficientFolderAccessPermissionsException $e) {
556  $numFiles = 0;
557  }
558  $theData[$field] = $numFiles . ' ' . $this->getLanguageService()->getLL(($numFiles === 1 ? 'file' : 'files'), true);
559  break;
560  case 'rw':
561  $theData[$field] = '<strong class="text-danger">' . $this->getLanguageService()->getLL('read', true) . '</strong>' . (!$isWritable ? '' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('write', true) . '</strong>');
562  break;
563  case 'fileext':
564  $theData[$field] = $this->getLanguageService()->getLL('folder', true);
565  break;
566  case 'tstamp':
567  // @todo: FAL: how to get the mtime info -- $theData[$field] = \TYPO3\CMS\Backend\Utility\BackendUtility::date($theFile['tstamp']);
568  $theData[$field] = '-';
569  break;
570  case 'file':
571  $theData[$field] = $this->linkWrapDir($displayName, $folderObject);
572  break;
573  case '_CONTROL_':
574  $theData[$field] = $this->makeEdit($folderObject);
575  break;
576  case '_CLIPBOARD_':
577  $theData[$field] = $this->makeClip($folderObject);
578  break;
579  case '_REF_':
580  $theData[$field] = $this->makeRef($folderObject);
581  break;
582  default:
583  $theData[$field] = GeneralUtility::fixed_lgd_cs($theData[$field], $this->fixedL);
584  }
585  }
586  }
587  $out .= $this->addelement(1, $theIcon, $theData);
588  }
589  return $out;
590  }
591 
599  public function linkWrapDir($title, Folder $folderObject)
600  {
601  $href = BackendUtility::getModuleUrl('file_FilelistList', ['id' => $folderObject->getCombinedIdentifier()]);
602  $onclick = ' onclick="' . htmlspecialchars(('top.document.getElementsByName("navigation")[0].contentWindow.Tree.highlightActiveItem("file","folder' . GeneralUtility::md5int($folderObject->getCombinedIdentifier()) . '_"+top.fsMod.currentBank)')) . '"';
603  // Sometimes $code contains plain HTML tags. In such a case the string should not be modified!
604  if ((string)$title === strip_tags($title)) {
605  return '<a href="' . htmlspecialchars($href) . '"' . $onclick . ' title="' . htmlspecialchars($title) . '">' . GeneralUtility::fixed_lgd_cs($title, $this->fixedL) . '</a>';
606  } else {
607  return '<a href="' . htmlspecialchars($href) . '"' . $onclick . '>' . $title . '</a>';
608  }
609  }
610 
618  public function linkWrapFile($code, File $fileObject)
619  {
620  try {
621  if ($fileObject instanceof File && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
622  $metaData = $fileObject->_getMetaData();
623  $urlParameters = [
624  'edit' => [
625  'sys_file_metadata' => [
626  $metaData['uid'] => 'edit'
627  ]
628  ],
629  'returnUrl' => $this->listURL()
630  ];
631  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
632  $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.editMetadata'));
633  $code = '<a href="' . htmlspecialchars($url) . '" title="' . $title . '">' . GeneralUtility::fixed_lgd_cs($code, $this->fixedL) . '</a>';
634  }
635  } catch (\Exception $e) {
636  // intentional fall-through
637  }
638  return $code;
639  }
640 
650  public function listURL($altId = '')
651  {
652  return GeneralUtility::linkThisScript(array(
653  'target' => rawurlencode($this->folderObject->getCombinedIdentifier()),
654  'imagemode' => $this->thumbs
655  ));
656  }
657 
664  public function formatFileList(array $files)
665  {
666  $out = '';
667  // first two keys are "0" (default) and "-1" (multiple), after that comes the "other languages"
668  $allSystemLanguages = GeneralUtility::makeInstance(TranslationConfigurationProvider::class)->getSystemLanguages();
669  $systemLanguages = array_filter($allSystemLanguages, function ($languageRecord) {
670  if ($languageRecord['uid'] === -1 || $languageRecord['uid'] === 0 || !$this->getBackendUser()->checkLanguageAccess($languageRecord['uid'])) {
671  return false;
672  } else {
673  return true;
674  }
675  });
676 
677  foreach ($files as $fileObject) {
678  // Initialization
679  $this->counter++;
680  $this->totalbytes += $fileObject->getSize();
681  $ext = $fileObject->getExtension();
682  $fileName = trim($fileObject->getName());
683  // The icon with link
684  $theIcon = '<span title="' . htmlspecialchars($fileName . ' [' . (int)$fileObject->getUid() . ']') . '">'
685  . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render() . '</span>';
686  if ($this->clickMenus) {
687  $theIcon = BackendUtility::wrapClickMenuOnIcon($theIcon, $fileObject->getCombinedIdentifier());
688  }
689  // Preparing and getting the data-array
690  $theData = array();
691  foreach ($this->fieldArray as $field) {
692  switch ($field) {
693  case 'size':
694  $theData[$field] = GeneralUtility::formatSize($fileObject->getSize(), $this->getLanguageService()->getLL('byteSizeUnits', true));
695  break;
696  case 'rw':
697  $theData[$field] = '' . (!$fileObject->checkActionPermission('read') ? ' ' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('read', true) . '</strong>') . (!$fileObject->checkActionPermission('write') ? '' : '<strong class="text-danger">' . $this->getLanguageService()->getLL('write', true) . '</strong>');
698  break;
699  case 'fileext':
700  $theData[$field] = strtoupper($ext);
701  break;
702  case 'tstamp':
703  $theData[$field] = BackendUtility::date($fileObject->getModificationTime());
704  break;
705  case '_CONTROL_':
706  $theData[$field] = $this->makeEdit($fileObject);
707  break;
708  case '_CLIPBOARD_':
709  $theData[$field] = $this->makeClip($fileObject);
710  break;
711  case '_LOCALIZATION_':
712  if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
713  $metaDataRecord = $fileObject->_getMetaData();
714  $translations = $this->getTranslationsForMetaData($metaDataRecord);
715  $languageCode = '';
716 
717  foreach ($systemLanguages as $language) {
718  $languageId = $language['uid'];
719  $flagIcon = $language['flagIcon'];
720  if (array_key_exists($languageId, $translations)) {
721  $title = htmlspecialchars(sprintf($this->getLanguageService()->getLL('editMetadataForLanguage'), $language['title']));
722  // @todo the overlay for the flag needs to be added ($flagIcon . '-overlay')
723  $urlParameters = [
724  'edit' => [
725  'sys_file_metadata' => [
726  $translations[$languageId]['uid'] => 'edit'
727  ]
728  ],
729  'returnUrl' => $this->listURL()
730  ];
731  $flagButtonIcon = $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-edit')->render();
732  $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
733  $languageCode .= '<a href="' . htmlspecialchars($url) . '" class="btn btn-default" title="' . $title . '">'
734  . $flagButtonIcon . '</a>';
735  } else {
736  $parameters = [
737  'justLocalized' => 'sys_file_metadata:' . $metaDataRecord['uid'] . ':' . $languageId,
738  'returnUrl' => $this->listURL()
739  ];
740  $returnUrl = BackendUtility::getModuleUrl('record_edit', $parameters);
741  $href = BackendUtility::getLinkToDataHandlerAction(
742  '&cmd[sys_file_metadata][' . $metaDataRecord['uid'] . '][localize]=' . $languageId,
743  $returnUrl
744  );
745  $flagButtonIcon = '<span title="' . htmlspecialchars(sprintf($this->getLanguageService()->getLL('createMetadataForLanguage'), $language['title'])) . '">' . $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-new')->render() . '</span>';
746  $languageCode .= '<a href="' . htmlspecialchars($href) . '" class="btn btn-default">' . $flagButtonIcon . '</a> ';
747  }
748  }
749 
750  // Hide flag button bar when not translated yet
751  $theData[$field] = ' <div class="localisationData btn-group" data-fileid="' . $fileObject->getUid() . '"' .
752  (empty($translations) ? ' style="display: none;"' : '') . '>' . $languageCode . '</div>';
753  $theData[$field] .= '<a class="btn btn-default filelist-translationToggler" data-fileid="' . $fileObject->getUid() . '">' .
754  '<span title="' . $this->getLanguageService()->getLL('translateMetadata', true) . '">'
755  . $this->iconFactory->getIcon('mimetypes-x-content-page-language-overlay', Icon::SIZE_SMALL)->render() . '</span>'
756  . '</a>';
757  }
758  break;
759  case '_REF_':
760  $theData[$field] = $this->makeRef($fileObject);
761  break;
762  case 'file':
763  // Edit metadata of file
764  $theData[$field] = $this->linkWrapFile(htmlspecialchars($fileName), $fileObject);
765 
766  if ($fileObject->isMissing()) {
767  $flashMessage = \TYPO3\CMS\Core\Resource\Utility\BackendUtility::getFlashMessageForMissingFile($fileObject);
768  $theData[$field] .= $flashMessage->render();
769  // Thumbnails?
770  } elseif ($this->thumbs && ($this->isImage($ext) || $this->isMediaFile($ext))) {
771  $processedFile = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, array());
772  if ($processedFile) {
773  $thumbUrl = $processedFile->getPublicUrl(true);
774  $theData[$field] .= '<br /><img src="' . $thumbUrl . '" ' .
775  'width="' . $processedFile->getProperty('width') . '" ' .
776  'height="' . $processedFile->getProperty('height') . '" ' .
777  'title="' . htmlspecialchars($fileName) . '" alt="" />';
778  }
779  }
780  break;
781  default:
782  $theData[$field] = '';
783  if ($fileObject->hasProperty($field)) {
784  $theData[$field] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getProperty($field), $this->fixedL));
785  }
786  }
787  }
788  $out .= $this->addelement(1, $theIcon, $theData);
789  }
790  return $out;
791  }
792 
799  protected function getTranslationsForMetaData($metaDataRecord)
800  {
801  $where = $GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'] . '=' . (int)$metaDataRecord['uid'] .
802  ' AND ' . $GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'] . '>0';
803  $translationRecords = $this->getDatabaseConnection()->exec_SELECTgetRows('*', 'sys_file_metadata', $where);
804  $translations = array();
805  foreach ($translationRecords as $record) {
806  $translations[$record[$GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField']]] = $record;
807  }
808  return $translations;
809  }
810 
817  public function isImage($ext)
818  {
819  return GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], strtolower($ext));
820  }
821 
828  public function isMediaFile($ext)
829  {
830  return GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext'], strtolower($ext));
831  }
832 
841  public function linkWrapSort($code, $folderIdentifier, $col)
842  {
843  $params = ['id' => $folderIdentifier, 'SET' => [ 'sort' => $col ]];
844 
845  if ($this->sort === $col) {
846  // Check reverse sorting
847  $params['SET']['reverse'] = ($this->sortRev ? '0' : '1');
848  $sortArrow = $this->iconFactory->getIcon('status-status-sorting-light-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render();
849  } else {
850  $params['SET']['reverse'] = 0;
851  $sortArrow = '';
852  }
853  $href = BackendUtility::getModuleUrl('file_FilelistList', $params);
854  return '<a href="' . htmlspecialchars($href) . '">' . $code . ' ' . $sortArrow . '</a>';
855  }
856 
863  public function makeClip($fileOrFolderObject)
864  {
865  if (!$fileOrFolderObject->checkActionPermission('read')) {
866  return '';
867  }
868  $cells = array();
869  $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
870  $fullName = $fileOrFolderObject->getName();
871  $md5 = GeneralUtility::shortmd5($fullIdentifier);
872  // For normal clipboard, add copy/cut buttons:
873  if ($this->clipObj->current === 'normal') {
874  $isSel = $this->clipObj->isSelected('_FILE', $md5);
875  $copyTitle = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.copy', true);
876  $cutTitle = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.cut', true);
877  $copyIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render();
878  $cutIcon = $this->iconFactory->getIcon('actions-edit-cut', Icon::SIZE_SMALL)->render();
879 
880  if ($isSel === 'copy') {
881  $copyIcon = $this->iconFactory->getIcon('actions-edit-copy-release', Icon::SIZE_SMALL)->render();
882  } elseif ($isSel === 'cut') {
883  $cutIcon = $this->iconFactory->getIcon('actions-edit-cut-release', Icon::SIZE_SMALL)->render();
884  }
885 
886  $cells[] = '<a class="btn btn-default"" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 1, ($isSel === 'copy'))) . '" title="' . $copyTitle . '">' . $copyIcon . '</a>';
887  // we can only cut if file can be moved
888  if ($fileOrFolderObject->checkActionPermission('move')) {
889  $cells[] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->selUrlFile($fullIdentifier, 0, ($isSel === 'cut'))) . '" title="' . $cutTitle . '">' . $cutIcon . '</a>';
890  } else {
891  $cells[] = $this->spaceIcon;
892  }
893  } else {
894  // For numeric pads, add select checkboxes:
895  $n = '_FILE|' . $md5;
896  $this->CBnames[] = $n;
897  $checked = $this->clipObj->isSelected('_FILE', $md5) ? ' checked="checked"' : '';
898  $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>';
899  }
900  // Display PASTE button, if directory:
901  $elFromTable = $this->clipObj->elFromTable('_FILE');
902  if ($fileOrFolderObject instanceof Folder && !empty($elFromTable) && $fileOrFolderObject->checkActionPermission('write')) {
903  $addPasteButton = true;
904  $elToConfirm = array();
905  foreach ($elFromTable as $key => $element) {
906  $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
907  if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $fileOrFolderObject)) {
908  $addPasteButton = false;
909  }
910  $elToConfirm[$key] = $clipBoardElement->getName();
911  }
912  if ($addPasteButton) {
913  $cells[] = '<a class="btn btn-default" href="' . htmlspecialchars($this->clipObj->pasteUrl('_FILE', $fullIdentifier)) . '" onclick="return ' . htmlspecialchars($this->clipObj->confirmMsg('_FILE', $fullName, 'into', $elToConfirm)) . '" title="' . $this->getLanguageService()->getLL('clip_pasteInto', true) . '">' . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render() . '</a>';
914  }
915  }
916  // Compile items into a DIV-element:
917  return ' <div class="btn-group" role="group">' . implode('', $cells) . '</div>';
918  }
919 
926  public function makeEdit($fileOrFolderObject)
927  {
928  $cells = array();
929  $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
930 
931  // Edit file content (if editable)
932  if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('write') && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], $fileOrFolderObject->getExtension())) {
933  $url = BackendUtility::getModuleUrl('file_edit', array('target' => $fullIdentifier));
934  $editOnClick = 'top.content.list_frame.location.href=' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+top.rawurlencode(top.content.list_frame.document.location.pathname+top.content.list_frame.document.location.search);return false;';
935  $cells['edit'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($editOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.editcontent') . '">'
936  . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render()
937  . '</a>';
938  } else {
939  $cells['edit'] = $this->spaceIcon;
940  }
941  if ($fileOrFolderObject instanceof File) {
942  $fileUrl = $fileOrFolderObject->getPublicUrl(true);
943  if ($fileUrl) {
944  $aOnClick = 'return top.openUrlInWindow(' . GeneralUtility::quoteJSvalue($fileUrl) . ', \'WebFile\');';
945  $cells['view'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($aOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.view') . '">' . $this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL)->render() . '</a>';
946  } else {
947  $cells['view'] = $this->spaceIcon;
948  }
949  } else {
950  $cells['view'] = $this->spaceIcon;
951  }
952 
953  // replace file
954  if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('replace')) {
955  $url = BackendUtility::getModuleUrl('file_replace', array('target' => $fullIdentifier, 'uid' => $fileOrFolderObject->getUid()));
956  $replaceOnClick = 'top.content.list_frame.location.href = ' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+top.rawurlencode(top.content.list_frame.document.location.pathname+top.content.list_frame.document.location.search);return false;';
957  $cells['replace'] = '<a href="#" class="btn btn-default" onclick="' . $replaceOnClick . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.replace') . '">' . $this->iconFactory->getIcon('actions-edit-replace', Icon::SIZE_SMALL)->render() . '</a>';
958  }
959 
960  // rename the file
961  if ($fileOrFolderObject->checkActionPermission('rename')) {
962  $url = BackendUtility::getModuleUrl('file_rename', array('target' => $fullIdentifier));
963  $renameOnClick = 'top.content.list_frame.location.href = ' . GeneralUtility::quoteJSvalue($url) . '+\'&returnUrl=\'+top.rawurlencode(top.content.list_frame.document.location.pathname+top.content.list_frame.document.location.search);return false;';
964  $cells['rename'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($renameOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.rename') . '">' . $this->iconFactory->getIcon('actions-edit-rename', Icon::SIZE_SMALL)->render() . '</a>';
965  } else {
966  $cells['rename'] = $this->spaceIcon;
967  }
968  if ($fileOrFolderObject->checkActionPermission('read')) {
969  $infoOnClick = '';
970  if ($fileOrFolderObject instanceof Folder) {
971  $infoOnClick = 'top.launchView( \'_FOLDER\', ' . GeneralUtility::quoteJSvalue($fullIdentifier) . ');return false;';
972  } elseif ($fileOrFolderObject instanceof File) {
973  $infoOnClick = 'top.launchView( \'_FILE\', ' . GeneralUtility::quoteJSvalue($fullIdentifier) . ');return false;';
974  }
975  $cells['info'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($infoOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.info') . '">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>';
976  } else {
977  $cells['info'] = $this->spaceIcon;
978  }
979 
980  // delete the file
981  if ($fileOrFolderObject->checkActionPermission('delete')) {
982  $identifier = $fileOrFolderObject->getIdentifier();
983  if ($fileOrFolderObject instanceof Folder) {
984  $referenceCountText = BackendUtility::referenceCount('_FILE', $identifier, ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.referencesToFolder'));
985  } else {
986  $referenceCountText = BackendUtility::referenceCount('sys_file', $fileOrFolderObject->getUid(), ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.referencesToFile'));
987  }
988 
989  if ($this->getBackendUser()->jsConfirmation(JsConfirmation::DELETE)) {
990  $confirmationCheck = 'confirm(' . GeneralUtility::quoteJSvalue(sprintf($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:mess.delete'), $fileOrFolderObject->getName()) . $referenceCountText) . ')';
991  } else {
992  $confirmationCheck = '1 == 1';
993  }
994 
995  $removeOnClick = 'if (' . $confirmationCheck . ') { top.content.list_frame.location.href=' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_file') . '&file[delete][0][data]=' . rawurlencode($fileOrFolderObject->getCombinedIdentifier()) . '&vC=' . $this->getBackendUser()->veriCode() . '&redirect=') . '+top.rawurlencode(top.content.list_frame.document.location.pathname+top.content.list_frame.document.location.search);};';
996 
997  $cells['delete'] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($removeOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.delete') . '">' . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
998  } else {
999  $cells['delete'] = $this->spaceIcon;
1000  }
1001 
1002  // Hook for manipulating edit icons.
1003  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['fileList']['editIconsHook'])) {
1004  $cells['__fileOrFolderObject'] = $fileOrFolderObject;
1005  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['fileList']['editIconsHook'] as $classData) {
1006  $hookObject = GeneralUtility::getUserObj($classData);
1007  if (!$hookObject instanceof FileListEditIconHookInterface) {
1008  throw new \UnexpectedValueException(
1009  '$hookObject must implement interface \\TYPO3\\CMS\\Filelist\\FileListEditIconHookInterface',
1010  1235225797
1011  );
1012  }
1013  $hookObject->manipulateEditIcons($cells, $this);
1014  }
1015  unset($cells['__fileOrFolderObject']);
1016  }
1017  // Compile items into a DIV-element:
1018  return '<div class="btn-group">' . implode('', $cells) . '</div>';
1019  }
1020 
1027  public function makeRef($fileOrFolderObject)
1028  {
1029  if ($fileOrFolderObject instanceof FolderInterface) {
1030  return '-';
1031  }
1032  // Look up the file in the sys_refindex.
1033  // Exclude sys_file_metadata records as these are no use references
1034  $databaseConnection = $this->getDatabaseConnection();
1035  $table = 'sys_refindex';
1036  $referenceCount = $databaseConnection->exec_SELECTcountRows(
1037  '*',
1038  $table,
1039  'ref_table=' . $databaseConnection->fullQuoteStr('sys_file', $table)
1040  . ' AND ref_uid=' . (int)$fileOrFolderObject->getUid()
1041  . ' AND deleted=0'
1042  . ' AND tablename != ' . $databaseConnection->fullQuoteStr('sys_file_metadata', $table)
1043  );
1044  return $this->generateReferenceToolTip($referenceCount, '\'_FILE\', ' . GeneralUtility::quoteJSvalue($fileOrFolderObject->getCombinedIdentifier()));
1045  }
1046 
1052  protected function getDatabaseConnection()
1053  {
1054  return $GLOBALS['TYPO3_DB'];
1055  }
1056 
1062  protected function getLanguageService()
1063  {
1064  return $GLOBALS['LANG'];
1065  }
1066 
1072  protected function getBackendUser()
1073  {
1074  return $GLOBALS['BE_USER'];
1075  }
1076 }