Filelist (basically used in FileListController) * @see \TYPO3\CMS\Filelist\Controller\FileListController * @internal this is a concrete TYPO3 controller implementation and solely used for EXT:filelist and not part of TYPO3's Core API. */ class FileList { public Mode $mode = Mode::MANAGE; public ViewMode $viewMode = ViewMode::TILES; /** * Default Max items shown */ public int $itemsPerPage = 40; /** * Current Page */ public int $currentPage = 1; /** * Total file size of the current selection */ public int $totalbytes = 0; /** * Total count of folders and files */ public int $totalItems = 0; /** * The field to sort by */ public string $sortField = ''; public SortDirection $sortDirection = SortDirection::DESCENDING; /** * Thumbnails on records containing files (pictures) */ public bool $thumbs = false; /** * Max length of strings */ public int $maxTitleLength = 30; /** * Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column. */ public array $fieldArray = []; /** * Keys are fieldnames and values are td-css-classes to add in addElement(); * * @var array */ public array $addElement_tdCssClass = [ '_CONTROL_' => 'col-control', '_SELECTOR_' => 'col-checkbox', 'icon' => 'col-icon', 'name' => 'col-title col-responsive', ]; /** * @var Folder */ protected $folderObject; public Clipboard $clipObj; // Evaluates if a resource can be downloaded protected ?Matcher $resourceDownloadMatcher = null; // Evaluates if a resource can be displayed protected ?Matcher $resourceDisplayMatcher = null; // Evaluates if a resource can be selected protected ?Matcher $resourceSelectableMatcher = null; // Evaluates if a resource is currently selected protected ?Matcher $resourceSelectedMatcher = null; protected ?FileSearchDemand $searchDemand = null; protected EventDispatcherInterface $eventDispatcher; protected ServerRequestInterface $request; protected IconFactory $iconFactory; protected ResourceFactory $resourceFactory; protected UriBuilder $uriBuilder; protected TranslationConfigurationProvider $translateTools; protected OnlineMediaHelperRegistry $onlineMediaHelperRegistry; protected TcaSchemaFactory $tcaSchemaFactory; protected ComponentFactory $componentFactory; public function __construct(ServerRequestInterface $request) { $this->request = $request; // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->maxTitleLength) $this->maxTitleLength = max($this->maxTitleLength, (int)($this->getBackendUser()->uc['titleLen'] ?? 1)); $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class); $this->eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class); $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class); $this->tcaSchemaFactory = GeneralUtility::makeInstance(TcaSchemaFactory::class); $this->itemsPerPage = MathUtility::forceIntegerInRange( $this->getBackendUser()->getTSConfig()['options.']['file_list.']['filesPerPage'] ?? $this->itemsPerPage, 1 ); // Create clipboard object and initialize that $this->clipObj = GeneralUtility::makeInstance(Clipboard::class); $this->clipObj->initializeClipboard($request); $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $this->onlineMediaHelperRegistry = GeneralUtility::makeInstance(OnlineMediaHelperRegistry::class); // Initialize Resource Download $this->resourceDownloadMatcher = GeneralUtility::makeInstance(Matcher::class); $this->resourceDownloadMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class)); // Create filter for file extensions $fileExtensionMatcher = GeneralUtility::makeInstance(ResourceFileExtensionMatcher::class); $fileDownloadConfiguration = (array)($this->getBackendUser()->getTSConfig()['options.']['file_list.']['fileDownload.'] ?? []); if ($fileDownloadConfiguration !== []) { $allowedExtensions = GeneralUtility::trimExplode(',', (string)($fileDownloadConfiguration['allowedFileExtensions'] ?? ''), true); $disallowedExtensions = GeneralUtility::trimExplode(',', (string)($fileDownloadConfiguration['disallowedFileExtensions'] ?? ''), true); $fileExtensionMatcher = GeneralUtility::makeInstance(ResourceFileExtensionMatcher::class); $fileExtensionMatcher->setExtensions($allowedExtensions); $fileExtensionMatcher->setIgnoredExtensions($disallowedExtensions); } else { $fileExtensionMatcher->addExtension('*'); } $this->resourceDownloadMatcher->addMatcher($fileExtensionMatcher); $this->componentFactory = GeneralUtility::makeInstance(ComponentFactory::class); } public function setResourceDownloadMatcher(?Matcher $matcher): self { $this->resourceDownloadMatcher = $matcher; return $this; } public function setResourceDisplayMatcher(?Matcher $matcher): self { $this->resourceDisplayMatcher = $matcher; return $this; } public function setResourceSelectableMatcher(?Matcher $matcher): self { $this->resourceSelectableMatcher = $matcher; return $this; } public function setResourceSelectedMatcher(?Matcher $matcher): self { $this->resourceSelectedMatcher = $matcher; return $this; } /** * Initialization of class * * @param Folder $folderObject The folder to work on * @param int $currentPage The current page to render * @param string $sortField Sorting column * @param Mode $mode Mode of the file list */ public function start(Folder $folderObject, int $currentPage, string $sortField, SortDirection $sortDirection, Mode $mode = Mode::MANAGE): void { $this->folderObject = $folderObject; $this->currentPage = MathUtility::forceIntegerInRange($currentPage, 1, 100000); $this->sortField = $sortField; $this->sortDirection = $sortDirection; $this->totalbytes = 0; $this->resourceDownloadMatcher = null; $this->resourceDisplayMatcher = null; $this->resourceSelectableMatcher = null; $this->setMode($mode); } public function setMode(Mode $mode) { $this->mode = $mode; $this->fieldArray = $mode->fieldArray(); } public function setColumnsToRender(array $additionalFields = []): void { // Passed fields might have fields utilized that are no longer / not yet part of TCA Schema. // For example, EXT:filemetadata might not be available, so fields that this extension provide // must only be allowed when the extension is active. $allowedAdditionalFields = []; foreach ($additionalFields as $field) { if (!$this->tcaSchemaFactory->get('sys_file')->hasField($field) && (!$this->tcaSchemaFactory->has('sys_file_metadata') || !$this->tcaSchemaFactory->get('sys_file_metadata')->hasField($field)) ) { continue; } $allowedAdditionalFields[] = $field; } $this->fieldArray = array_unique(array_merge($this->fieldArray, $allowedAdditionalFields)); } /** * @param ResourceView[] $resourceViews */ protected function renderTiles(array $resourceViews, ViewInterface $view): string { $view->assignMultiple([ 'displayThumbs' => $this->thumbs, 'displayCheckbox' => (bool)$this->resourceSelectableMatcher, 'defaultLanguageAccess' => $this->getBackendUser()->checkLanguageAccess(0), 'resources' => $resourceViews, ]); return $view->render('Filelist/Tiles'); } /** * @param ResourceView[] $resourceViews */ protected function renderList(array $resourceViews, ViewInterface $view): string { $view->assignMultiple([ 'mode' => $this->mode->value, 'tableHeader' => $this->renderListTableHeader(), 'tableBody' => $this->renderListTableBody($resourceViews), ]); return $view->render('Filelist/List'); } public function render(?FileSearchDemand $searchDemand, ViewInterface $view): string { $storage = $this->folderObject->getStorage(); $storage->resetFileAndFolderNameFiltersToDefault(); if (!$this->folderObject->getStorage()->isBrowsable()) { return ''; } if ($searchDemand !== null) { $this->searchDemand = $searchDemand; if ($searchDemand->hasSearchTerm()) { $folders = []; // Add special "Path" field for the search result array_splice($this->fieldArray, 3, 0, '_PATH_'); } else { $folders = $storage->getFoldersInFolder($this->folderObject); } $files = iterator_to_array($this->folderObject->searchFiles($searchDemand)); } else { $folders = $storage->getFoldersInFolder($this->folderObject); $files = $this->folderObject->getFiles(); } // Cleanup field array $this->fieldArray = array_filter($this->fieldArray, function (string $fieldName) { if ($fieldName === '_SELECTOR_' && $this->resourceSelectableMatcher === null) { return false; } return true; }); // Remove processing folders $folders = array_filter($folders, function (Folder $folder) { return $folder->getRole() !== FolderInterface::ROLE_PROCESSING; }); // Apply filter $resources = array_filter($folders + $files, function (ResourceInterface $resource) { return $this->resourceDisplayMatcher === null || $this->resourceDisplayMatcher->match($resource); }); $resourceCollection = new ResourceCollection($resources); $this->totalItems = $resourceCollection->getTotalCount(); $this->totalbytes = $resourceCollection->getTotalBytes(); // Sort the files before sending it to the renderer if (trim($this->sortField) !== '') { $resourceCollection->setResources($this->sortResources($resourceCollection->getResources(), $this->sortField)); } $paginator = new ResourceCollectionPaginator($resourceCollection, $this->currentPage, $this->itemsPerPage); // Prepare Resources for View $resourceViews = []; $userPermissions = $this->getUserPermissions(); foreach ($paginator->getPaginatedItems() as $resource) { $resourceView = new ResourceView( $resource, $userPermissions, $this->iconFactory->getIconForResource($resource, IconSize::SMALL) ); $resourceView->moduleUri = $this->createModuleUriForResource($resource); $resourceView->editDataUri = $this->createEditDataUriForResource($resource); $resourceView->editContentUri = $this->createEditContentUriForResource($resource); $resourceView->isDownloadable = $this->resourceDownloadMatcher !== null && $this->resourceDownloadMatcher->match($resource); $resourceView->isSelectable = $this->resourceSelectableMatcher !== null && $this->resourceSelectableMatcher->match($resource); $resourceView->isSelected = $this->resourceSelectedMatcher !== null && $this->resourceSelectedMatcher->match($resource); $resourceViews[] = $resourceView; } $pagination = new SimplePagination($paginator); $currentPage = $paginator->getCurrentPageNumber(); $totalItems = $this->totalItems; $itemsPerPage = $this->itemsPerPage; if ($totalItems > $currentPage * $itemsPerPage) { $lastElementNumber = $currentPage * $itemsPerPage; } else { $lastElementNumber = $totalItems; } $view->assignMultiple([ 'currentUrl' => $this->getListURL(), 'paginator' => $paginator, 'pagination' => $pagination, 'currentPage' => $currentPage, 'totalPages' => $paginator->getNumberOfPages(), 'firstElement' => ((($currentPage - 1) * $itemsPerPage) + 1), 'lastElement' => $lastElementNumber, ]); if ($this->viewMode === ViewMode::TILES) { return $this->renderTiles($resourceViews, $view); } return $this->renderList($resourceViews, $view); } protected function getListURL(): UriInterface { $uri = new Uri($this->request->getAttribute('normalizedParams')->getRequestUri()); parse_str($uri->getQuery(), $queryParameters); unset($queryParameters['contentOnly'], $queryParameters['currentPage']); if ($this->searchDemand) { $queryParameters['searchTerm'] = $this->searchDemand->getSearchTerm() ?? ''; } return $uri->withQuery(HttpUtility::buildQueryString($queryParameters, '&')); } /** * Returns a table-row with the content from the fields in the input data array. * OBS: $this->fieldArray MUST be set! (represents the list of fields to display) * * @param array $data Is the data array, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in -tags * @param array $attributes Attributes for the table row. Values will be htmlspecialchar'ed! * @param bool $isTableHeader Whether the element to be added is a table header * * @return string HTML content for the table row */ public function addElement(array $data, array $attributes = [], bool $isTableHeader = false): string { // Initialize rendering. $cols = []; $colType = $isTableHeader ? 'th' : 'td'; // Traverse field array which contains the data to present: foreach ($this->fieldArray as $fieldName) { $cellAttributes = []; $cellAttributes['class'] = $this->addElement_tdCssClass[$fieldName] ?? 'col-nowrap'; // Special handling to combine icon and name column if ($isTableHeader && $fieldName === 'icon') { continue; } if ($isTableHeader && $fieldName === 'name') { $cellAttributes['colspan'] = 2; } $cols[] = '<' . $colType . ' ' . GeneralUtility::implodeAttributes($cellAttributes, true) . '>' . ($data[$fieldName] ?? '') . ''; } // Add the table row return ' ' . implode(PHP_EOL, $cols) . ' '; } /** * Gets the number of files and total size of a folder */ public function getFolderInfo(): string { if ($this->totalItems == 1) { $fileLabel = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:file'); } else { $fileLabel = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:files'); } return $this->totalItems . ' ' . htmlspecialchars($fileLabel) . ', ' . GeneralUtility::formatSize( $this->totalbytes, htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:byteSizeUnits')) ); } /** * @return array */ public function getSortableFields(): array { return array_filter($this->fieldArray, $this->isFieldSortable(...)); } protected function renderListTableHeader(): string { $data = []; foreach ($this->fieldArray as $field) { $data[$field] = match ($field) { 'icon' => '', '_SELECTOR_' => $this->renderCheckboxActions(), default => $this->renderListTableFieldHeader($field), }; } return $this->addElement($data, [], true); } protected function isFieldSortable(string $field): bool { return !in_array($field, ['icon', 'rw', '_SELECTOR_', '_CONTROL_', '_PATH_'], true); } protected function renderListTableFieldHeader(string $field): string { $label = $this->getFieldLabel($field); if (!$this->isFieldSortable($field)) { return $label; } $params = ['sortField' => $field, 'currentPage' => 0]; $paramsAsc = $params; $paramsAsc['sortDirection'] = SortDirection::ASCENDING->value; $paramsDesc = $params; $paramsDesc['sortDirection'] = SortDirection::DESCENDING->value; $icon = $this->sortField === $field ? $this->iconFactory->getIcon($this->sortDirection->getIconIdentifier(), IconSize::SMALL)->render() : $this->iconFactory->getIcon('empty-empty', IconSize::SMALL)->render(); return ' '; } /** * @param ResourceView[] $resourceViews */ protected function renderListTableBody(array $resourceViews): string { $output = ''; foreach ($resourceViews as $resourceView) { $data = []; $attributes = [ 'class' => $resourceView->isSelected ? 'selected' : '', 'data-filelist-element' => 'true', 'data-filelist-type' => $resourceView->getType(), 'data-filelist-identifier' => $resourceView->getIdentifier(), 'data-filelist-name' => htmlspecialchars($resourceView->getName()), 'data-filelist-icon' => $resourceView->getIconIdentifier(), 'data-filelist-preview' => $resourceView->getPreview() !== null ? 'true' : 'false', 'data-filelist-uid' => $resourceView->getUid(), 'data-filelist-meta-uid' => $resourceView->getMetaDataUid(), 'data-filelist-url' => $resourceView->getPublicUrl(), 'data-filelist-selectable' => $resourceView->isSelectable ? 'true' : 'false', 'data-filelist-selected' => $resourceView->isSelected ? 'true' : 'false', 'data-multi-record-selection-element' => 'true', 'draggable' => $resourceView->canMove() ? 'true' : 'false', ]; if ($this->getBackendUser()->checkLanguageAccess(0)) { $attributes['data-default-language-access'] = 'true'; } foreach ($this->fieldArray as $field) { switch ($field) { case 'icon': $data[$field] = $this->renderIcon($resourceView); break; case 'name': $data[$field] = $this->renderName($resourceView) . $this->renderThumbnail($resourceView); break; case 'size': $data[$field] = $this->renderSize($resourceView); break; case 'rw': $data[$field] = $this->renderPermission($resourceView); break; case 'record_type': $data[$field] = $this->renderType($resourceView); break; case 'crdate': $data[$field] = $this->renderCreationTime($resourceView); break; case 'tstamp': $data[$field] = $this->renderModificationTime($resourceView); break; case '_SELECTOR_': $data[$field] = $this->renderSelector($resourceView); break; case '_PATH_': $data[$field] = $this->renderPath($resourceView); break; case '_REF_': $data[$field] = $this->renderReferenceCount($resourceView); break; case '_CONTROL_': $data[$field] = $this->renderControl($resourceView); break; default: $data[$field] = $this->renderField($resourceView, $field); } } $event = $this->eventDispatcher->dispatch( new AfterFileListRowPreparedEvent($resourceView->resource, $data, $this, $attributes) ); $output .= $this->addElement($event->getData(), $event->getAttributes()); } return $output; } /** * Fetch the translations for a sys_file_metadata record * * @param array $metaDataRecord * @return array> keys are the site language ids, values are the $rows */ protected function getTranslationsForMetaData(array $metaDataRecord): array { $schema = $this->tcaSchemaFactory->get('sys_file_metadata'); if (!$schema->isLanguageAware()) { return []; } /** @var LanguageAwareSchemaCapability $languageCapability */ $languageCapability = $schema->getCapability(TcaSchemaCapability::Language); $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_metadata'); $queryBuilder->getRestrictions()->removeAll(); $translationRecords = $queryBuilder->select('*') ->from('sys_file_metadata') ->where( $queryBuilder->expr()->eq( $languageCapability->getTranslationOriginPointerField()->getName(), $queryBuilder->createNamedParameter($metaDataRecord['uid'] ?? 0, Connection::PARAM_INT) ), $queryBuilder->expr()->gt( $languageCapability->getLanguageField()->getName(), $queryBuilder->createNamedParameter(0, Connection::PARAM_INT) ) ) ->executeQuery() ->fetchAllAssociative(); $translations = []; foreach ($translationRecords as $record) { $languageId = $record[$languageCapability->getLanguageField()->getName()]; $translations[$languageId] = $record; } return $translations; } /** * Render icon */ protected function renderIcon(ResourceView $resourceView): string { return $this->mode === Mode::BROWSE ? $resourceView->getIconSmall()->render() : BackendUtility::wrapClickMenuOnIcon($resourceView->getIconSmall()->render(), 'sys_file', $resourceView->getIdentifier()); } /** * Render name */ protected function renderName(ResourceView $resourceView): string { $resourceName = htmlspecialchars($resourceView->getName()); if ($resourceView->resource instanceof Folder && $resourceView->resource->getRole() !== FolderInterface::ROLE_DEFAULT) { $resourceName = '' . $resourceName . ''; } $attributes = []; $attributes['title'] = $resourceView->getName(); $attributes['type'] = 'button'; $attributes['class'] = 'btn btn-link'; $attributes['data-filelist-action'] = 'primary'; $output = ''; if ($resourceView->isMissing()) { $label = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing')); $output = '' . $label . ' ' . $output; } return $output; } /** * Render thumbnail */ protected function renderThumbnail(ResourceView $resourceView): string { if ($this->thumbs === false || $resourceView->getPreview() === null || !($resourceView->getPreview()->isImage() || $resourceView->getPreview()->isMediaFile()) ) { return ''; } $processedFile = $resourceView->getPreview()->process( ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, [ 'maxWidth' => (int)($this->getBackendUser()->getTSConfig()['options.']['file_list.']['thumbnail.']['width'] ?? 64), 'maxHeight' => (int)($this->getBackendUser()->getTSConfig()['options.']['file_list.']['thumbnail.']['height'] ?? 64), ] ); if (($thumbnailUrl = ($processedFile->getPublicUrl() ?? '')) === '') { // Prevent rendering of a "img" tag with an empty "src" attribute return ''; } if (!str_contains($thumbnailUrl, '?') && !PathUtility::hasProtocolAndScheme($thumbnailUrl)) { $thumbnailUrl .= '?' . $processedFile->getModificationTime(); } return '
'; } /** * Render type */ protected function renderType(ResourceView $resourceView): string { $type = $resourceView->getType(); $content = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:' . $type); if ($resourceView->resource instanceof File && $resourceView->resource->getExtension() !== '') { $content .= ' (' . strtoupper($resourceView->resource->getExtension()) . ')'; } return htmlspecialchars($content); } /** * Render creation time */ protected function renderCreationTime(ResourceView $resourceView): string { if ($resourceView->resource instanceof File) { $timestamp = $resourceView->getCreatedAt(); } elseif ($resourceView->resource instanceof Folder) { $timestamp = $resourceView->resource->getCreationTime(); } else { $timestamp = null; } return $timestamp ? BackendUtility::datetime($timestamp) : ''; } /** * Render modification time */ protected function renderModificationTime(ResourceView $resourceView): string { if ($resourceView->resource instanceof File) { $timestamp = $resourceView->getUpdatedAt(); } elseif ($resourceView->resource instanceof Folder) { $timestamp = $resourceView->resource->getModificationTime(); } else { $timestamp = null; } return $timestamp ? BackendUtility::datetime($timestamp) : ''; } /** * Render size */ protected function renderSize(ResourceView $resourceView): string { if ($resourceView->resource instanceof File) { return GeneralUtility::formatSize((int)$resourceView->resource->getSize(), htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:byteSizeUnits'))); } if ($resourceView->resource instanceof Folder) { try { $numFiles = $resourceView->resource->getFileCount(); } catch (InsufficientFolderAccessPermissionsException $e) { $numFiles = 0; } if ($numFiles === 1) { return $numFiles . ' ' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:file')); } return $numFiles . ' ' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:files')); } return ''; } /** * Render resource permission */ protected function renderPermission(ResourceView $resourceView): string { return '' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:read')) . ($resourceView->canWrite() ? htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:write')) : '') . ''; } /** * Render any resource field */ protected function renderField(ResourceView $resourceView, string $field): string { if ($resourceView->resource instanceof File && $resourceView->resource->hasProperty($field)) { if ($field === 'storage') { // Fetch storage name of the current file $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid((int)$resourceView->resource->getProperty($field)); if ($storage !== null) { return htmlspecialchars($storage->getName()); } } else { $metaData = $resourceView->resource->getMetaData()->get(); return htmlspecialchars( (string)BackendUtility::getProcessedValueExtra( $this->getConcreteTableName($field), $field, $resourceView->resource->getProperty($field), $this->maxTitleLength, $metaData['uid'], false, 0, $metaData, ) ); } } return ''; } /** * Renders the checkbox to select a resource in the listing */ protected function renderSelector(ResourceView $resourceView): string { $checkboxConfig = $resourceView->getCheckboxConfig(); if ($checkboxConfig === null) { return ''; } if (!$resourceView->isSelectable) { return ''; } $attributes = [ 'class' => 'form-check-input ' . $checkboxConfig['class'], 'type' => 'checkbox', 'name' => $checkboxConfig['name'], 'value' => $checkboxConfig['value'], 'checked' => $checkboxConfig['checked'], ]; return '' . '' . ''; } /** * Render resource path */ protected function renderPath(ResourceView $resourceView): string { return htmlspecialchars($resourceView->getPath()); } /** * Render reference count. Wraps the count into a button to * open the element information in case references exists. */ protected function renderReferenceCount(ResourceView $resourceView): string { if (!$resourceView->resource instanceof File) { return '-'; } $referenceCount = $this->getFileReferenceCount($resourceView->resource); if (!$referenceCount) { return '-'; } $attributes = [ 'type' => 'button', 'class' => 'btn btn-sm btn-link', 'data-filelist-action' => 'show', 'title' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references') . ' (' . $referenceCount . ')', ]; return ''; } /** * Renders the control section */ protected function renderControl(ResourceView $resourceView): string { return match ($this->mode) { Mode::MANAGE => $this->renderControlManage($resourceView), Mode::BROWSE => $this->renderControlBrowse($resourceView), }; } /** * Creates the control section for the file list module */ protected function renderControlManage(ResourceView $resourceView): string { if (!$resourceView->resource instanceof File && !$resourceView->resource instanceof Folder) { return ''; } // primary actions $userTsConfig = $this->getBackendUser()->getTSConfig(); $primaryActions = GeneralUtility::trimExplode(',', $userTsConfig['options.']['file_list.']['primaryActions'] ?? 'view,metadata,translations,delete'); $primary = new ComponentGroup('primary'); $secondary = new ComponentGroup('secondary'); $actions = [ 'edit' => $this->createControlEditContent($resourceView), 'metadata' => $this->createControlEditMetaData($resourceView), 'translations' => $this->createControlTranslation($resourceView), 'view' => $this->createControlView($resourceView), 'replace' => $this->createControlReplace($resourceView), 'rename' => $this->createControlRename($resourceView), 'download' => $this->createControlDownload($resourceView), 'info' => $this->createControlInfo($resourceView), 'delete' => $this->createControlDelete($resourceView), 'copy' => $this->createControlCopy($resourceView), 'cut' => $this->createControlCut($resourceView), 'paste' => $this->createControlPaste($resourceView), 'updateOnlineMedia' => $this->createControlUpdateOnlineMedia($resourceView), ]; foreach ($actions as $actionName => $action) { if (in_array($actionName, $primaryActions, true)) { $primary->add($actionName, $action); } else { $secondary->add($actionName, $action); } } $event = new ProcessFileListActionsEvent($primary, $secondary, $resourceView->resource, $this->request); $event = $this->eventDispatcher->dispatch($event); if ($event->hasAction('translation')) { // Always move "translations" to primary as this action has an own dropdown container and therefore cannot be a secondary action $event->moveActionTo('translation', ActionGroup::primary); } $cellOutput = ''; $output = ''; foreach ($event->getActionGroup(ActionGroup::primary)->getItems() as $action) { if (method_exists($action, 'setSize')) { $action->setSize(ButtonSize::MEDIUM); } $output .= $action; } foreach ($event->getActionGroup(ActionGroup::secondary)->getItems() as $action) { if ($action instanceof GenericButton) { $action = $this->componentFactory ->createDropDownItem() ->setTag($action->getTag()) ->setLabel($action->getLabel() ?: $action->getTitle()) ->setIcon($action->getIcon()) ->setHref($action->getHref()) ->setAttributes($action->getAttributes()); $cellOutput .= '
  • ' . $action->render() . '
  • '; continue; } if ($action instanceof LinkButton) { $attributes = []; foreach ($action->getDataAttributes() as $key => $value) { $attributes['data-' . $key] = $value; } $action = $this->componentFactory ->createDropDownItem() ->setLabel($action->getTitle()) ->setIcon($action->getIcon()) ->setHref($action->getHref()) ->setAttributes([ ...$action->getAttributes(), ...$attributes, 'role' => $action->getRole(), ]); $cellOutput .= '
  • ' . $action->render() . '
  • '; continue; } $cellOutput .= '
  • ' . $action->render() . '
  • '; } if ($cellOutput !== '') { $title = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.more'); $output .= ''; } return '
    ' . $output . '
    '; } /** * Creates the control section for the element browser */ protected function renderControlBrowse(ResourceView $resourceView): string { $fileOrFolderObject = $resourceView->resource; if (!$fileOrFolderObject instanceof File && !$fileOrFolderObject instanceof Folder) { return ''; } $actions = [ 'select' => $this->createControlSelect($resourceView), 'info' => $this->createControlInfo($resourceView), ]; // Remove empty actions $actions = array_filter($actions, static fn($action) => $action !== null && trim($action) !== ''); if (empty($actions)) { return ''; } foreach ($actions as $action) { if (method_exists($action, 'setSize')) { $action->setSize(ButtonSize::MEDIUM); } } return '
    ' . implode(' ', $actions) . '
    '; } protected function createControlSelect(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->isSelectable) { return null; } $title = sprintf( $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.selectFile'), $resourceView->getName(), ); $button = $this->componentFactory->createGenericButton(); $button->setTitle($title); $button->setAttributes([ 'type' => 'button', 'data-filelist-action' => 'select', 'aria-label' => $title, ]); $button->setIcon($this->iconFactory->getIcon('actions-plus', IconSize::SMALL)); return $button; } protected function createControlEditContent(ResourceView $resourceView): ?ButtonInterface { if (!($resourceView->resource instanceof File && $resourceView->resource->isTextFile()) || !$resourceView->canWrite()) { return null; } $button = $this->componentFactory->createLinkButton(); $button->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editcontent')); $button->setHref($resourceView->editContentUri); $button->setIcon($this->iconFactory->getIcon('actions-page-open', IconSize::SMALL)); return $button; } protected function createControlEditMetaData(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->getMetaDataUid() || !$this->getBackendUser()->checkLanguageAccess(0)) { return null; } $button = $this->componentFactory->createLinkButton(); $button->setSize(ButtonSize::MEDIUM); $button->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editMetadata')); $button->setHref($resourceView->editDataUri); $button->setIcon($this->iconFactory->getIcon('actions-open', IconSize::SMALL)); return $button; } protected function createControlView(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->getPublicUrl()) { return null; } $button = $this->componentFactory->createLinkButton(); $button->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.view')); $button->setHref($resourceView->getPublicUrl()); $button->setAttributes(['target' => '_blank']); $button->setSize(ButtonSize::MEDIUM); $button->setIcon($this->iconFactory->getIcon('actions-document-view', IconSize::SMALL)); return $button; } protected function createControlReplace(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->canReplace()) { return null; } $button = $this->componentFactory->createGenericButton(); $button->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.replace')); $button->setAttributes(['type' => 'button', 'data-filelist-action' => 'replace']); $button->setIcon($this->iconFactory->getIcon('actions-edit-replace', IconSize::SMALL)); return $button; } protected function createControlRename(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->canRename()) { return null; } $button = $this->componentFactory->createGenericButton(); $button->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.rename')); $button->setAttributes(['type' => 'button', 'data-filelist-action' => 'rename']); $button->setIcon($this->iconFactory->getIcon('actions-edit-rename', IconSize::SMALL)); return $button; } protected function createControlDownload(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->canRead() || !(bool)($this->getBackendUser()->getTSConfig()['options.']['file_list.']['fileDownload.']['enabled'] ?? true)) { return null; } if (!$resourceView->isDownloadable) { return null; } $button = $this->componentFactory->createGenericButton(); $button->setLabel($this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:download')); $button->setAttributes([ 'type' => 'button', 'data-filelist-action' => 'download', 'data-filelist-action-url' => $this->uriBuilder->buildUriFromRoute('file_download'), ]); $button->setIcon($this->iconFactory->getIcon('actions-download', IconSize::SMALL)); return $button; } protected function createControlInfo(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->canRead()) { return null; } $button = $this->componentFactory->createGenericButton(); $button->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info')); $button->setAttributes([ 'type' => 'button', 'data-filelist-action' => 'show', ]); $button->setIcon($this->iconFactory->getIcon('actions-document-info', IconSize::SMALL)); return $button; } protected function createControlDelete(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->canDelete()) { return null; } $recordInfo = $resourceView->getName(); $referenceCountText = ''; if ($resourceView->resource instanceof Folder) { $identifier = $resourceView->getIdentifier(); $deleteType = 'delete_folder'; if ($this->getBackendUser()->shallDisplayDebugInformation()) { $recordInfo .= ' [' . $identifier . ']'; } } else { $referenceCountText = BackendUtility::referenceCount('sys_file', (int)$resourceView->getUid(), LF . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile')); $deleteType = 'delete_file'; if ($this->getBackendUser()->shallDisplayDebugInformation()) { $recordInfo .= ' [sys_file:' . $resourceView->getUid() . ']'; } } $title = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete'); $button = $this->componentFactory->createGenericButton(); $button->setLabel($title); $button->setIcon($this->iconFactory->getIcon('actions-edit-delete', IconSize::SMALL)); $button->setAttributes([ 'type' => 'button', 'data-title' => $title, 'data-content' => sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.delete'), trim($recordInfo)) . $referenceCountText, 'data-filelist-action' => 'delete', 'data-filelist-delete' => 'true', 'data-filelist-delete-identifier' => $resourceView->getIdentifier(), 'data-filelist-delete-url' => $this->uriBuilder->buildUriFromRoute('tce_file'), 'data-filelist-delete-type' => $deleteType, 'data-filelist-delete-check' => $this->getBackendUser()->jsConfirmation(JsConfirmation::DELETE) ? '1' : '0', 'data-redirect-url' => $this->createModuleUri(), ]); $button->setSize(ButtonSize::MEDIUM); return $button; } /** * Creates the file metadata translation dropdown. Each item links * to the corresponding metadata translation, while depending on * the current state, either a new translation can be created or * an existing translation can be edited. */ protected function createControlTranslation(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->resource instanceof File) { return null; } $backendUser = $this->getBackendUser(); // Fetch all system languages except "default (0)" and "all languages (-1)" $systemLanguages = array_filter( $this->translateTools->getSystemLanguages(), static fn(array $languageRecord): bool => $languageRecord['uid'] > 0 && $backendUser->checkLanguageAccess($languageRecord['uid']) ); if ($systemLanguages === [] || !$this->tcaSchemaFactory->get('sys_file_metadata')->isLanguageAware() || !$resourceView->resource->isIndexed() || !$resourceView->resource->checkActionPermission('editMeta') || !$backendUser->check('tables_modify', 'sys_file_metadata') ) { // Early return in case no system languages exists or metadata // of this file can not be created / edited by the current user. return null; } $dropdownItems = []; $metaDataRecord = $resourceView->resource->getMetaData()->get(); $existingTranslations = $this->getTranslationsForMetaData($metaDataRecord); foreach ($systemLanguages as $languageId => $language) { if (!isset($existingTranslations[$languageId]) && !($metaDataRecord['uid'] ?? false)) { // Skip if neither a translation nor the metadata uid exists continue; } if (isset($existingTranslations[$languageId])) { // Set options for edit action of an existing translation $title = sprintf($this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:editMetadataForLanguage'), $language['title']); $actionType = 'edit'; $url = (string)$this->uriBuilder->buildUriFromRoute( 'record_edit', [ 'edit' => [ 'sys_file_metadata' => [ $existingTranslations[$languageId]['uid'] => 'edit', ], ], 'module' => 'media_management', 'returnUrl' => $this->createModuleUri(), ] ); } else { // Set options for "create new" action of a new translation using localization wizard $title = sprintf($this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:createMetadataForLanguage'), $language['title']); $actionType = 'new'; $metaDataRecordId = (int)($metaDataRecord['uid'] ?? 0); $dropdownItem = $this->componentFactory->createDropDownItem() ->setTag('typo3-backend-localization-button') ->setAttribute('record-type', 'sys_file_metadata') ->setAttribute('record-uid', (string)$metaDataRecordId) ->setAttribute('target-language', (string)$languageId) ->setLabel($title); if (!empty($language['flagIcon'])) { $dropdownItem->setIcon($this->iconFactory->getIcon($language['flagIcon'], IconSize::SMALL, 'overlay-' . $actionType)); } $dropdownItems[] = $dropdownItem; continue; } $dropdownItem = $this->componentFactory->createDropDownItem(); $dropdownItem->setLabel($title); $dropdownItem->setHref($url); $dropdownItem->setIcon($this->iconFactory->getIcon($language['flagIcon'], IconSize::SMALL, 'overlay-' . $actionType)); $dropdownItems[] = $dropdownItem; } if (empty($dropdownItems)) { return null; } $dropdownButton = $this->componentFactory->createDropDownButton(); $dropdownButton->setLabel($this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:translations')); $dropdownButton->setIcon($this->iconFactory->getIcon('actions-translate', IconSize::SMALL)); foreach ($dropdownItems as $dropdownItem) { $dropdownButton->addItem($dropdownItem); } return $dropdownButton; } protected function createControlCopy(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->canRead() || !$resourceView->canCopy()) { return null; } if ($this->clipObj->current === 'normal') { $isSelected = $this->clipObj->isSelected('_FILE', md5($resourceView->getIdentifier())); $button = $this->componentFactory->createLinkButton(); $button->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . ($isSelected === 'copy' ? 'copyrelease' : 'copy'))); $button->setHref($this->clipObj->selUrlFile($resourceView->getIdentifier(), true, $isSelected === 'copy')); $button->setIcon($this->iconFactory->getIcon($isSelected === 'copy' ? 'actions-edit-copy-release' : 'actions-edit-copy', IconSize::SMALL)); return $button; } return null; } protected function createControlCut(ResourceView $resourceView): ?ButtonInterface { if (!$resourceView->canRead() || !$resourceView->canMove()) { return null; } if ($this->clipObj->current === 'normal') { $isSelected = $this->clipObj->isSelected('_FILE', md5($resourceView->getIdentifier())); $button = $this->componentFactory->createLinkButton(); $button->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . ($isSelected === 'cut' ? 'cutrelease' : 'cut'))); $button->setHref($this->clipObj->selUrlFile($resourceView->getIdentifier(), false, $isSelected === 'cut')); $button->setIcon($this->iconFactory->getIcon($isSelected === 'cut' ? 'actions-edit-cut-release' : 'actions-edit-cut', IconSize::SMALL)); return $button; } return null; } protected function createControlPaste(ResourceView $resourceView): ?ButtonInterface { $permission = ($this->clipObj->clipData[$this->clipObj->current]['mode'] ?? '') === 'copy' ? 'copy' : 'move'; $addPasteButton = $this->folderObject->checkActionPermission($permission); $elementFromTable = $this->clipObj->elFromTable('_FILE'); if ($elementFromTable === [] || !$addPasteButton || !$resourceView->canRead() || !$resourceView->canWrite() || !$resourceView->resource instanceof Folder) { return null; } foreach ($elementFromTable as $element) { $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element); if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $resourceView->resource) ) { // In case folder is already present in the target folder, return actions without paste button return null; } } $pasteTitle = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_pasteInto'); $button = $this->componentFactory->createLinkButton(); $button->setTitle($pasteTitle); $button->setHref($this->clipObj->pasteUrl('_FILE', $resourceView->getIdentifier())); $button->setDataAttributes([ 'title' => $pasteTitle, 'bs-content' => $this->clipObj->confirmMsgText('_FILE', $resourceView->getName(), 'into'), ]); $button->setIcon($this->iconFactory->getIcon('actions-document-paste-into', IconSize::SMALL)); return $button; } protected function createControlUpdateOnlineMedia(ResourceView $resourceView): ?ButtonInterface { if (!($resourceView->resource instanceof File) || !$resourceView->canEditMetadata() || !$this->getBackendUser()->checkLanguageAccess(0) || !$this->onlineMediaHelperRegistry->hasOnlineMediaHelper($resourceView->resource->getExtension()) ) { return null; } $title = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:reloadMetadata'); $button = $this->componentFactory->createGenericButton(); $button->setLabel($title); $button->setIcon($this->iconFactory->getIcon('actions-refresh', IconSize::SMALL)); $button->setAttributes([ 'type' => 'button', 'data-title' => $title, 'data-filelist-action' => 'updateOnlineMedia', 'data-filelist-action-url' => $this->uriBuilder->buildUriFromRoute('file_update_online_media'), ]); return $button; } protected function isEditMetadataAllowed(File $file): bool { return $file->isIndexed() && $file->checkActionPermission('editMeta') && $this->getUserPermissions()->editMetaData; } /** * Render convenience actions, such as "check all" * * @return string HTML markup for the checkbox actions */ protected function renderCheckboxActions(): string { // Early return in case there are no items if (!$this->totalItems) { return ''; } $lang = $this->getLanguageService(); $dropdownItems['checkAll'] = '
  • '; $dropdownItems['checkNone'] = '
  • '; $dropdownItems['toggleSelection'] = '
  • '; return ' '; } /** * Determine the concrete table name by checking if * the field exists, while sys_file takes precedence. */ protected function getConcreteTableName(string $fieldName): string { if ($this->tcaSchemaFactory->get('sys_file')->hasField($fieldName)) { return 'sys_file'; } return 'sys_file_metadata'; } /** * Returns list URL; This is the URL of the current script with id and imagemode parameters, that's all. */ public function createModuleUri(array $params = []): ?string { $request = $this->request; $queryParams = $request->getQueryParams(); $parsedBody = $request->getParsedBody(); $route = $request->getAttribute('route'); if (!$route instanceof Route) { return null; } $baseParams = [ 'currentPage' => $this->currentPage, 'id' => $this->folderObject->getCombinedIdentifier(), 'searchTerm' => $this->searchDemand ? $this->searchDemand->getSearchTerm() : '', ]; // Keep ElementBrowser Settings if ($mode = $parsedBody['mode'] ?? $queryParams['mode'] ?? null) { $baseParams['mode'] = $mode; } foreach (['fieldReference', 'allowedTypes', 'disallowedFileExtensions', 'irreObjectId'] as $paramName) { if ($value = $parsedBody[$paramName] ?? $queryParams[$paramName] ?? null) { $baseParams[$paramName] = $value; } } // Keep LinkHandler Settings if ($act = ($parsedBody['act'] ?? $queryParams['act'] ?? null)) { $baseParams['act'] = $act; } if ($linkHandlerParams = ($parsedBody['P'] ?? $queryParams['P'] ?? null)) { $baseParams['P'] = $linkHandlerParams; } $params = array_replace_recursive($baseParams, $params); // Expanded folder is used in the element browser. // We always map it to the id here. $params['expandFolder'] = $params['id']; $params = array_filter($params, static function ($value) { return (is_array($value) && $value !== []) || (trim((string)$value) !== ''); }); return (string)$this->uriBuilder->buildUriFromRequest($request, $params); } protected function createEditDataUriForResource(ResourceInterface $resource): ?string { if ($resource instanceof File && $this->isEditMetadataAllowed($resource) && ($metaDataUid = $resource->getMetaData()->offsetGet('uid')) ) { $parameter = [ 'edit' => ['sys_file_metadata' => [$metaDataUid => 'edit']], 'module' => 'media_management', 'returnUrl' => $this->createModuleUri(), ]; return (string)$this->uriBuilder->buildUriFromRoute('record_edit', $parameter); } return null; } protected function createEditContentUriForResource(ResourceInterface $resource): ?string { if ($resource instanceof File && $resource->checkActionPermission('write') && $resource->isTextFile() ) { $parameter = [ 'target' => $resource->getCombinedIdentifier(), 'returnUrl' => $this->createModuleUri(), ]; return (string)$this->uriBuilder->buildUriFromRoute('file_edit', $parameter); } return null; } protected function createModuleUriForResource(ResourceInterface $resource): ?string { if ($resource instanceof Folder) { $parameter = [ 'id' => $resource->getCombinedIdentifier(), 'searchTerm' => '', 'currentPage' => 1, ]; return (string)$this->createModuleUri($parameter); } if ($resource instanceof File) { return $this->createEditDataUriForResource($resource); } return null; } /** * @return ResourceInterface[] */ protected function sortResources(array $resources, string $sortField): array { $collator = new \Collator((string)($this->getLanguageService()->getLocale() ?? 'en')); $collator->setAttribute(\Collator::NUMERIC_COLLATION, \Collator::ON); $sortMultiplier = $this->sortDirection === SortDirection::DESCENDING ? -1 : 1; uksort($resources, function (int $index1, int $index2) use ($sortField, $sortMultiplier, $resources, $collator) { $resource1 = $resources[$index1]; $resource2 = $resources[$index2]; // Folders are always prioritized above files if ($resource1 instanceof File && $resource2 instanceof Folder) { return 1 * $sortMultiplier; } if ($resource1 instanceof Folder && $resource2 instanceof File) { return -1 * $sortMultiplier; } // Sort by value first $result = (int)$collator->compare( $this->getSortingValue($resource1, $sortField), $this->getSortingValue($resource2, $sortField) ); // Use index as tiebreaker for stable sorting if ($result === 0) { $result = $index1 <=> $index2; } return $result * $sortMultiplier; }); return $resources; } protected function getSortingValue(ResourceInterface $resource, string $sortField): string { if ($resource instanceof File) { return $this->getSortingValueForFile($resource, $sortField); } if ($resource instanceof Folder) { return $this->getSortingValueForFolder($resource, $sortField); } return ''; } protected function getSortingValueForFile(File $resource, string $sortField): string { switch ($sortField) { case 'fileext': return $resource->getExtension(); case 'size': return $resource->getSize() . 's'; case 'rw': return ($resource->checkActionPermission('read') ? 'R' : '') . ($resource->checkActionPermission('write') ? 'W' : ''); case '_REF_': return $this->getFileReferenceCount($resource) . 'ref'; case 'tstamp': return $resource->getModificationTime() . 't'; case 'crdate': return $resource->getCreationTime() . 'c'; case 'file': return $resource->getName(); default: return $resource->hasProperty($sortField) ? (string)$resource->getProperty($sortField) : ''; } } protected function getSortingValueForFolder(Folder $resource, string $sortField): string { switch ($sortField) { case 'size': try { $fileCount = $resource->getFileCount(); } catch (InsufficientFolderAccessPermissionsException $e) { $fileCount = 0; } return '0' . $fileCount . 's'; case 'rw': return ($resource->checkActionPermission('read') ? 'R' : '') . ($resource->checkActionPermission('write') ? 'W' : ''); case 'name': return $resource->getName(); case 'tstamp': return $resource->getModificationTime() . 't'; case 'crdate': return $resource->getCreationTime() . 'c'; default: return ''; } } public function getFieldLabel(string $field): string { $lang = $this->getLanguageService(); if ($specialLabel = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $field)) { return $specialLabel; } if ($customLabel = $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:c_' . $field)) { return $customLabel; } $concreteTableName = $this->getConcreteTableName($field); $schema = $this->tcaSchemaFactory->has($concreteTableName) ? $this->tcaSchemaFactory->get($concreteTableName) : null; $label = ($schema?->hasField($field) ? $schema->getField($field)->getLabel() : '') ?: null; // In case global TSconfig exists we have to check if the label is overridden there $tsConfig = BackendUtility::getPagesTSconfig(0); $label = $lang->translateLabel( $tsConfig['TCEFORM.'][$concreteTableName . '.'][$field . '.']['label.'] ?? [], $tsConfig['TCEFORM.'][$concreteTableName . '.'][$field . '.']['label'] ?? $label ?? 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $field ); return $label ?: $field; } /** * Counts how often the given file is referenced. This is done by * looking up the file in the "sys_refindex" table, while excluding * sys_file_metadata relations as these are no such references. */ protected function getFileReferenceCount(File $file): int { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex'); return (int)$queryBuilder ->count('*') ->from('sys_refindex') ->where( $queryBuilder->expr()->eq( 'ref_table', $queryBuilder->createNamedParameter('sys_file') ), $queryBuilder->expr()->eq( 'ref_uid', $queryBuilder->createNamedParameter($file->getUid(), Connection::PARAM_INT) ), $queryBuilder->expr()->neq( 'tablename', $queryBuilder->createNamedParameter('sys_file_metadata') ) ) ->executeQuery() ->fetchOne(); } protected function getUserPermissions(): UserPermissions { return new UserPermissions($this->getBackendUser()->check('tables_modify', 'sys_file_metadata')); } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } }