Filelist module. * * @internal this is a concrete TYPO3 controller implementation and solely used for EXT:filelist and not part of TYPO3's Core API. */ #[AsController] class FileListController { protected string $id = ''; protected string $cmd = ''; protected string $searchTerm = ''; protected int $currentPage = 1; protected bool $allowClipboard = true; protected ?Folder $folderObject = null; protected ?DuplicationBehavior $overwriteExistingFiles = null; protected ?ModuleTemplate $view = null; protected ?FileList $filelist = null; protected ?ModuleData $moduleData = null; public function __construct( protected readonly UriBuilder $uriBuilder, protected readonly PageRenderer $pageRenderer, protected readonly IconFactory $iconFactory, protected readonly ResourceFactory $resourceFactory, protected readonly ModuleTemplateFactory $moduleTemplateFactory, protected readonly BackendViewFactory $viewFactory, protected readonly ResponseFactoryInterface $responseFactory, protected readonly TcaSchemaFactory $tcaSchemaFactory, protected readonly ComponentFactory $componentFactory, protected readonly LoggerInterface $logger, protected readonly FlashMessageService $flashMessageService, ) {} public function handleRequest(ServerRequestInterface $request): ResponseInterface { $lang = $this->getLanguageService(); $backendUser = $this->getBackendUser(); $this->moduleData = $request->getAttribute('moduleData'); $this->view = $this->moduleTemplateFactory->create($request); $this->view->setTitle($lang->translate('title', 'filelist.module')); $queryParams = $request->getQueryParams(); $parsedBody = $request->getParsedBody(); $this->id = (string)($parsedBody['id'] ?? $queryParams['id'] ?? ''); $this->cmd = (string)($parsedBody['cmd'] ?? $queryParams['cmd'] ?? ''); $this->searchTerm = (string)trim($parsedBody['searchTerm'] ?? $queryParams['searchTerm'] ?? ''); $this->currentPage = (int)($parsedBody['currentPage'] ?? $queryParams['currentPage'] ?? 1); $duplicationBehaviorFromRequest = $parsedBody['overwriteExistingFiles'] ?? $queryParams['overwriteExistingFiles'] ?? ''; $this->overwriteExistingFiles = DuplicationBehavior::tryFrom($duplicationBehaviorFromRequest) ?? DuplicationBehavior::getDefaultDuplicationBehaviour(); $storage = null; try { if ($this->id !== '') { $backendUser->evaluateUserSpecificFileFilterSettings(); $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByCombinedIdentifier($this->id); if ($storage !== null) { $identifier = substr($this->id, strpos($this->id, ':') + 1); if (!$storage->hasFolder($identifier)) { // @todo: should we redirect to form engine instead of implicitly showing the folder, when a file is requested? // (that way breadcrumb would not need to handle module-specific formegine routes) $identifier = $storage->getFolderIdentifierFromFileIdentifier($identifier); } $this->folderObject = $storage->getFolder($identifier); // Disallow access to fallback storage 0 if ($storage->isFallbackStorage()) { throw new InsufficientFolderAccessPermissionsException( 'You are not allowed to access files outside your storages', 1434539815 ); } // Disallow the rendering of the processing folder (e.g. could be called manually) if ($storage->isProcessingFolder($this->folderObject)) { $this->folderObject = $storage->getRootLevelFolder(); } } } else { // Take the first available storage $fileStorages = array_filter($backendUser->getFileStorages(), static fn(ResourceStorage $storage) => $storage->isBrowsable()); $fileStorage = reset($fileStorages); if ($fileStorage) { $this->folderObject = $fileStorage->getRootLevelFolder(); } else { throw new \RuntimeException('Could not find any folder to be displayed.', 1349276894); } } if ($this->folderObject && !$this->folderObject->getStorage()->isWithinFileMountBoundaries($this->folderObject)) { throw new \RuntimeException('Folder not accessible.', 1430409089); } } catch (FolderDoesNotExistException|InsufficientFolderAccessPermissionsException $permissionException) { $this->folderObject = null; if ($storage->getDriverType() === 'Local' && !$storage->isOnline()) { // If the base folder for a local storage does not exists, the storage is marked as offline and the // access permission exception is thrown. In this case we however want to display another error message. // @see https://forge.typo3.org/issues/85323 $this->addFlashMessage( sprintf($lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:localStorageOfflineMessage'), $storage->getName()), $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:localStorageOfflineTitle'), ContextualFeedbackSeverity::ERROR ); } else { $this->addFlashMessage( sprintf($lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:missingFolderPermissionsMessage'), $this->id), $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:missingFolderPermissionsTitle'), ContextualFeedbackSeverity::ERROR ); } } catch (Exception $fileException) { $this->folderObject = null; // Take the first object of the first storage $fileStorages = $backendUser->getFileStorages(); $fileStorage = reset($fileStorages); if ($fileStorage instanceof ResourceStorage) { $this->folderObject = $fileStorage->getRootLevelFolder(); if (!$fileStorage->isWithinFileMountBoundaries($this->folderObject)) { $this->folderObject = null; } } if (!$this->folderObject) { $this->addFlashMessage( sprintf($lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:folderNotFoundMessage'), $this->id), $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:folderNotFoundTitle'), ContextualFeedbackSeverity::ERROR ); } } catch (\RuntimeException $e) { $this->folderObject = null; $this->addFlashMessage( $e->getMessage() . ' (' . $e->getCode() . ')', $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:folderNotFoundTitle'), ContextualFeedbackSeverity::ERROR ); } if ($this->folderObject && !$this->folderObject->getStorage()->checkFolderActionPermission('read', $this->folderObject) ) { $this->folderObject = null; } $this->view->assign('currentIdentifier', $this->folderObject ? $this->folderObject->getCombinedIdentifier() : ''); $javaScriptRenderer = $this->pageRenderer->getJavaScriptRenderer(); $javaScriptRenderer->addJavaScriptModuleInstruction( JavaScriptModuleInstruction::create('@typo3/filelist/file-list.js')->instance() ); $this->pageRenderer->loadJavaScriptModule('@typo3/filelist/file-list-dragdrop.js'); $this->pageRenderer->loadJavaScriptModule('@typo3/filelist/file-list-transfer-handler.js'); $this->pageRenderer->addInlineLanguageLabelFile('EXT:filelist/Resources/Private/Language/locallang_transfer_handler.xlf'); $this->pageRenderer->loadJavaScriptModule('@typo3/filelist/file-list-actions.js'); $this->pageRenderer->loadJavaScriptModule('@typo3/filelist/file-list-rename-handler.js'); $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf', 'file_rename'); $this->pageRenderer->loadJavaScriptModule('@typo3/filelist/file-list-replace-handler.js'); $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf', 'file_replace'); $this->pageRenderer->loadJavaScriptModule('@typo3/filelist/file-delete.js'); $this->pageRenderer->loadJavaScriptModule('@typo3/backend/context-menu.js'); $this->pageRenderer->loadJavaScriptModule('@typo3/backend/clipboard-panel.js'); $this->pageRenderer->loadJavaScriptModule('@typo3/backend/localization.js'); $this->pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/Wizards/localization.xlf'); $this->pageRenderer->loadJavaScriptModule('@typo3/backend/multi-record-selection.js'); $this->pageRenderer->loadJavaScriptModule('@typo3/backend/column-selector-button.js'); $this->pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf', 'buttons'); $this->initializeModule(); // In case the folderObject is NULL, the request is either invalid or the user // does not have necessary permissions. Just render and return the "empty" view. if ($this->folderObject === null) { return $this->view->renderResponse('File/List'); } return $this->processRequest($request); } protected function processRequest(ServerRequestInterface $request): ResponseInterface { $lang = $this->getLanguageService(); // Initialize FileList, including the clipboard actions $this->initializeFileList($request); // Generate the file listing markup $this->generateFileList($request); // Generate the clipboard, if enabled $this->view->assign('showClipboardPanel', (bool)$this->moduleData->get('clipBoard')); // Register drag-uploader $this->registerDragUploader(); // Register the display thumbnails / show clipboard checkboxes $this->registerFileListCheckboxes(); // Register additional doc header buttons $this->registerAdditionalDocHeaderButtons(); // Add additional view variables $this->view->assignMultiple([ 'headline' => $this->getModuleHeadline(), 'folderIdentifier' => $this->folderObject->getCombinedIdentifier(), 'searchTerm' => $this->searchTerm, ]); // Overwrite the default module title, adding the specific module headline (the folder name) $this->view->setTitle( $lang->translate('title', 'filelist.module'), $this->getModuleHeadline() ); $this->view->getDocHeaderComponent()->setBreadcrumbContext(new BreadcrumbContext($this->folderObject)); return $this->view->renderResponse('File/List'); } protected function initializeModule(): void { $userTsConfig = $this->getBackendUser()->getTSConfig(); // Set predefined value for DisplayThumbnails: if (($userTsConfig['options.']['file_list.']['enableDisplayThumbnails'] ?? '') === 'activated') { $this->moduleData->set('displayThumbs', true); } elseif (($userTsConfig['options.']['file_list.']['enableDisplayThumbnails'] ?? '') === 'deactivated') { $this->moduleData->set('displayThumbs', false); } // Set predefined value for Clipboard: if (($userTsConfig['options.']['file_list.']['enableClipBoard'] ?? '') === 'activated') { $this->moduleData->set('clipBoard', true); $this->allowClipboard = false; } elseif (($userTsConfig['options.']['file_list.']['enableClipBoard'] ?? '') === 'selectable') { $this->allowClipboard = true; } elseif (($userTsConfig['options.']['file_list.']['enableClipBoard'] ?? '') === 'deactivated') { $this->moduleData->set('clipBoard', false); $this->allowClipboard = false; } // Set predefined value for viewMode: $viewMode = ViewMode::tryFrom($this->moduleData->get('viewMode') ?? '') ?? ViewMode::tryFrom($userTsConfig['options.']['defaultResourcesViewMode'] ?? '') ?? ViewMode::TILES; $this->moduleData->set('viewMode', $viewMode->value); } protected function initializeFileList(ServerRequestInterface $request): void { // Create the file list $this->filelist = GeneralUtility::makeInstance(FileList::class, $request); $this->filelist->viewMode = ViewMode::tryFrom($this->moduleData->get('viewMode')) ?? ViewMode::TILES; $this->filelist->thumbs = ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] ?? false) && $this->moduleData->get('displayThumbs'); // Create clipboard object and initialize it $CB = array_replace_recursive($request->getQueryParams()['CB'] ?? [], $request->getParsedBody()['CB'] ?? []); if (($this->cmd === 'copyMarked' || $this->cmd === 'removeMarked')) { // Get CBC from request, and map the element values, since they must either be the file identifier, // in case the element should be transferred to the clipboard, or false if it should be removed. $CBC = array_map(fn($item) => $this->cmd === 'copyMarked' ? $item : false, (array)($request->getParsedBody()['CBC'] ?? [])); // Cleanup CBC $CB['el'] = $this->filelist->clipObj->cleanUpCBC($CBC, '_FILE'); } if (!$this->moduleData->get('clipBoard')) { $CB['setP'] = 'normal'; } $this->filelist->clipObj->setCmd($CB); $this->filelist->clipObj->cleanCurrent(); $this->filelist->clipObj->endClipboard(); // If the "cmd" was to delete files from the list, do that: if ($this->cmd === 'delete') { $items = $this->filelist->clipObj->cleanUpCBC( (array)($request->getParsedBody()['CBC'] ?? []), '_FILE', true ); if (!empty($items)) { // Make command array: $FILE = []; foreach ($items as $clipboardIdentifier => $combinedIdentifier) { $FILE['delete'][] = ['data' => $combinedIdentifier]; $this->filelist->clipObj->removeElement($clipboardIdentifier); } // Init file processing object for deleting and pass the cmd array. $fileProcessor = GeneralUtility::makeInstance(ExtendedFileUtility::class); $fileProcessor->setActionPermissions(); $fileProcessor->setExistingFilesConflictMode($this->overwriteExistingFiles); $fileProcessor->start($FILE, []); $fileProcessor->processData(); // Clean & Save clipboard state $this->filelist->clipObj->cleanCurrent(); $this->filelist->clipObj->endClipboard(); } } // Start up the file list by including processed settings. $this->filelist->start( $this->folderObject, MathUtility::forceIntegerInRange($this->currentPage, 1, 100000), (string)($this->moduleData->get('sortField') ?: 'name'), SortDirection::tryFrom($this->moduleData->get('sortDirection') ?? '') ?? SortDirection::ASCENDING ); // Only add selected columns if the feature is enabled if ($this->getBackendUser()->getTSConfig()['options.']['file_list.']['displayColumnSelector'] ?? true) { $this->filelist->setColumnsToRender($this->getBackendUser()->getModuleData('list/displayFields')['_FILE'] ?? []); } $resourceSelectableMatcher = GeneralUtility::makeInstance(Matcher::class); $resourceSelectableMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFileTypeMatcher::class)); $resourceSelectableMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class)); $this->filelist->setResourceSelectableMatcher($resourceSelectableMatcher); $resourceDownloadMatcher = GeneralUtility::makeInstance(Matcher::class); $resourceDownloadMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFileTypeMatcher::class)); $resourceDownloadMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class)); $this->filelist->setResourceDownloadMatcher($resourceDownloadMatcher); } protected function generateFileList(ServerRequestInterface $request): void { $lang = $this->getLanguageService(); // If a searchTerm is provided, create the searchDemand object $searchDemand = $this->searchTerm !== '' ? FileSearchDemand::createForSearchTerm($this->searchTerm)->withRecursive() : null; // Generate the list, if accessible if ($this->folderObject->getStorage()->isBrowsable()) { $fileListView = $this->viewFactory->create($request); $this->view->assignMultiple([ 'listHtml' => $this->filelist->render($searchDemand, $fileListView), 'listUrl' => $this->filelist->createModuleUri(), 'totalItems' => $this->filelist->totalItems, ]); // Add edit metadata configuration, if user can edit default language if ($this->getBackendUser()->checkLanguageAccess(0)) { $this->view->assign( 'editActionConfiguration', GeneralUtility::jsonEncodeForHtmlAttribute([ 'idField' => 'filelistMetaUid', 'table' => 'sys_file_metadata', 'returnUrl' => $this->filelist->createModuleUri(), ]) ); $allowedFields = BackendUtility::getAllowedFieldsForTable('sys_file_metadata'); $columnsOnly = array_filter($this->filelist->fieldArray, static fn($field) => in_array($field, $allowedFields, true)); if ($columnsOnly !== []) { $this->view->assign( 'editColumnsActionConfiguration', GeneralUtility::jsonEncodeForHtmlAttribute([ 'idField' => 'filelistMetaUid', 'table' => 'sys_file_metadata', 'columnsOnly' => array_values($columnsOnly), 'returnUrl' => $this->filelist->createModuleUri(), ]) ); } } // Assign meta information for the multi record selection $this->view->assign( 'deleteActionConfiguration', GeneralUtility::jsonEncodeForHtmlAttribute([ 'ok' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete'), 'title' => $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:deleteMarked'), 'content' => $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:deleteMarkedWarning'), ]), ); // Add download button configuration, if file download is enabled if ($this->getBackendUser()->getTSConfig()['options.']['file_list.']['fileDownload.']['enabled'] ?? true) { $this->view->assign( 'downloadActionConfiguration', GeneralUtility::jsonEncodeForHtmlAttribute([ 'downloadUrl' => (string)$this->uriBuilder->buildUriFromRoute('file_download'), ]) ); } } else { $this->addFlashMessage( $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:storageNotBrowsableMessage'), $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:storageNotBrowsableTitle') ); } } protected function registerDragUploader(): void { // Include DragUploader only if we have write access if ($this->folderObject->checkActionPermission('write') && $this->folderObject->getStorage()->checkUserActionPermission('add', 'File') ) { $lang = $this->getLanguageService(); $this->pageRenderer->loadJavaScriptModule('@typo3/backend/drag-uploader.js'); $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf', 'file_upload'); $this->pageRenderer->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_core.xlf', 'file_download'); $this->pageRenderer->addInlineLanguageLabelArray([ 'type.file' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:file'), 'permissions.read' => $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:read'), 'permissions.write' => $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:write'), 'online_media.update.success' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.update.success'), 'online_media.update.error' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.update.error'), 'labels.contextMenu.open' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.contextMenu.open'), ]); $defaultDuplicationBehavior = DuplicationBehavior::getDefaultDuplicationBehaviour($this->getBackendUser()); $this->view->assign('dragUploader', [ 'fileDenyPattern' => $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] ?? null, 'maxFileSize' => GeneralUtility::getMaxUploadFileSize() * 1024, 'defaultDuplicationBehaviourAction' => $defaultDuplicationBehavior->value, ]); } } protected function registerFileListCheckboxes(): void { $lang = $this->getLanguageService(); $userTsConfig = $this->getBackendUser()->getTSConfig(); $enableClipBoard = ($userTsConfig['options.']['file_list.']['enableClipBoard'] ?? ''); $this->view->assign('enableClipBoard', [ 'enabled' => $enableClipBoard === 'activated' || $enableClipBoard === 'selectable', 'label' => htmlspecialchars($lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clipBoard')), 'mode' => $this->filelist->clipObj->current, ]); } /** * Create the panel of buttons for submitting the form or otherwise perform operations. */ protected function registerAdditionalDocHeaderButtons(): void { $lang = $this->getLanguageService(); // ViewMode $viewModeItems = []; $viewModeItems[] = $this->componentFactory->createDropDownRadio() ->setActive($this->moduleData->get('viewMode') === ViewMode::TILES->value) ->setHref($this->filelist->createModuleUri(['viewMode' => ViewMode::TILES->value])) ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.tiles')) ->setIcon($this->iconFactory->getIcon('actions-viewmode-tiles')); $viewModeItems[] = $this->componentFactory->createDropDownRadio() ->setActive($this->moduleData->get('viewMode') === ViewMode::LIST->value) ->setHref($this->filelist->createModuleUri(['viewMode' => ViewMode::LIST->value])) ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.list')) ->setIcon($this->iconFactory->getIcon('actions-viewmode-list')); $viewModeItems[] = $this->componentFactory->createDropDownDivider(); if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] && ($this->getBackendUser()->getTSConfig()['options.']['file_list.']['enableDisplayThumbnails'] ?? '') === 'selectable') { $viewModeItems[] = $this->componentFactory->createDropDownToggle() ->setActive((bool)$this->moduleData->get('displayThumbs')) ->setHref($this->filelist->createModuleUri(['displayThumbs' => $this->moduleData->get('displayThumbs') ? 0 : 1])) ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.showThumbnails')) ->setIcon($this->iconFactory->getIcon('actions-image')); } if ($this->allowClipboard) { $viewModeItems[] = $this->componentFactory->createDropDownToggle() ->setActive((bool)$this->moduleData->get('clipBoard')) ->setHref($this->filelist->createModuleUri(['clipBoard' => $this->moduleData->get('clipBoard') ? 0 : 1])) ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.showClipboard')) ->setIcon($this->iconFactory->getIcon('actions-clipboard')); } if (($this->getBackendUser()->getTSConfig()['options.']['file_list.']['displayColumnSelector'] ?? true) && $this->moduleData->get('viewMode') === ViewMode::LIST->value) { $viewModeItems[] = $this->componentFactory->createDropDownDivider(); $viewModeItems[] = $this->componentFactory->createDropDownItem() ->setTag('typo3-backend-column-selector-button') ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.selectColumns')) ->setAttributes([ 'data-url' => (string)$this->uriBuilder->buildUriFromRoute( 'ajax_show_columns_selector', ['table' => '_FILE'] ), 'data-target' => (string)$this->filelist->createModuleUri(), 'data-title' => sprintf( $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:showColumnsSelection'), $this->tcaSchemaFactory->get('sys_file')->getTitle($lang->sL(...)), ), 'data-button-ok' => $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:updateColumnView'), 'data-button-close' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel'), 'data-error-message' => $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:updateColumnView.error'), ]) ->setIcon($this->iconFactory->getIcon('actions-options')); } $sortingButton = $this->componentFactory->createDropDownButton() ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting')) ->setIcon($this->iconFactory->getIcon($this->filelist->sortDirection->getIconIdentifier())) ->setShowLabelText(true); $sortingModeButtons = []; $sortableFields = $this->filelist->getSortableFields(); if (count($sortableFields) > 1) { foreach ($sortableFields as $field) { $label = $this->filelist->getFieldLabel($field); $sortingModeButtons[] = $this->componentFactory->createDropDownRadio() ->setActive($this->filelist->sortField === $field) ->setHref($this->filelist->createModuleUri([ 'sortField' => $field, 'currentPage' => 0, 'sortDirection' => (int)($this->filelist->sortDirection === SortDirection::DESCENDING), ])) ->setLabel($label); } $sortingModeButtons[] = $this->componentFactory->createDropDownDivider(); } $defaultSortingDirectionParams = ['sortField' => $this->filelist->sortField, 'currentPage' => 0]; $sortingModeButtons[] = $this->componentFactory->createDropDownRadio() ->setActive($this->filelist->sortDirection === SortDirection::ASCENDING) ->setHref($this->filelist->createModuleUri(array_merge($defaultSortingDirectionParams, ['sortDirection' => SortDirection::ASCENDING->value]))) ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting.asc')); $sortingModeButtons[] = $this->componentFactory->createDropDownRadio() ->setActive($this->filelist->sortDirection === SortDirection::DESCENDING) ->setHref($this->filelist->createModuleUri(array_merge($defaultSortingDirectionParams, ['sortDirection' => SortDirection::DESCENDING->value]))) ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting.desc')); foreach ($sortingModeButtons as $sortingModeButton) { $sortingButton->addItem($sortingModeButton); } $this->view->addButtonToButtonBar($sortingButton, ButtonBar::BUTTON_POSITION_RIGHT, 2); $viewModeButton = $this->componentFactory->createDropDownButton() ->setLabel($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view')) ->setIcon($this->iconFactory->getIcon('actions-cog')) ->setShowLabelText(true); foreach ($viewModeItems as $viewModeItem) { $viewModeButton->addItem($viewModeItem); } $this->view->addButtonToButtonBar($viewModeButton, ButtonBar::BUTTON_POSITION_RIGHT, 3); // Level up try { $currentStorage = $this->folderObject->getStorage(); $parentFolder = $this->folderObject->getParentFolder(); if ($currentStorage->isWithinFileMountBoundaries($parentFolder) && $parentFolder->getIdentifier() !== $this->folderObject->getIdentifier() ) { $levelUpButton = $this->componentFactory->createLinkButton() ->setDataAttributes([ 'tree-update-request' => htmlspecialchars('folder' . GeneralUtility::md5int($parentFolder->getCombinedIdentifier())), ]) ->setHref( (string)$this->uriBuilder->buildUriFromRoute( 'media_management', ['id' => $parentFolder->getCombinedIdentifier()] ) ) ->setShowLabelText(true) ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.upOneLevel')) ->setIcon($this->iconFactory->getIcon('actions-view-go-up', IconSize::SMALL)); $this->view->addButtonToButtonBar($levelUpButton); } } catch (\Exception $e) { } // Shortcut $this->view->getDocHeaderComponent()->setShortcutContext( 'media_management', sprintf( '%s: %s', $lang->translate('title', 'filelist.module'), $this->folderObject->getName() ?: $this->folderObject->getIdentifier() ), array_filter([ 'id' => $this->id, 'searchTerm' => $this->searchTerm, ]) ); // New file button if ($this->folderObject && $this->folderObject->checkActionPermission('write') && $this->folderObject->getStorage()->checkUserActionPermission('add', 'File') ) { $newButton = $this->componentFactory->createLinkButton() ->setClasses('t3js-element-browser') ->setHref((string)$this->uriBuilder->buildUriFromRoute('wizard_element_browser')) ->setDataAttributes([ 'identifier' => $this->folderObject->getCombinedIdentifier(), 'mode' => CreateFileBrowser::IDENTIFIER, ]) ->setShowLabelText(true) ->setTitle($lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:actions.new_file')) ->setIcon($this->iconFactory->getIcon('actions-file-add', IconSize::SMALL)); $this->view->addButtonToButtonBar($newButton, ButtonBar::BUTTON_POSITION_LEFT, 2); } // New folder button if ($this->folderObject && $this->folderObject->checkActionPermission('write') && $this->folderObject->checkActionPermission('add')) { $newButton = $this->componentFactory->createLinkButton() ->setClasses('t3js-element-browser') ->setHref((string)$this->uriBuilder->buildUriFromRoute('wizard_element_browser')) ->setDataAttributes([ 'identifier' => $this->folderObject->getCombinedIdentifier(), 'mode' => CreateFolderBrowser::IDENTIFIER, ]) ->setShowLabelText(true) ->setTitle($lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:actions.new_folder')) ->setIcon($this->iconFactory->getIcon('actions-folder-add', IconSize::SMALL)); $this->view->addButtonToButtonBar($newButton, ButtonBar::BUTTON_POSITION_LEFT, 3); } // Add paste button if ($this->folderObject->checkActionPermission('write')) { $elFromTable = $this->filelist->clipObj->elFromTable('_FILE'); if ($elFromTable !== []) { $addPasteButton = true; foreach ($elFromTable as $element) { $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element); if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder( $clipBoardElement, $this->folderObject ) ) { $addPasteButton = false; } } if ($addPasteButton) { $confirmText = $this->filelist->clipObj ->confirmMsgText('_FILE', $this->folderObject->getReadablePath(), 'into'); $pastButtonTitle = $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_paste'); $pasteButton = $this->componentFactory->createLinkButton() ->setHref($this->filelist->clipObj ->pasteUrl('_FILE', $this->folderObject->getCombinedIdentifier())) ->setClasses('t3js-modal-trigger') ->setDataAttributes([ 'severity' => 'warning', 'content' => $confirmText, 'title' => $pastButtonTitle, ]) ->setShowLabelText(true) ->setTitle($pastButtonTitle) ->setIcon($this->iconFactory->getIcon('actions-document-paste-into', IconSize::SMALL)); $this->view->addButtonToButtonBar($pasteButton, ButtonBar::BUTTON_POSITION_LEFT, 10); } } } } /** * Get main headline based on active folder or storage for backend module * Folder names are resolved to their special names like done in the tree view. */ protected function getModuleHeadline(): string { $name = $this->folderObject->getName(); if ($name === '') { // Show storage name on storage root if ($this->folderObject->getIdentifier() === '/') { $name = $this->folderObject->getStorage()->getName(); } } else { $name = key(ListUtility::resolveSpecialFolderNames( [$name => $this->folderObject] )); } return (string)$name; } /** * Generate a response by either the given $html or by rendering the module content. */ protected function htmlResponse(string $html): ResponseInterface { $response = $this->responseFactory ->createResponse() ->withHeader('Content-Type', 'text/html; charset=utf-8'); $response->getBody()->write($html); return $response; } /** * Adds a flash message to the default flash message queue */ protected function addFlashMessage(string $message, string $title = '', ContextualFeedbackSeverity $severity = ContextualFeedbackSeverity::INFO): void { $flashMessage = new FlashMessage($message, $title, $severity, true); $defaultFlashMessageQueue = $this->flashMessageService->getMessageQueueByIdentifier(); $defaultFlashMessageQueue->enqueue($flashMessage); } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } }