TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/vendor/
|
||||||
@@ -0,0 +1,522 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\ContextMenu\ItemProviders;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Backend\ContextMenu\ItemProviders\AbstractProvider;
|
||||||
|
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||||
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||||
|
use TYPO3\CMS\Core\Authentication\JsConfirmation;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
|
||||||
|
use TYPO3\CMS\Core\Resource\File;
|
||||||
|
use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Filelist\ElementBrowser\CreateFileBrowser;
|
||||||
|
use TYPO3\CMS\Filelist\ElementBrowser\CreateFolderBrowser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides click menu items for files and folders
|
||||||
|
*/
|
||||||
|
class FileProvider extends AbstractProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var File|Folder|null
|
||||||
|
*/
|
||||||
|
protected $record;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $itemsConfiguration = [
|
||||||
|
'edit' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editcontent',
|
||||||
|
'iconIdentifier' => 'actions-page-open',
|
||||||
|
'callbackAction' => 'editFile',
|
||||||
|
],
|
||||||
|
'editMetadata' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editMetadata',
|
||||||
|
'iconIdentifier' => 'actions-open',
|
||||||
|
'callbackAction' => 'editMetadata',
|
||||||
|
],
|
||||||
|
'replaceFile' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.replace',
|
||||||
|
'iconIdentifier' => 'actions-edit-replace',
|
||||||
|
'callbackAction' => 'replaceFile',
|
||||||
|
],
|
||||||
|
'rename' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.rename',
|
||||||
|
'iconIdentifier' => 'actions-edit-rename',
|
||||||
|
'callbackAction' => 'renameFile',
|
||||||
|
],
|
||||||
|
'new' => [
|
||||||
|
'label' => 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:actions.new_folder',
|
||||||
|
'iconIdentifier' => 'actions-folder-add',
|
||||||
|
'callbackAction' => 'createFolder',
|
||||||
|
],
|
||||||
|
'newFile' => [
|
||||||
|
'label' => 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:actions.new_file',
|
||||||
|
'iconIdentifier' => 'actions-file-add',
|
||||||
|
'callbackAction' => 'createFile',
|
||||||
|
],
|
||||||
|
'downloadFile' => [
|
||||||
|
'label' => 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:download',
|
||||||
|
'iconIdentifier' => 'actions-download',
|
||||||
|
'callbackAction' => 'downloadFile',
|
||||||
|
],
|
||||||
|
'downloadFolder' => [
|
||||||
|
'label' => 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:download',
|
||||||
|
'iconIdentifier' => 'actions-download',
|
||||||
|
'callbackAction' => 'downloadFolder',
|
||||||
|
],
|
||||||
|
'newFileMount' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.newFilemount',
|
||||||
|
'iconIdentifier' => 'mimetypes-x-sys_filemounts',
|
||||||
|
'callbackAction' => 'createFilemount',
|
||||||
|
],
|
||||||
|
'info' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info',
|
||||||
|
'iconIdentifier' => 'actions-document-info',
|
||||||
|
'callbackAction' => 'openInfoPopUp',
|
||||||
|
],
|
||||||
|
'updateOnlineMedia' => [
|
||||||
|
'label' => 'LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:reloadMetadata',
|
||||||
|
'iconIdentifier' => 'actions-refresh',
|
||||||
|
'callbackAction' => 'updateOnlineMedia',
|
||||||
|
],
|
||||||
|
'divider' => [
|
||||||
|
'type' => 'divider',
|
||||||
|
],
|
||||||
|
'copy' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy',
|
||||||
|
'iconIdentifier' => 'actions-edit-copy',
|
||||||
|
'callbackAction' => 'copyFile',
|
||||||
|
],
|
||||||
|
'copyRelease' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy',
|
||||||
|
'iconIdentifier' => 'actions-edit-copy-release',
|
||||||
|
'callbackAction' => 'copyReleaseFile',
|
||||||
|
],
|
||||||
|
'cut' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cut',
|
||||||
|
'iconIdentifier' => 'actions-edit-cut',
|
||||||
|
'callbackAction' => 'cutFile',
|
||||||
|
],
|
||||||
|
'cutRelease' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cutrelease',
|
||||||
|
'iconIdentifier' => 'actions-edit-cut-release',
|
||||||
|
'callbackAction' => 'cutReleaseFile',
|
||||||
|
],
|
||||||
|
'pasteInto' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.pasteinto',
|
||||||
|
'iconIdentifier' => 'actions-document-paste-into',
|
||||||
|
'callbackAction' => 'pasteFileInto',
|
||||||
|
],
|
||||||
|
'divider2' => [
|
||||||
|
'type' => 'divider',
|
||||||
|
],
|
||||||
|
'delete' => [
|
||||||
|
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete',
|
||||||
|
'iconIdentifier' => 'actions-edit-delete',
|
||||||
|
'callbackAction' => 'deleteFile',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ResourceFactory $resourceFactory,
|
||||||
|
private readonly UriBuilder $uriBuilder,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canHandle(): bool
|
||||||
|
{
|
||||||
|
return $this->table === 'sys_file';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize file object
|
||||||
|
*/
|
||||||
|
protected function initialize()
|
||||||
|
{
|
||||||
|
parent::initialize();
|
||||||
|
try {
|
||||||
|
$this->record = $this->resourceFactory->retrieveFileOrFolderObject($this->identifier);
|
||||||
|
} catch (ResourceDoesNotExistException $e) {
|
||||||
|
$this->record = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether certain item can be rendered (e.g. check for disabled items or permissions)
|
||||||
|
*/
|
||||||
|
protected function canRender(string $itemName, string $type): bool
|
||||||
|
{
|
||||||
|
if (in_array($type, ['divider', 'submenu'], true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (in_array($itemName, $this->disabledItems, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$canRender = false;
|
||||||
|
switch ($itemName) {
|
||||||
|
//just for files
|
||||||
|
case 'edit':
|
||||||
|
$canRender = $this->canBeEdited();
|
||||||
|
break;
|
||||||
|
case 'replaceFile':
|
||||||
|
$canRender = $this->canBeReplaced();
|
||||||
|
break;
|
||||||
|
case 'editMetadata':
|
||||||
|
$canRender = $this->canEditMetadata();
|
||||||
|
break;
|
||||||
|
case 'updateOnlineMedia':
|
||||||
|
$canRender = $this->isOnlineMedia() && $this->canEditMetadata();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// just for folders
|
||||||
|
case 'new':
|
||||||
|
case 'newFile':
|
||||||
|
$canRender = $this->canCreateNew();
|
||||||
|
break;
|
||||||
|
case 'newFileMount':
|
||||||
|
$canRender = $this->canCreateNewFilemount();
|
||||||
|
break;
|
||||||
|
case 'pasteInto':
|
||||||
|
$canRender = $this->canBePastedInto();
|
||||||
|
break;
|
||||||
|
|
||||||
|
//for both files and folders
|
||||||
|
case 'info':
|
||||||
|
$canRender = $this->canShowInfo();
|
||||||
|
break;
|
||||||
|
case 'rename':
|
||||||
|
$canRender = $this->canBeRenamed();
|
||||||
|
break;
|
||||||
|
case 'copy':
|
||||||
|
$canRender = $this->canBeCopied();
|
||||||
|
break;
|
||||||
|
case 'copyRelease':
|
||||||
|
$canRender = $this->isRecordInClipboard('copy');
|
||||||
|
break;
|
||||||
|
case 'cut':
|
||||||
|
$canRender = $this->canBeCut();
|
||||||
|
break;
|
||||||
|
case 'cutRelease':
|
||||||
|
$canRender = $this->isRecordInClipboard('cut');
|
||||||
|
break;
|
||||||
|
case 'downloadFile':
|
||||||
|
$canRender = $this->isFile() && $this->canBeDownloaded();
|
||||||
|
break;
|
||||||
|
case 'downloadFolder':
|
||||||
|
$canRender = $this->isFolder() && $this->canBeDownloaded();
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
$canRender = $this->canBeDeleted();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $canRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canBeReplaced(): bool
|
||||||
|
{
|
||||||
|
return $this->isFile()
|
||||||
|
&& $this->record->checkActionPermission('replace');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canBeEdited(): bool
|
||||||
|
{
|
||||||
|
return $this->isFile()
|
||||||
|
&& $this->record->checkActionPermission('write')
|
||||||
|
&& $this->record->isTextFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canEditMetadata(): bool
|
||||||
|
{
|
||||||
|
return $this->isFile()
|
||||||
|
&& $this->record->isIndexed()
|
||||||
|
&& $this->record->checkActionPermission('editMeta')
|
||||||
|
&& $this->record->getMetaData()->offsetExists('uid')
|
||||||
|
&& $this->backendUser->check('tables_modify', 'sys_file_metadata')
|
||||||
|
&& $this->backendUser->checkLanguageAccess(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canBeRenamed(): bool
|
||||||
|
{
|
||||||
|
return $this->record->checkActionPermission('rename');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canBeDeleted(): bool
|
||||||
|
{
|
||||||
|
return $this->record->checkActionPermission('delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canShowInfo(): bool
|
||||||
|
{
|
||||||
|
return $this->record !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canCreateNew(): bool
|
||||||
|
{
|
||||||
|
return $this->isFolder() && $this->record->checkActionPermission('write');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New file mounts can only be created for readable folders by admins
|
||||||
|
*/
|
||||||
|
protected function canCreateNewFilemount(): bool
|
||||||
|
{
|
||||||
|
return $this->isFolder() && $this->record->checkActionPermission('read') && $this->backendUser->isAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canBeCopied(): bool
|
||||||
|
{
|
||||||
|
return $this->record->checkActionPermission('read') && $this->record->checkActionPermission('copy') && !$this->isRecordInClipboard('copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canBeCut(): bool
|
||||||
|
{
|
||||||
|
return $this->record->checkActionPermission('move') && !$this->isRecordInClipboard('cut');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canBePastedInto(): bool
|
||||||
|
{
|
||||||
|
$elArr = $this->clipboard->elFromTable('_FILE');
|
||||||
|
if (empty($elArr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$selItem = reset($elArr);
|
||||||
|
$fileOrFolderInClipBoard = $this->resourceFactory->retrieveFileOrFolderObject($selItem);
|
||||||
|
|
||||||
|
return $this->isFolder()
|
||||||
|
&& $this->record->checkActionPermission('write')
|
||||||
|
&& (
|
||||||
|
!$fileOrFolderInClipBoard instanceof Folder
|
||||||
|
|| !$fileOrFolderInClipBoard->getStorage()->isWithinFolder($fileOrFolderInClipBoard, $this->record)
|
||||||
|
)
|
||||||
|
&& $this->isFoldersAreInTheSameRoot($fileOrFolderInClipBoard);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canBeDownloaded(): bool
|
||||||
|
{
|
||||||
|
if (!$this->record->checkActionPermission('read')) {
|
||||||
|
// Early return if no read access
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileDownloadConfiguration = (array)($this->backendUser->getTSConfig()['options.']['file_list.']['fileDownload.'] ?? []);
|
||||||
|
if (!($fileDownloadConfiguration['enabled'] ?? true)) {
|
||||||
|
// File download is disabled
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fileDownloadConfiguration === [] || $this->isFolder()) {
|
||||||
|
// In case no configuration exists, or we deal with a folder, download is allowed at this point
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize file extension filter
|
||||||
|
$filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
|
||||||
|
$filter->setAllowedFileExtensions(
|
||||||
|
GeneralUtility::trimExplode(',', (string)($fileDownloadConfiguration['allowedFileExtensions'] ?? ''), true)
|
||||||
|
);
|
||||||
|
$filter->setDisallowedFileExtensions(
|
||||||
|
GeneralUtility::trimExplode(',', (string)($fileDownloadConfiguration['disallowedFileExtensions'] ?? ''), true)
|
||||||
|
);
|
||||||
|
return $filter->isAllowed($this->record->getExtension());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isOnlineMedia(): bool
|
||||||
|
{
|
||||||
|
return $this->isFile()
|
||||||
|
&& GeneralUtility::makeInstance(OnlineMediaHelperRegistry::class)->hasOnlineMediaHelper($this->record->getExtension());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if folder and record are in the same file mount
|
||||||
|
* Cannot copy folders between file mounts
|
||||||
|
*
|
||||||
|
* @param File|Folder|null $fileOrFolderInClipBoard
|
||||||
|
*/
|
||||||
|
protected function isFoldersAreInTheSameRoot($fileOrFolderInClipBoard): bool
|
||||||
|
{
|
||||||
|
return (!$fileOrFolderInClipBoard instanceof Folder)
|
||||||
|
|| (
|
||||||
|
$this->record->getStorage()->getRootLevelFolder()->getCombinedIdentifier()
|
||||||
|
== $fileOrFolderInClipBoard->getStorage()->getRootLevelFolder()->getCombinedIdentifier()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a file record is in the "normal" pad of the clipboard
|
||||||
|
*
|
||||||
|
* @param string $mode "copy", "cut" or '' for any mode
|
||||||
|
*/
|
||||||
|
protected function isRecordInClipboard(string $mode = ''): bool
|
||||||
|
{
|
||||||
|
if ($mode !== '' && !$this->record->checkActionPermission($mode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$isSelected = '';
|
||||||
|
// Pseudo table name for use in the clipboard.
|
||||||
|
$table = '_FILE';
|
||||||
|
$uid = md5($this->record->getCombinedIdentifier());
|
||||||
|
if ($this->clipboard->current === 'normal') {
|
||||||
|
$isSelected = $this->clipboard->isSelected($table, $uid);
|
||||||
|
}
|
||||||
|
return $mode === '' ? !empty($isSelected) : $isSelected === $mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isStorageRoot(): bool
|
||||||
|
{
|
||||||
|
return $this->record->getIdentifier() === $this->record->getStorage()->getRootLevelFolder()->getIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isFile(): bool
|
||||||
|
{
|
||||||
|
return $this->record instanceof File;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isFolder(): bool
|
||||||
|
{
|
||||||
|
return $this->record instanceof Folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAdditionalAttributes(string $itemName): array
|
||||||
|
{
|
||||||
|
$attributes = [
|
||||||
|
'data-callback-module' => '@typo3/filelist/context-menu-actions',
|
||||||
|
];
|
||||||
|
if ($itemName === 'delete' && $this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
|
||||||
|
$title = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete');
|
||||||
|
if ($this->isFolder()) {
|
||||||
|
$attributes += [
|
||||||
|
'data-button-close-text' => $this->languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:buttons.confirm.delete_folder.no'),
|
||||||
|
'data-button-ok-text' => $this->languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:buttons.confirm.delete_folder.yes'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($this->isFile()) {
|
||||||
|
$attributes += [
|
||||||
|
'data-button-close-text' => $this->languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:buttons.confirm.delete_file.no'),
|
||||||
|
'data-button-ok-text' => $this->languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:buttons.confirm.delete_file.yes'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$recordInfo = BackendUtility::cropToTitleLength($this->record->getName());
|
||||||
|
if ($this->isFolder()) {
|
||||||
|
if ($this->backendUser->shallDisplayDebugInformation()) {
|
||||||
|
$recordInfo .= ' [' . $this->record->getIdentifier() . ']';
|
||||||
|
}
|
||||||
|
$confirmMessage = sprintf(
|
||||||
|
$this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.delete'),
|
||||||
|
trim($recordInfo)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if ($this->backendUser->shallDisplayDebugInformation()) {
|
||||||
|
$recordInfo .= ' [sys_file:' . $this->record->getUid() . ']';
|
||||||
|
}
|
||||||
|
$confirmMessage = sprintf(
|
||||||
|
$this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.delete'),
|
||||||
|
trim($recordInfo)
|
||||||
|
) . BackendUtility::referenceCount(
|
||||||
|
'sys_file',
|
||||||
|
(int)$this->record->getUid(),
|
||||||
|
LF . $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$attributes += [
|
||||||
|
'data-title' => $title,
|
||||||
|
'data-message' => $confirmMessage,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($itemName === 'new' && $this->isFolder()) {
|
||||||
|
$attributes += [
|
||||||
|
'data-identifier' => $this->record->getCombinedIdentifier(),
|
||||||
|
'data-mode' => CreateFolderBrowser::IDENTIFIER,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($itemName === 'newFile' && $this->isFolder()) {
|
||||||
|
$attributes += [
|
||||||
|
'data-identifier' => $this->record->getCombinedIdentifier(),
|
||||||
|
'data-mode' => CreateFileBrowser::IDENTIFIER,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($itemName === 'pasteInto' && $this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
|
||||||
|
$elArr = $this->clipboard->elFromTable('_FILE');
|
||||||
|
$selItem = reset($elArr);
|
||||||
|
$fileOrFolderInClipBoard = $this->resourceFactory->retrieveFileOrFolderObject($selItem);
|
||||||
|
|
||||||
|
$title = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste');
|
||||||
|
|
||||||
|
$confirmMessage = sprintf(
|
||||||
|
$this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.'
|
||||||
|
. ($this->clipboard->currentMode() === 'copy' ? 'copy' : 'move') . '_into'),
|
||||||
|
$fileOrFolderInClipBoard->getName(),
|
||||||
|
$this->record->getName()
|
||||||
|
);
|
||||||
|
$closeText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.cancel');
|
||||||
|
$okLabel = $this->clipboard->currentMode() === 'copy' ? 'copy' : 'pasteinto';
|
||||||
|
$okText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . $okLabel);
|
||||||
|
$attributes += [
|
||||||
|
'data-title' => $title,
|
||||||
|
'data-message' => $confirmMessage,
|
||||||
|
'data-button-close-text' => $closeText,
|
||||||
|
'data-button-ok-text' => $okText,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($itemName === 'downloadFile') {
|
||||||
|
$attributes += [
|
||||||
|
'data-url' => (string)$this->record->getPublicUrl(),
|
||||||
|
'data-name' => $this->record->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resource Settings
|
||||||
|
$attributes['data-filecontext-type'] = $this->record instanceof File ? 'file' : 'folder';
|
||||||
|
$attributes['data-filecontext-identifier'] = $this->getIdentifier();
|
||||||
|
$attributes['data-filecontext-name'] = $this->record->getName();
|
||||||
|
$attributes['data-filecontext-uid'] = $this->record instanceof File ? $this->record->getUid() : '';
|
||||||
|
$attributes['data-filecontext-meta-uid'] = $this->record instanceof File ? $this->record->getMetaData()->offsetGet('uid') : '';
|
||||||
|
|
||||||
|
// Add action url for file operations
|
||||||
|
switch ($itemName) {
|
||||||
|
case 'downloadFolder':
|
||||||
|
$attributes['data-action-url'] = (string)$this->uriBuilder->buildUriFromRoute('file_download');
|
||||||
|
break;
|
||||||
|
case 'edit':
|
||||||
|
$attributes['data-action-url'] = (string)$this->uriBuilder->buildUriFromRoute('file_edit');
|
||||||
|
break;
|
||||||
|
case 'new':
|
||||||
|
$attributes['data-action-url'] = (string)$this->uriBuilder->buildUriFromRoute('wizard_element_browser');
|
||||||
|
break;
|
||||||
|
case 'newFile':
|
||||||
|
$attributes['data-action-url'] = (string)$this->uriBuilder->buildUriFromRoute('wizard_element_browser');
|
||||||
|
break;
|
||||||
|
case 'updateOnlineMedia':
|
||||||
|
$attributes['data-action-url'] = (string)$this->uriBuilder->buildUriFromRoute('file_update_online_media');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getIdentifier(): string
|
||||||
|
{
|
||||||
|
return $this->record->getCombinedIdentifier();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Controller\File;
|
||||||
|
|
||||||
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Message\StreamFactoryInterface;
|
||||||
|
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||||
|
use TYPO3\CMS\Backend\Form\FormResultFactory;
|
||||||
|
use TYPO3\CMS\Backend\Form\FormResultHandler;
|
||||||
|
use TYPO3\CMS\Backend\Form\NodeFactory;
|
||||||
|
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||||
|
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||||
|
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||||
|
use TYPO3\CMS\Core\Http\ResponseFactory;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||||
|
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\InvalidFileException;
|
||||||
|
use TYPO3\CMS\Core\Resource\FileInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
|
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Filelist\Event\ModifyEditFileFormDataEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit text files via FormEngine. Reachable via FileList module "Edit content".
|
||||||
|
*
|
||||||
|
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||||
|
*/
|
||||||
|
#[AsController]
|
||||||
|
class EditFileController
|
||||||
|
{
|
||||||
|
protected array $dataColumnTca = [
|
||||||
|
'label' => '',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'text',
|
||||||
|
'cols' => 48,
|
||||||
|
'wrap' => 'off',
|
||||||
|
'enableTabulator' => true,
|
||||||
|
'fixedFont' => true,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected array $formEngineData = [
|
||||||
|
'databaseRow' => [
|
||||||
|
'uid' => 0,
|
||||||
|
'data' => '',
|
||||||
|
'target' => 0,
|
||||||
|
'redirect' => '',
|
||||||
|
],
|
||||||
|
'tableName' => 'editfile',
|
||||||
|
'processedTca' => [
|
||||||
|
'columns' => [
|
||||||
|
'data' => [],
|
||||||
|
'target' => [
|
||||||
|
'config' => [
|
||||||
|
'type' => 'input',
|
||||||
|
'renderType' => 'hidden',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'redirect' => [
|
||||||
|
'config' => [
|
||||||
|
'type' => 'input',
|
||||||
|
'renderType' => 'hidden',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'types' => [
|
||||||
|
1 => [
|
||||||
|
'showitem' => 'data,target,redirect',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'recordTypeValue' => 1,
|
||||||
|
'inlineStructure' => [],
|
||||||
|
'renderType' => 'fullRecordContainer',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected readonly IconFactory $iconFactory,
|
||||||
|
protected readonly UriBuilder $uriBuilder,
|
||||||
|
protected readonly ResourceFactory $resourceFactory,
|
||||||
|
protected readonly ModuleTemplateFactory $moduleTemplateFactory,
|
||||||
|
protected readonly ResponseFactory $responseFactory,
|
||||||
|
protected readonly StreamFactoryInterface $streamFactory,
|
||||||
|
protected readonly EventDispatcherInterface $eventDispatcher,
|
||||||
|
protected readonly NodeFactory $nodeFactory,
|
||||||
|
protected readonly ComponentFactory $componentFactory,
|
||||||
|
protected readonly FormResultFactory $formResultFactory,
|
||||||
|
protected readonly FormResultHandler $formResultHandler,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the edit file content form using FormEngine.
|
||||||
|
*/
|
||||||
|
public function mainAction(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$languageService = $this->getLanguageService();
|
||||||
|
$view = $this->moduleTemplateFactory->create($request);
|
||||||
|
$parsedBody = $request->getParsedBody();
|
||||||
|
$queryParams = $request->getQueryParams();
|
||||||
|
|
||||||
|
$combinedIdentifier = $parsedBody['target'] ?? $queryParams['target'] ?? '';
|
||||||
|
$file = $this->resourceFactory->retrieveFileOrFolderObject($combinedIdentifier);
|
||||||
|
|
||||||
|
if (!$file instanceof FileInterface) {
|
||||||
|
throw new InvalidFileException('Referenced target "' . $combinedIdentifier . '" could not be resolved to a valid file', 1294586841);
|
||||||
|
}
|
||||||
|
if ($file->getStorage()->isFallbackStorage()) {
|
||||||
|
throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889832);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Folder $parentFolder */
|
||||||
|
$parentFolder = $file->getParentFolder();
|
||||||
|
|
||||||
|
$returnUrl = GeneralUtility::sanitizeLocalUrl(
|
||||||
|
$parsedBody['returnUrl']
|
||||||
|
?? $queryParams['returnUrl']
|
||||||
|
?? (string)$this->uriBuilder->buildUriFromRoute('media_management', [
|
||||||
|
'id' => $parentFolder->getCombinedIdentifier(),
|
||||||
|
]),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$file->isTextFile()) {
|
||||||
|
$extList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'];
|
||||||
|
$view->addFlashMessage('Files with that extension are not editable. Allowed extensions are: ' . $extList, '', ContextualFeedbackSeverity::ERROR, true);
|
||||||
|
return $this->responseFactory->createResponse(400)->withHeader('location', $returnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addDocHeaderButtons($view, $returnUrl);
|
||||||
|
|
||||||
|
$dataColumnDefinition = $this->dataColumnTca;
|
||||||
|
$dataColumnDefinition['label'] = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:file')) . ' ' . htmlspecialchars($combinedIdentifier);
|
||||||
|
|
||||||
|
$formData = $this->formEngineData;
|
||||||
|
$formData['databaseRow']['data'] = $file->getContents();
|
||||||
|
$formData['databaseRow']['target'] = $file->getUid();
|
||||||
|
$formData['databaseRow']['redirect'] = (string)$this->uriBuilder->buildUriFromRoute('file_edit', ['target' => $combinedIdentifier, 'returnUrl' => $returnUrl]);
|
||||||
|
$formData['processedTca']['columns']['data'] = $dataColumnDefinition;
|
||||||
|
|
||||||
|
$formData = $this->eventDispatcher->dispatch(
|
||||||
|
new ModifyEditFileFormDataEvent($formData, $file, $request)
|
||||||
|
)->getFormData();
|
||||||
|
|
||||||
|
$resultArray = $this->nodeFactory->create($formData)->render();
|
||||||
|
$formResult = $this->formResultFactory->create($resultArray);
|
||||||
|
|
||||||
|
$this->formResultHandler->addAssets($formResult);
|
||||||
|
// Rendering of the output via fluid and PageRenderer
|
||||||
|
$view->assignMultiple([
|
||||||
|
'moduleUrlTceFile' => (string)$this->uriBuilder->buildUriFromRoute('tce_file'),
|
||||||
|
'fileName' => $file->getName(),
|
||||||
|
'form' => $formResult->html,
|
||||||
|
]);
|
||||||
|
$content = $view->render('File/EditFile');
|
||||||
|
|
||||||
|
return $this->responseFactory->createResponse()
|
||||||
|
->withHeader('Content-Type', 'text/html; charset=utf-8')
|
||||||
|
->withBody($this->streamFactory->createStream($content));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addDocHeaderButtons(ModuleTemplate $view, string $returnUrl): void
|
||||||
|
{
|
||||||
|
$languageService = $this->getLanguageService();
|
||||||
|
$view->addButtonToButtonBar(
|
||||||
|
$this->componentFactory->createSaveButton('EditFileController')
|
||||||
|
->setTitle($languageService->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_edit.php.submit')),
|
||||||
|
ButtonBar::BUTTON_POSITION_LEFT,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
$view->addButtonToButtonBar($this->componentFactory->createCloseButton($returnUrl), ButtonBar::BUTTON_POSITION_LEFT, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLanguageService(): LanguageService
|
||||||
|
{
|
||||||
|
return $GLOBALS['LANG'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Controller;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseFactoryInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Message\StreamFactoryInterface;
|
||||||
|
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||||
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||||
|
use TYPO3\CMS\Core\Context\Context;
|
||||||
|
use TYPO3\CMS\Core\Resource\FileInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller class to create a zip file for given items from a file or folder.
|
||||||
|
*
|
||||||
|
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||||
|
*/
|
||||||
|
#[AsController]
|
||||||
|
class FileDownloadController
|
||||||
|
{
|
||||||
|
protected ResourceFactory $resourceFactory;
|
||||||
|
protected ResponseFactoryInterface $responseFactory;
|
||||||
|
protected StreamFactoryInterface $streamFactory;
|
||||||
|
protected Context $context;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ResourceFactory $resourceFactory,
|
||||||
|
ResponseFactoryInterface $responseFactory,
|
||||||
|
StreamFactoryInterface $streamFactory,
|
||||||
|
Context $context
|
||||||
|
) {
|
||||||
|
$this->resourceFactory = $resourceFactory;
|
||||||
|
$this->responseFactory = $responseFactory;
|
||||||
|
$this->streamFactory = $streamFactory;
|
||||||
|
$this->context = $context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$items = (array)($request->getParsedBody()['items'] ?? []);
|
||||||
|
if ($items === []) {
|
||||||
|
// Return in case no items are given
|
||||||
|
return $this->responseFactory->createResponse(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileExtensionFilter = null;
|
||||||
|
$fileDownloadConfiguration = (array)($this->getBackendUser()->getTSConfig()['options.']['file_list.']['fileDownload.'] ?? []);
|
||||||
|
if ($fileDownloadConfiguration !== []) {
|
||||||
|
if (!($fileDownloadConfiguration['enabled'] ?? true)) {
|
||||||
|
// Return if file download is disabled
|
||||||
|
return $this->responseFactory->createResponse(403);
|
||||||
|
}
|
||||||
|
// Initialize file extension filter, if configured
|
||||||
|
$fileExtensionFilter = GeneralUtility::makeInstance(FileExtensionFilter::class);
|
||||||
|
$fileExtensionFilter->setAllowedFileExtensions(
|
||||||
|
GeneralUtility::trimExplode(',', (string)($fileDownloadConfiguration['allowedFileExtensions'] ?? ''), true)
|
||||||
|
);
|
||||||
|
$fileExtensionFilter->setDisallowedFileExtensions(
|
||||||
|
GeneralUtility::trimExplode(',', (string)($fileDownloadConfiguration['disallowedFileExtensions'] ?? ''), true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zipStream = tmpfile();
|
||||||
|
if (!is_resource($zipStream)) {
|
||||||
|
throw new \RuntimeException('Could not open temporary resource for creating archive', 1630346631);
|
||||||
|
}
|
||||||
|
$zipFileName = stream_get_meta_data($zipStream)['uri'];
|
||||||
|
$zipFile = new \ZipArchive();
|
||||||
|
$zipFile->open($zipFileName, \ZipArchive::OVERWRITE);
|
||||||
|
$filesAdded = 0;
|
||||||
|
foreach ($this->collectFiles($items) as $fileName => $fileObject) {
|
||||||
|
// Add files with read permission and allowed file extension
|
||||||
|
if (!$fileObject->getStorage()->checkFileActionPermission('read', $fileObject)
|
||||||
|
|| ($fileExtensionFilter !== null && !$fileExtensionFilter->isAllowed($fileObject->getExtension()))
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$filesAdded++;
|
||||||
|
$zipFile->addFile($fileObject->getForLocalProcessing(false), $fileName);
|
||||||
|
}
|
||||||
|
$zipFile->close();
|
||||||
|
$response = $this->createResponse($zipFileName, $filesAdded);
|
||||||
|
if ($filesAdded > 0) {
|
||||||
|
unlink($zipFileName);
|
||||||
|
}
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createResponse(string $temporaryFileName, int $filesAdded): ResponseInterface
|
||||||
|
{
|
||||||
|
if ($filesAdded === 0) {
|
||||||
|
return $this->responseFactory->createResponse()
|
||||||
|
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
||||||
|
->withBody($this->streamFactory->createStream(json_encode(['success' => false, 'status' => 'noFiles'])));
|
||||||
|
}
|
||||||
|
|
||||||
|
$downloadFileName = 'typo3_download_' . $this->context->getAspect('date')->getDateTime()->format('Y-m-d-His') . '.zip';
|
||||||
|
return $this->responseFactory->createResponse()
|
||||||
|
->withHeader('Content-Type', 'application/zip')
|
||||||
|
->withHeader('Content-Disposition', 'attachment; filename=' . $downloadFileName)
|
||||||
|
->withHeader('Content-Transfer-Encoding', 'binary')
|
||||||
|
->withHeader('Pragma', 'no-cache')
|
||||||
|
->withHeader('Cache-Control', 'no-cache, no-store')
|
||||||
|
->withBody($this->streamFactory->createStreamFromFile($temporaryFileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FileInterface[]
|
||||||
|
*/
|
||||||
|
protected function collectFiles(array $items): array
|
||||||
|
{
|
||||||
|
$files = [];
|
||||||
|
foreach ($items as $itemIdentifier) {
|
||||||
|
$fileOrFolderObject = $this->resourceFactory->retrieveFileOrFolderObject($itemIdentifier);
|
||||||
|
if ($fileOrFolderObject === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Files from fallback storage must be skipped in general
|
||||||
|
if ($fileOrFolderObject->getStorage()->isFallbackStorage()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$baseIdentifier = dirname($fileOrFolderObject->getIdentifier());
|
||||||
|
if ($fileOrFolderObject instanceof Folder) {
|
||||||
|
// handle file / folder structure
|
||||||
|
foreach ($this->getFilesAndFoldersRecursive($fileOrFolderObject) as $fileObject) {
|
||||||
|
$commonPrefix = (string)PathUtility::getCommonPrefix([$baseIdentifier, $fileObject->getIdentifier()]);
|
||||||
|
$files[substr($fileObject->getIdentifier(), strlen($commonPrefix))] = $fileObject;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$files[$fileOrFolderObject->getName()] = $fileOrFolderObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getFilesAndFoldersRecursive(Folder $folder): iterable
|
||||||
|
{
|
||||||
|
foreach ($folder->getSubfolders() as $subFolder) {
|
||||||
|
yield from $this->getFilesAndFoldersRecursive($subFolder);
|
||||||
|
}
|
||||||
|
foreach ($folder->getFiles() as $file) {
|
||||||
|
yield $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getBackendUser(): BackendUserAuthentication
|
||||||
|
{
|
||||||
|
return $GLOBALS['BE_USER'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,762 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Controller;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseFactoryInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||||
|
use TYPO3\CMS\Backend\Breadcrumb\BreadcrumbContext;
|
||||||
|
use TYPO3\CMS\Backend\Clipboard\Clipboard;
|
||||||
|
use TYPO3\CMS\Backend\Module\ModuleData;
|
||||||
|
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||||
|
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||||
|
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||||
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||||
|
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||||
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||||
|
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||||
|
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||||
|
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||||
|
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
|
||||||
|
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||||
|
use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceStorage;
|
||||||
|
use TYPO3\CMS\Core\Resource\Search\FileSearchDemand;
|
||||||
|
use TYPO3\CMS\Core\Resource\StorageRepository;
|
||||||
|
use TYPO3\CMS\Core\Resource\Utility\ListUtility;
|
||||||
|
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||||
|
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||||
|
use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||||
|
use TYPO3\CMS\Filelist\ElementBrowser\CreateFileBrowser;
|
||||||
|
use TYPO3\CMS\Filelist\ElementBrowser\CreateFolderBrowser;
|
||||||
|
use TYPO3\CMS\Filelist\FileList;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFileTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFolderTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\SortDirection;
|
||||||
|
use TYPO3\CMS\Filelist\Type\ViewMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script Class for creating the list of files in the File > 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'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Controller;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseFactoryInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Message\StreamFactoryInterface;
|
||||||
|
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||||
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
|
||||||
|
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
|
||||||
|
use TYPO3\CMS\Core\Resource\OnlineMedia\Service\PreviewService;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller class to update an online media resource
|
||||||
|
*
|
||||||
|
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||||
|
*/
|
||||||
|
#[AsController]
|
||||||
|
readonly class FileUpdateOnlineMediaController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected ResourceFactory $resourceFactory,
|
||||||
|
protected OnlineMediaHelperRegistry $onlineMediaHelperRegistry,
|
||||||
|
protected PreviewService $previewService,
|
||||||
|
protected ResponseFactoryInterface $responseFactory,
|
||||||
|
protected StreamFactoryInterface $streamFactory
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$resource = $request->getParsedBody()['resource'] ?? [];
|
||||||
|
|
||||||
|
if (($resource['type'] ?? '') !== 'file' || !isset($resource['uid'])) {
|
||||||
|
return $this->createResponse(['success' => false], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileObject = null;
|
||||||
|
try {
|
||||||
|
$fileObject = $this->resourceFactory->getFileObject($resource['uid']);
|
||||||
|
} catch (FileDoesNotExistException $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fileObject === null
|
||||||
|
|| !($onlineMediaHelper = $this->onlineMediaHelperRegistry->getOnlineMediaHelper($fileObject))
|
||||||
|
|| !$fileObject->checkActionPermission('editMeta')
|
||||||
|
|| !$fileObject->getMetaData()->offsetExists('uid')
|
||||||
|
|| !$this->getBackendUser()->check('tables_modify', 'sys_file_metadata')
|
||||||
|
) {
|
||||||
|
return $this->createResponse(['success' => false], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->previewService->updatePreviewImage($fileObject);
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
return $this->createResponse(['success' => false], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update remaining meta data from online media helper
|
||||||
|
$fileObject->getMetaData()->add($onlineMediaHelper->getMetaData($fileObject))->save();
|
||||||
|
|
||||||
|
return $this->createResponse(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createResponse(array $data = [], int $status = 200): ResponseInterface
|
||||||
|
{
|
||||||
|
return $this->responseFactory->createResponse($status)
|
||||||
|
->withHeader('Content-Type', 'application/json; charset=utf-8')
|
||||||
|
->withBody($this->streamFactory->createStream((string)json_encode($data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getBackendUser(): BackendUserAuthentication
|
||||||
|
{
|
||||||
|
return $GLOBALS['BE_USER'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Dto;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\Resource\File;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ResourceCollection implements \Countable, \Iterator, \ArrayAccess
|
||||||
|
{
|
||||||
|
private int $position = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ResourceInterface[]
|
||||||
|
*/
|
||||||
|
protected array $resources = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ResourceInterface[] $resources
|
||||||
|
*/
|
||||||
|
public function __construct(array $resources = [])
|
||||||
|
{
|
||||||
|
$this->setResources($resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addResource(ResourceInterface $resource): self
|
||||||
|
{
|
||||||
|
$this->resources[] = $resource;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addResources(array $resources): self
|
||||||
|
{
|
||||||
|
foreach ($resources as $resource) {
|
||||||
|
$this->addResource($resource);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setResources(array $resources): self
|
||||||
|
{
|
||||||
|
$this->resources = [];
|
||||||
|
$this->addResources($resources);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResourceInterface[]
|
||||||
|
*/
|
||||||
|
public function getResources(): array
|
||||||
|
{
|
||||||
|
return $this->resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Folder[]
|
||||||
|
*/
|
||||||
|
public function getFolders(): array
|
||||||
|
{
|
||||||
|
return array_filter($this->resources, static function (ResourceInterface $resource): bool {
|
||||||
|
return $resource instanceof Folder;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return File[]
|
||||||
|
*/
|
||||||
|
public function getFiles(): array
|
||||||
|
{
|
||||||
|
return array_filter($this->resources, static function (ResourceInterface $resource): bool {
|
||||||
|
return $resource instanceof File;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalBytes(): int
|
||||||
|
{
|
||||||
|
$totalBytes = 0;
|
||||||
|
foreach ($this->getFiles() as $file) {
|
||||||
|
$totalBytes += $file->getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $totalBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalFolderCount(): int
|
||||||
|
{
|
||||||
|
return count($this->getFolders());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalFileCount(): int
|
||||||
|
{
|
||||||
|
return count($this->getFiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalCount(): int
|
||||||
|
{
|
||||||
|
return count($this->resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array Access
|
||||||
|
*/
|
||||||
|
public function offsetSet($offset, $value): void
|
||||||
|
{
|
||||||
|
if (is_null($offset)) {
|
||||||
|
$this->resources[] = $value;
|
||||||
|
} else {
|
||||||
|
$this->resources[$offset] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetExists($offset): bool
|
||||||
|
{
|
||||||
|
return isset($this->resources[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetUnset($offset): void
|
||||||
|
{
|
||||||
|
unset($this->resources[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetGet($offset): ?ResourceInterface
|
||||||
|
{
|
||||||
|
return $this->resources[$offset] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function current(): ?ResourceInterface
|
||||||
|
{
|
||||||
|
return $this->resources[$this->position];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function key(): int
|
||||||
|
{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function next(): void
|
||||||
|
{
|
||||||
|
++$this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function valid(): bool
|
||||||
|
{
|
||||||
|
return isset($this->resources[$this->position]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Countable
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return $this->getTotalCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Dto;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\Imaging\Icon;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||||
|
use TYPO3\CMS\Core\Resource\File;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\InaccessibleFolder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\Utility\ListUtility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ResourceView
|
||||||
|
{
|
||||||
|
public ?string $moduleUri;
|
||||||
|
public ?string $editContentUri;
|
||||||
|
public ?string $editDataUri;
|
||||||
|
public ?string $replaceUri;
|
||||||
|
|
||||||
|
public bool $isDownloadable = true;
|
||||||
|
public bool $isSelectable = true;
|
||||||
|
public bool $isSelected = false;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public readonly ResourceInterface $resource,
|
||||||
|
public readonly UserPermissions $userPermissions,
|
||||||
|
public readonly Icon $icon
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getUid(): ?int
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File) {
|
||||||
|
return $this->resource->getUid();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIdentifier(): string
|
||||||
|
{
|
||||||
|
return $this->resource->getStorage()->getUid() . ':' . $this->resource->getIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetaDataUid(): ?int
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File
|
||||||
|
&& $this->canEditMetadata()) {
|
||||||
|
return (int)$this->resource->getMetaData()->offsetGet('uid');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof Folder) {
|
||||||
|
return 'folder';
|
||||||
|
}
|
||||||
|
if ($this->resource instanceof File) {
|
||||||
|
return 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'resource';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof Folder) {
|
||||||
|
return ListUtility::resolveSpecialFolderName($this->resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resource->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPath(): string
|
||||||
|
{
|
||||||
|
$resource = $this->resource;
|
||||||
|
if ($resource instanceof File && !$resource->isMissing()) {
|
||||||
|
$resource = $resource->getParentFolder();
|
||||||
|
}
|
||||||
|
if ($resource instanceof Folder) {
|
||||||
|
return $resource->getReadablePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPublicUrl(): ?string
|
||||||
|
{
|
||||||
|
if (!$this->resource instanceof File) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resource->getPublicUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPreview(): ?File
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File
|
||||||
|
&& ($this->resource->isImage() || $this->resource->isMediaFile())
|
||||||
|
) {
|
||||||
|
return $this->resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIconIdentifier(): string
|
||||||
|
{
|
||||||
|
return $this->icon->getIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIconSmall(): Icon
|
||||||
|
{
|
||||||
|
$icon = clone $this->icon;
|
||||||
|
$icon->setSize(IconSize::SMALL);
|
||||||
|
|
||||||
|
return $icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIconMedium(): Icon
|
||||||
|
{
|
||||||
|
$icon = clone $this->icon;
|
||||||
|
$icon->setSize(IconSize::MEDIUM);
|
||||||
|
|
||||||
|
return $icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIconLarge(): Icon
|
||||||
|
{
|
||||||
|
$icon = clone $this->icon;
|
||||||
|
$icon->setSize(IconSize::LARGE);
|
||||||
|
|
||||||
|
return $icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?int
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File) {
|
||||||
|
return $this->resource->getCreationTime();
|
||||||
|
}
|
||||||
|
if ($this->resource instanceof Folder) {
|
||||||
|
return $this->resource->getCreationTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedAt(): ?int
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File) {
|
||||||
|
return $this->resource->getModificationTime();
|
||||||
|
}
|
||||||
|
if ($this->resource instanceof Folder) {
|
||||||
|
return $this->resource->getModificationTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSize(): ?int
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File) {
|
||||||
|
return $this->resource->getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCheckboxConfig(): ?array
|
||||||
|
{
|
||||||
|
if (($this->resource instanceof Folder || $this->resource instanceof File)
|
||||||
|
&& !$this->resource->checkActionPermission('read')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'class' => 't3js-multi-record-selection-check',
|
||||||
|
'name' => 'CBC[_FILE|' . md5($this->getIdentifier()) . ']',
|
||||||
|
'value' => $this->getIdentifier(),
|
||||||
|
'checked' => $this->isSelected,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isMissing(): ?bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File) {
|
||||||
|
return $this->resource->isMissing();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLocked(): bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof InaccessibleFolder) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canEditMetadata(): bool
|
||||||
|
{
|
||||||
|
return $this->resource instanceof File
|
||||||
|
&& $this->resource->isIndexed()
|
||||||
|
&& $this->resource->checkActionPermission('editMeta')
|
||||||
|
&& $this->userPermissions->editMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canRead(): ?bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File || $this->resource instanceof Folder) {
|
||||||
|
return $this->resource->checkActionPermission('read');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canWrite(): ?bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File || $this->resource instanceof Folder) {
|
||||||
|
return $this->resource->checkActionPermission('write');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canDelete(): ?bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File || $this->resource instanceof Folder) {
|
||||||
|
return $this->resource->checkActionPermission('delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canCopy(): ?bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File || $this->resource instanceof Folder) {
|
||||||
|
return $this->resource->checkActionPermission('copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canRename(): ?bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File || $this->resource instanceof Folder) {
|
||||||
|
return $this->resource->checkActionPermission('rename');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canReplace(): ?bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File) {
|
||||||
|
return $this->resource->checkActionPermission('replace');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canMove(): ?bool
|
||||||
|
{
|
||||||
|
if ($this->resource instanceof File || $this->resource instanceof Folder) {
|
||||||
|
return $this->resource->checkActionPermission('move');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
readonly class UserPermissions
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public bool $editMetaData = false
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\ElementBrowser;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use TYPO3\CMS\Backend\ElementBrowser\AbstractElementBrowser;
|
||||||
|
use TYPO3\CMS\Backend\ElementBrowser\ElementBrowserInterface;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface;
|
||||||
|
use TYPO3\CMS\Backend\Tree\View\LinkParameterProviderInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Filelist\FileList;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\SortDirection;
|
||||||
|
use TYPO3\CMS\Filelist\Type\ViewMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
abstract class AbstractResourceBrowser extends AbstractElementBrowser implements ElementBrowserInterface, LinkParameterProviderInterface
|
||||||
|
{
|
||||||
|
protected ?string $expandFolder = null;
|
||||||
|
protected int $currentPage = 1;
|
||||||
|
protected string $moduleStorageIdentifier = 'media_management';
|
||||||
|
|
||||||
|
protected ?FileList $filelist = null;
|
||||||
|
protected string $sortField = 'name';
|
||||||
|
protected ?SortDirection $sortDirection = null;
|
||||||
|
protected ?ViewMode $viewMode = null;
|
||||||
|
protected bool $displayThumbs = true;
|
||||||
|
|
||||||
|
protected ?Folder $selectedFolder = null;
|
||||||
|
protected ?Matcher $resourceDisplayMatcher = null;
|
||||||
|
protected ?Matcher $resourceSelectableMatcher = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads additional JavaScript
|
||||||
|
*/
|
||||||
|
protected function initialize(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initialize($request);
|
||||||
|
$this->view = $this->backendViewFactory->create($this->getRequest(), ['typo3/cms-filelist']);
|
||||||
|
$this->view->assign('initialNavigationWidth', $this->getBackendUser()->uc['selector']['navigation']['width'] ?? 250);
|
||||||
|
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/backend/tree/file-storage-browser.js');
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/filelist/file-list-actions.js');
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/backend/multi-record-selection.js');
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/backend/global-event-handler.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initVariables(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initVariables($request);
|
||||||
|
|
||||||
|
$this->currentPage = (int)($request->getParsedBody()['currentPage'] ?? $request->getQueryParams()['currentPage'] ?? 1);
|
||||||
|
$this->expandFolder = $request->getParsedBody()['expandFolder'] ?? $request->getQueryParams()['expandFolder'] ?? null;
|
||||||
|
$this->sortField = ($request->getParsedBody()['sortField'] ?? $request->getQueryParams()['sortField'] ?? 'name');
|
||||||
|
$this->sortDirection = SortDirection::tryFrom($request->getParsedBody()['sortDirection'] ?? $request->getQueryParams()['sortDirection'] ?? '') ?? SortDirection::ASCENDING;
|
||||||
|
|
||||||
|
$this->viewMode = ViewMode::tryFrom($request->getParsedBody()['viewMode'] ?? $request->getQueryParams()['viewMode'] ?? '');
|
||||||
|
if ($this->viewMode !== null) {
|
||||||
|
$this->getBackendUser()->pushModuleData(
|
||||||
|
$this->moduleStorageIdentifier,
|
||||||
|
array_merge($this->getBackendUser()->getModuleData($this->moduleStorageIdentifier) ?? [], ['viewMode' => $this->viewMode->value])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->viewMode = ViewMode::tryFrom($this->getBackendUser()->getModuleData($this->moduleStorageIdentifier)['viewMode'] ?? '')
|
||||||
|
?? ViewMode::tryFrom($this->getBackendUser()->getTSConfig()['options.']['defaultResourcesViewMode'] ?? '')
|
||||||
|
?? ViewMode::TILES;
|
||||||
|
}
|
||||||
|
|
||||||
|
$displayThumbs = $request->getParsedBody()['displayThumbs'] ?? $request->getQueryParams()['displayThumbs'] ?? null;
|
||||||
|
if ($displayThumbs !== null) {
|
||||||
|
$this->displayThumbs = (bool)$displayThumbs;
|
||||||
|
$this->getBackendUser()->pushModuleData(
|
||||||
|
$this->moduleStorageIdentifier,
|
||||||
|
array_merge($this->getBackendUser()->getModuleData($this->moduleStorageIdentifier) ?? [], ['displayThumbs' => $this->displayThumbs])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->displayThumbs = (bool)($this->getBackendUser()->getModuleData($this->moduleStorageIdentifier)['displayThumbs'] ?? true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->filelist = GeneralUtility::makeInstance(FileList::class, $this->getRequest());
|
||||||
|
$this->filelist->viewMode = $this->viewMode;
|
||||||
|
$this->filelist->thumbs = ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] ?? false) && $this->displayThumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last selected folder is stored in user module session. Sanitize it
|
||||||
|
* to set $this->selectedFolder or keep it null.
|
||||||
|
*/
|
||||||
|
protected function initSelectedFolder(): void
|
||||||
|
{
|
||||||
|
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
|
||||||
|
if ($this->expandFolder) {
|
||||||
|
try {
|
||||||
|
$this->selectedFolder = $resourceFactory->getFolderObjectFromCombinedIdentifier($this->expandFolder);
|
||||||
|
} catch (FolderDoesNotExistException|InsufficientFolderAccessPermissionsException) {
|
||||||
|
// Outdated module session data: Last used folder has been removed meanwhile, or
|
||||||
|
// access to last used folder has been removed. Do not set a preselected folder.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSortingModeButtons(): ButtonInterface
|
||||||
|
{
|
||||||
|
$sortingButton = $this->componentFactory->createDropDownButton()
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting'))
|
||||||
|
->setIcon($this->iconFactory->getIcon($this->sortDirection->getIconIdentifier()));
|
||||||
|
|
||||||
|
$sortingModeButtons = [];
|
||||||
|
$sortableFields = $this->filelist->getSortableFields();
|
||||||
|
if (count($sortableFields) > 1) {
|
||||||
|
foreach ($sortableFields as $field) {
|
||||||
|
$label = $this->filelist->getFieldLabel($field);
|
||||||
|
|
||||||
|
$sortingModeButtons[] = $this->componentFactory->createDropDownRadio()
|
||||||
|
->setActive($this->sortField === $field)
|
||||||
|
->setHref($this->createUri([
|
||||||
|
'sortField' => $field,
|
||||||
|
'sortDirection' => SortDirection::ASCENDING->value,
|
||||||
|
'currentPage' => 1,
|
||||||
|
]))
|
||||||
|
->setLabel($label);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sortingModeButtons[] = $this->componentFactory->createDropDownDivider();
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultSortingDirectionParams = ['sortField' => $this->sortField, 'currentPage' => 1];
|
||||||
|
$sortingModeButtons[] = $this->componentFactory->createDropDownRadio()
|
||||||
|
->setActive($this->sortDirection === SortDirection::ASCENDING)
|
||||||
|
->setHref($this->createUri(array_merge($defaultSortingDirectionParams, ['sortDirection' => SortDirection::ASCENDING->value])))
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting.asc'));
|
||||||
|
$sortingModeButtons[] = $this->componentFactory->createDropDownRadio()
|
||||||
|
->setActive($this->sortDirection === SortDirection::DESCENDING)
|
||||||
|
->setHref($this->createUri(array_merge($defaultSortingDirectionParams, ['sortDirection' => SortDirection::DESCENDING->value])))
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting.desc'));
|
||||||
|
|
||||||
|
foreach ($sortingModeButtons as $sortingModeButton) {
|
||||||
|
$sortingButton->addItem($sortingModeButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sortingButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getViewModeButton(): ButtonInterface
|
||||||
|
{
|
||||||
|
$viewModeItems = [];
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownRadio()
|
||||||
|
->setActive($this->viewMode === ViewMode::TILES)
|
||||||
|
->setHref($this->createUri(['viewMode' => ViewMode::TILES->value]))
|
||||||
|
->setLabel($this->getLanguageService()->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->viewMode === ViewMode::LIST)
|
||||||
|
->setHref($this->createUri(['viewMode' => ViewMode::LIST->value]))
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.list'))
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-viewmode-list'));
|
||||||
|
if (!($this->getBackendUser()->getTSConfig()['options.']['noThumbsInEB'] ?? false)) {
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownDivider();
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownToggle()
|
||||||
|
->setActive($this->displayThumbs)
|
||||||
|
->setHref($this->createUri(['displayThumbs' => $this->displayThumbs ? 0 : 1]))
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.showThumbnails'))
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-image'));
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
($this->getBackendUser()->getTSConfig()['options.']['file_list.']['displayColumnSelector'] ?? true)
|
||||||
|
&& $this->viewMode === ViewMode::LIST
|
||||||
|
&& $this->identifier === 'file'
|
||||||
|
) {
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/backend/column-selector-button.js');
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownDivider();
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownItem()
|
||||||
|
->setTag('typo3-backend-column-selector-button')
|
||||||
|
->setLabel($this->getLanguageService()->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(
|
||||||
|
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:showColumnsSelection'),
|
||||||
|
$this->tcaSchemaFactory->get('sys_file')->getTitle($this->getLanguageService()->sL(...)),
|
||||||
|
),
|
||||||
|
'data-button-ok' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:updateColumnView'),
|
||||||
|
'data-button-close' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel'),
|
||||||
|
'data-error-message' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:updateColumnView.error'),
|
||||||
|
])
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-options'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewModeButton = $this->componentFactory->createDropDownButton()
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view'))
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-cog'));
|
||||||
|
foreach ($viewModeItems as $viewModeItem) {
|
||||||
|
$viewModeButton->addItem($viewModeItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $viewModeButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $values Array of values to include into the parameters
|
||||||
|
* @return string[] Array of parameters which have to be added to URLs
|
||||||
|
*/
|
||||||
|
public function getUrlParameters(array $values): array
|
||||||
|
{
|
||||||
|
$values = array_replace_recursive(
|
||||||
|
array_merge(
|
||||||
|
[
|
||||||
|
'mode' => $this->identifier,
|
||||||
|
'expandFolder' => $values['identifier'] ?? $this->expandFolder,
|
||||||
|
],
|
||||||
|
$this->browserParameters->toQueryParameters()
|
||||||
|
),
|
||||||
|
$values
|
||||||
|
);
|
||||||
|
|
||||||
|
return array_filter($values, static function ($value) {
|
||||||
|
return $value !== null && trim((string)$value) !== '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createUri(array $parameters = []): string
|
||||||
|
{
|
||||||
|
$parameters = $this->getUrlParameters($parameters);
|
||||||
|
return (string)$this->uriBuilder->buildUriFromRequest($this->getRequest(), $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session data for this class can be set from outside with this method.
|
||||||
|
*
|
||||||
|
* @param mixed[] $data Session data array
|
||||||
|
* @return array<int, array|bool> Session data and boolean which indicates that data needs to be stored in session because it's changed
|
||||||
|
*/
|
||||||
|
public function processSessionData($data): array
|
||||||
|
{
|
||||||
|
if ($this->expandFolder !== null) {
|
||||||
|
$data['expandFolder'] = $this->expandFolder;
|
||||||
|
$store = true;
|
||||||
|
} else {
|
||||||
|
$this->expandFolder = $data['expandFolder'] ?? null;
|
||||||
|
$store = false;
|
||||||
|
}
|
||||||
|
return [$data, $store];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\ElementBrowser;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use TYPO3\CMS\Backend\View\ResourceUtilityRenderer;
|
||||||
|
use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFileTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFolderTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Browser to create new files. This is used with mode=create_file in the ElementBrowser.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class CreateFileBrowser extends AbstractResourceBrowser
|
||||||
|
{
|
||||||
|
public const IDENTIFIER = 'create_file';
|
||||||
|
protected string $identifier = self::IDENTIFIER;
|
||||||
|
|
||||||
|
protected function initialize(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initialize($request);
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/filelist/resource-creation.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initializeDragUploader(): void
|
||||||
|
{
|
||||||
|
$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 initVariables(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initVariables($request);
|
||||||
|
$this->resourceDisplayMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$this->resourceDisplayMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class));
|
||||||
|
$this->resourceDisplayMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFileTypeMatcher::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
$this->initSelectedFolder();
|
||||||
|
$this->initializeDragUploader();
|
||||||
|
$contentHtml = '';
|
||||||
|
|
||||||
|
if ($this->selectedFolder !== null) {
|
||||||
|
$markup = [];
|
||||||
|
|
||||||
|
// Build the file creation and upload forms
|
||||||
|
$resourceUtilityRenderer = GeneralUtility::makeInstance(ResourceUtilityRenderer::class, $this);
|
||||||
|
$markup[] = $resourceUtilityRenderer->createDragUpload($this->selectedFolder);
|
||||||
|
$markup[] = $resourceUtilityRenderer->addOnlineMedia($this->getRequest(), $this->selectedFolder);
|
||||||
|
$markup[] = $resourceUtilityRenderer->createRegularFile($this->getRequest(), $this->selectedFolder);
|
||||||
|
|
||||||
|
// Create the filelist
|
||||||
|
$this->filelist->start(
|
||||||
|
$this->selectedFolder,
|
||||||
|
MathUtility::forceIntegerInRange($this->currentPage, 1, 100000),
|
||||||
|
$this->sortField,
|
||||||
|
$this->sortDirection,
|
||||||
|
Mode::BROWSE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the filelist header bar
|
||||||
|
$markup[] = '<div class="row justify-content-between mb-2">';
|
||||||
|
$markup[] = ' <div class="col-auto"></div>';
|
||||||
|
$markup[] = ' <div class="col-auto">';
|
||||||
|
$markup[] = ' ' . $this->getSortingModeButtons();
|
||||||
|
$markup[] = ' ' . $this->getViewModeButton();
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = '</div>';
|
||||||
|
|
||||||
|
$this->filelist->setResourceDisplayMatcher($this->resourceDisplayMatcher);
|
||||||
|
$this->filelist->setResourceSelectableMatcher($this->resourceSelectableMatcher);
|
||||||
|
$markup[] = $this->filelist->render(null, $this->view);
|
||||||
|
|
||||||
|
$contentHtml = implode(PHP_EOL, $markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentOnly = (bool)($this->getRequest()->getQueryParams()['contentOnly'] ?? false);
|
||||||
|
$this->pageRenderer->setTitle($this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:actions.new_file'));
|
||||||
|
$this->view->assign('selectedFolder', $this->selectedFolder);
|
||||||
|
$this->view->assign('content', $contentHtml);
|
||||||
|
$this->view->assign('contentOnly', $contentOnly);
|
||||||
|
|
||||||
|
$content = $this->view->render('ElementBrowser/ResourceCreation');
|
||||||
|
if ($contentOnly) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
$this->pageRenderer->setBodyContent('<body ' . $this->getBodyTagParameters() . '>' . $content);
|
||||||
|
return $this->pageRenderer->render($this->getRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\ElementBrowser;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use TYPO3\CMS\Backend\View\ResourceUtilityRenderer;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFolderTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Browser to create one or more folders. This is used with type=folder to select folders.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class CreateFolderBrowser extends AbstractResourceBrowser
|
||||||
|
{
|
||||||
|
public const IDENTIFIER = 'create_folder';
|
||||||
|
protected string $identifier = self::IDENTIFIER;
|
||||||
|
|
||||||
|
protected function initialize(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initialize($request);
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/filelist/resource-creation.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initVariables(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initVariables($request);
|
||||||
|
$this->resourceDisplayMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$this->resourceDisplayMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
$this->initSelectedFolder();
|
||||||
|
$contentHtml = '';
|
||||||
|
|
||||||
|
if ($this->selectedFolder !== null) {
|
||||||
|
$markup = [];
|
||||||
|
|
||||||
|
// Build the folder creation form
|
||||||
|
$resourceUtilityRenderer = GeneralUtility::makeInstance(ResourceUtilityRenderer::class, $this);
|
||||||
|
$markup[] = $resourceUtilityRenderer->createFolder($this->getRequest(), $this->selectedFolder);
|
||||||
|
|
||||||
|
// Create the filelist
|
||||||
|
$this->filelist->start(
|
||||||
|
$this->selectedFolder,
|
||||||
|
MathUtility::forceIntegerInRange($this->currentPage, 1, 100000),
|
||||||
|
$this->sortField,
|
||||||
|
$this->sortDirection,
|
||||||
|
Mode::BROWSE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the filelist header bar
|
||||||
|
$markup[] = '<div class="row justify-content-between mb-2">';
|
||||||
|
$markup[] = ' <div class="col-auto"></div>';
|
||||||
|
$markup[] = ' <div class="col-auto">';
|
||||||
|
$markup[] = ' ' . $this->getSortingModeButtons();
|
||||||
|
$markup[] = ' ' . $this->getViewModeButton();
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = '</div>';
|
||||||
|
|
||||||
|
$this->filelist->setResourceDisplayMatcher($this->resourceDisplayMatcher);
|
||||||
|
$this->filelist->setResourceSelectableMatcher($this->resourceSelectableMatcher);
|
||||||
|
$markup[] = $this->filelist->render(null, $this->view);
|
||||||
|
|
||||||
|
$contentHtml = implode(PHP_EOL, $markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentOnly = (bool)($this->getRequest()->getQueryParams()['contentOnly'] ?? false);
|
||||||
|
$this->pageRenderer->setTitle($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:createFolder'));
|
||||||
|
$this->view->assign('selectedFolder', $this->selectedFolder);
|
||||||
|
$this->view->assign('content', $contentHtml);
|
||||||
|
$this->view->assign('contentOnly', $contentOnly);
|
||||||
|
|
||||||
|
$content = $this->view->render('ElementBrowser/ResourceCreation');
|
||||||
|
if ($contentOnly) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
$this->pageRenderer->setBodyContent('<body ' . $this->getBodyTagParameters() . '>' . $content);
|
||||||
|
return $this->pageRenderer->render($this->getRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\ElementBrowser;
|
||||||
|
|
||||||
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use TYPO3\CMS\Backend\ElementBrowser\Event\IsFileSelectableEvent;
|
||||||
|
use TYPO3\CMS\Backend\View\RecordSearchBoxComponent;
|
||||||
|
use TYPO3\CMS\Backend\View\ResourceUtilityRenderer;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||||
|
use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\Search\FileSearchDemand;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\AndMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\MatcherInterface;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFileExtensionMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFolderTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Browser for files. This is used when adding a FAL inline image with the 'add image' button in FormEngine.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class FileBrowser extends AbstractResourceBrowser
|
||||||
|
{
|
||||||
|
public const IDENTIFIER = 'file';
|
||||||
|
protected string $identifier = self::IDENTIFIER;
|
||||||
|
|
||||||
|
protected ?string $searchWord = null;
|
||||||
|
protected ?FileExtensionFilter $fileExtensionFilter;
|
||||||
|
protected ?FileSearchDemand $searchDemand = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads additional JavaScript
|
||||||
|
*/
|
||||||
|
protected function initialize(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initialize($request);
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/filelist/browse-files.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initVariables(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initVariables($request);
|
||||||
|
|
||||||
|
$this->searchWord = trim((string)($request->getParsedBody()['searchTerm'] ?? $request->getQueryParams()['searchTerm'] ?? ''));
|
||||||
|
|
||||||
|
$fileExtensions = $this->browserParameters->getFileExtensions();
|
||||||
|
|
||||||
|
$this->fileExtensionFilter = GeneralUtility::makeInstance(FileExtensionFilter::class);
|
||||||
|
if ($fileExtensions['allowed'] !== []) {
|
||||||
|
$this->fileExtensionFilter->setAllowedFileExtensions(implode(',', $fileExtensions['allowed']));
|
||||||
|
}
|
||||||
|
if ($fileExtensions['disallowed'] !== []) {
|
||||||
|
$this->fileExtensionFilter->setDisallowedFileExtensions(implode(',', $fileExtensions['disallowed']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resourceDisplayMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$this->resourceDisplayMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class));
|
||||||
|
$this->resourceDisplayMatcher->addMatcher(
|
||||||
|
GeneralUtility::makeInstance(
|
||||||
|
AndMatcher::class,
|
||||||
|
GeneralUtility::makeInstance(ResourceFileExtensionMatcher::class)
|
||||||
|
->setExtensions($this->fileExtensionFilter->getAllowedFileExtensions() ?? ['*'])
|
||||||
|
->setIgnoredExtensions($this->fileExtensionFilter->getDisallowedFileExtensions() ?? []),
|
||||||
|
new class (GeneralUtility::makeInstance(EventDispatcherInterface::class)) implements MatcherInterface {
|
||||||
|
public function __construct(private readonly EventDispatcherInterface $eventDispatcher) {}
|
||||||
|
public function supports(mixed $item): bool
|
||||||
|
{
|
||||||
|
return $item instanceof ResourceInterface;
|
||||||
|
}
|
||||||
|
public function match(mixed $item): bool
|
||||||
|
{
|
||||||
|
return $this->eventDispatcher->dispatch(new IsFileSelectableEvent($item))->isFileSelectable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->resourceSelectableMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$this->resourceSelectableMatcher->addMatcher(
|
||||||
|
GeneralUtility::makeInstance(ResourceFileExtensionMatcher::class)
|
||||||
|
->setExtensions($this->fileExtensionFilter->getAllowedFileExtensions() ?? ['*'])
|
||||||
|
->setIgnoredExtensions($this->fileExtensionFilter->getDisallowedFileExtensions() ?? [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
$this->initSelectedFolder();
|
||||||
|
$contentHtml = '';
|
||||||
|
|
||||||
|
if ($this->selectedFolder instanceof Folder) {
|
||||||
|
$markup = [];
|
||||||
|
|
||||||
|
// Prepare search box, since the component should always be displayed, even if no files are available
|
||||||
|
$markup[] = '<div class="mb-4">';
|
||||||
|
$markup[] = GeneralUtility::makeInstance(RecordSearchBoxComponent::class)
|
||||||
|
->setSearchWord($this->searchWord ?? '')
|
||||||
|
->render($this->getRequest(), $this->createUri());
|
||||||
|
$markup[] = '</div>';
|
||||||
|
|
||||||
|
// Create the filelist
|
||||||
|
$this->filelist->start(
|
||||||
|
$this->selectedFolder,
|
||||||
|
MathUtility::forceIntegerInRange($this->currentPage, 1, 100000),
|
||||||
|
$this->sortField,
|
||||||
|
$this->sortDirection,
|
||||||
|
Mode::BROWSE
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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'] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the filelist header bar
|
||||||
|
$markup[] = '<div class="row justify-content-between mb-2">';
|
||||||
|
$markup[] = ' <div class="col-auto">';
|
||||||
|
$markup[] = ' <div class="hidden t3js-multi-record-selection-actions">';
|
||||||
|
$markup[] = ' <strong>' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selection')) . '</strong>';
|
||||||
|
$markup[] = ' <button type="button" class="btn btn-default btn-sm" data-multi-record-selection-action="import" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:importSelection')) . '">';
|
||||||
|
$markup[] = ' ' . $this->iconFactory->getIcon('actions-document-import-t3d', IconSize::SMALL);
|
||||||
|
$markup[] = ' ' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:importSelection'));
|
||||||
|
$markup[] = ' </button>';
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = ' <div class="col-auto">';
|
||||||
|
$markup[] = ' ' . $this->getSortingModeButtons();
|
||||||
|
$markup[] = ' ' . $this->getViewModeButton();
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = '</div>';
|
||||||
|
|
||||||
|
$this->filelist->setResourceDisplayMatcher($this->resourceDisplayMatcher);
|
||||||
|
$this->filelist->setResourceSelectableMatcher($this->resourceSelectableMatcher);
|
||||||
|
$searchDemand = $this->searchWord !== ''
|
||||||
|
? FileSearchDemand::createForSearchTerm($this->searchWord)->withFolder($this->selectedFolder)->withRecursive()
|
||||||
|
: null;
|
||||||
|
$markup[] = $this->filelist->render($searchDemand, $this->view);
|
||||||
|
|
||||||
|
// Build the file upload and folder creation form
|
||||||
|
$resourceUtilityRenderer = GeneralUtility::makeInstance(ResourceUtilityRenderer::class, $this);
|
||||||
|
$markup[] = $resourceUtilityRenderer->uploadForm($this->getRequest(), $this->selectedFolder, $this->fileExtensionFilter);
|
||||||
|
$markup[] = $resourceUtilityRenderer->addOnlineMedia($this->getRequest(), $this->selectedFolder, $this->fileExtensionFilter);
|
||||||
|
$markup[] = $resourceUtilityRenderer->createFolder($this->getRequest(), $this->selectedFolder);
|
||||||
|
|
||||||
|
$contentHtml = implode('', $markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentOnly = (bool)($this->getRequest()->getQueryParams()['contentOnly'] ?? false);
|
||||||
|
$this->pageRenderer->setTitle($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:fileSelector'));
|
||||||
|
$this->view->assign('selectedFolder', $this->selectedFolder);
|
||||||
|
$this->view->assign('content', $contentHtml);
|
||||||
|
$this->view->assign('contentOnly', $contentOnly);
|
||||||
|
|
||||||
|
$content = $this->view->render('ElementBrowser/Files');
|
||||||
|
if ($contentOnly) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
$this->pageRenderer->setBodyContent('<body ' . $this->getBodyTagParameters() . '>' . $content);
|
||||||
|
return $this->pageRenderer->render($this->getRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\ElementBrowser;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use TYPO3\CMS\Backend\View\ResourceUtilityRenderer;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFolderTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Browser for folders. This is used with type=folder to select folders.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class FolderBrowser extends AbstractResourceBrowser
|
||||||
|
{
|
||||||
|
public const IDENTIFIER = 'folder';
|
||||||
|
protected string $identifier = self::IDENTIFIER;
|
||||||
|
|
||||||
|
protected function initialize(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initialize($request);
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/filelist/browse-folders.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initVariables(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initVariables($request);
|
||||||
|
$this->resourceDisplayMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$this->resourceDisplayMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class));
|
||||||
|
$this->resourceSelectableMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$this->resourceSelectableMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): string
|
||||||
|
{
|
||||||
|
$this->initSelectedFolder();
|
||||||
|
$contentHtml = '';
|
||||||
|
|
||||||
|
if ($this->selectedFolder instanceof Folder) {
|
||||||
|
$markup = [];
|
||||||
|
|
||||||
|
// Create the filelist
|
||||||
|
$this->filelist->start(
|
||||||
|
$this->selectedFolder,
|
||||||
|
MathUtility::forceIntegerInRange($this->currentPage, 1, 100000),
|
||||||
|
$this->sortField,
|
||||||
|
$this->sortDirection,
|
||||||
|
Mode::BROWSE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the filelist header bar
|
||||||
|
$markup[] = '<div class="row justify-content-between mb-2">';
|
||||||
|
$markup[] = ' <div class="col-auto">';
|
||||||
|
$markup[] = ' <div class="hidden t3js-multi-record-selection-actions">';
|
||||||
|
$markup[] = ' <strong>' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selection')) . '</strong>';
|
||||||
|
$markup[] = ' <button type="button" class="btn btn-default btn-sm" data-multi-record-selection-action="import" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:importSelection')) . '">';
|
||||||
|
$markup[] = ' ' . $this->iconFactory->getIcon('actions-document-import-t3d', IconSize::SMALL);
|
||||||
|
$markup[] = ' ' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:importSelection'));
|
||||||
|
$markup[] = ' </button>';
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = ' <div class="col-auto">';
|
||||||
|
$markup[] = ' ' . $this->getSortingModeButtons();
|
||||||
|
$markup[] = ' ' . $this->getViewModeButton();
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = '</div>';
|
||||||
|
|
||||||
|
$this->filelist->setResourceDisplayMatcher($this->resourceDisplayMatcher);
|
||||||
|
$this->filelist->setResourceSelectableMatcher($this->resourceSelectableMatcher);
|
||||||
|
$markup[] = $this->filelist->render(null, $this->view);
|
||||||
|
|
||||||
|
// Build the folder creation form
|
||||||
|
$resourceUtilityRenderer = GeneralUtility::makeInstance(ResourceUtilityRenderer::class, $this);
|
||||||
|
$markup[] = $resourceUtilityRenderer->createFolder($this->getRequest(), $this->selectedFolder);
|
||||||
|
|
||||||
|
$contentHtml = implode('', $markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentOnly = (bool)($this->getRequest()->getQueryParams()['contentOnly'] ?? false);
|
||||||
|
$this->pageRenderer->setTitle($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:folderSelector'));
|
||||||
|
$this->view->assign('selectedFolder', $this->selectedFolder);
|
||||||
|
$this->view->assign('content', $contentHtml);
|
||||||
|
$this->view->assign('contentOnly', $contentOnly);
|
||||||
|
|
||||||
|
$content = $this->view->render('ElementBrowser/Folder');
|
||||||
|
if ($contentOnly) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
$this->pageRenderer->setBodyContent('<body ' . $this->getBodyTagParameters() . '>' . $content);
|
||||||
|
return $this->pageRenderer->render($this->getRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Event;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
use TYPO3\CMS\Filelist\FileList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event to modify the rendered row data for a file or folder in the File List.
|
||||||
|
*/
|
||||||
|
final class AfterFileListRowPreparedEvent
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ResourceInterface $resource,
|
||||||
|
private array $data,
|
||||||
|
private readonly FileList $fileList,
|
||||||
|
private array $attributes,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getResource(): ResourceInterface
|
||||||
|
{
|
||||||
|
return $this->resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(): array
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setData(array $data): void
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileList(): FileList
|
||||||
|
{
|
||||||
|
return $this->fileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttributes(): array
|
||||||
|
{
|
||||||
|
return $this->attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAttributes(array $attributes): void
|
||||||
|
{
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Event;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\FileInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listeners to this event are be able to modify the form data,
|
||||||
|
* used to render the edit file form in the filelist module.
|
||||||
|
*/
|
||||||
|
final class ModifyEditFileFormDataEvent
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private array $formData,
|
||||||
|
private readonly FileInterface $file,
|
||||||
|
private readonly ServerRequestInterface $request
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getFormData(): array
|
||||||
|
{
|
||||||
|
return $this->formData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFormData(array $formData): void
|
||||||
|
{
|
||||||
|
$this->formData = $formData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile(): FileInterface
|
||||||
|
{
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequest(): ServerRequestInterface
|
||||||
|
{
|
||||||
|
return $this->request;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Event;
|
||||||
|
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\ActionGroup;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\ComponentGroup;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\ComponentInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\FileInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired to modify icons rendered for the file listings
|
||||||
|
*/
|
||||||
|
final readonly class ProcessFileListActionsEvent
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ComponentGroup $primary,
|
||||||
|
private ComponentGroup $secondary,
|
||||||
|
private ResourceInterface $fileOrFolder,
|
||||||
|
private RequestInterface $request,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getResource(): ResourceInterface
|
||||||
|
{
|
||||||
|
return $this->fileOrFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFile(): bool
|
||||||
|
{
|
||||||
|
return $this->fileOrFolder instanceof FileInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new action or override an existing one. Latter is only possible,
|
||||||
|
* in case $columnName is given. Otherwise, the column will be added with
|
||||||
|
* a numeric index, which is generally not recommended. It's also possible
|
||||||
|
* to define the position of an action with either the "before" or "after"
|
||||||
|
* argument, while their value must be an existing action.
|
||||||
|
*
|
||||||
|
* Note: In case none or an invalid $group is provided, the new action will
|
||||||
|
* be added to the secondary group.
|
||||||
|
*/
|
||||||
|
public function setAction(
|
||||||
|
?ComponentInterface $action,
|
||||||
|
string $actionName,
|
||||||
|
ActionGroup $group = ActionGroup::secondary,
|
||||||
|
string $before = '',
|
||||||
|
string $after = '',
|
||||||
|
): void {
|
||||||
|
if ($actionName === '') {
|
||||||
|
throw new \Exception('You must provide a valid action name when adding a new action.', 1761584689);
|
||||||
|
}
|
||||||
|
|
||||||
|
$componentGroup = match ($group) {
|
||||||
|
ActionGroup::primary => $this->primary,
|
||||||
|
ActionGroup::secondary => $this->secondary,
|
||||||
|
};
|
||||||
|
$componentGroup->add($actionName, $action, $before, $after);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the action exists in the given group. In case none or
|
||||||
|
* an invalid $group is provided, both groups will be checked.
|
||||||
|
*/
|
||||||
|
public function hasAction(string $actionName, ?ActionGroup $group = null): bool
|
||||||
|
{
|
||||||
|
return match ($group) {
|
||||||
|
ActionGroup::primary => $this->primary->has($actionName),
|
||||||
|
ActionGroup::secondary => $this->secondary->has($actionName),
|
||||||
|
null => $this->primary->has($actionName) || $this->secondary->has($actionName),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get action by its name. In case the action exists in both groups
|
||||||
|
* and none or an invalid $group is provided, the action from the
|
||||||
|
* "primary" group will be returned.
|
||||||
|
*/
|
||||||
|
public function getAction(string $actionName, ?ActionGroup $group = null): ?ComponentInterface
|
||||||
|
{
|
||||||
|
return match ($group) {
|
||||||
|
ActionGroup::primary => $this->primary->get($actionName),
|
||||||
|
ActionGroup::secondary => $this->secondary->get($actionName),
|
||||||
|
null => $this->primary->get($actionName) ?? $this->secondary->get($actionName),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove action by its name. In case the action exists in both groups
|
||||||
|
* and none or an invalid $group is provided, the action will be removed
|
||||||
|
* from both groups.
|
||||||
|
*/
|
||||||
|
public function removeAction(string $actionName, ?ActionGroup $group = null): void
|
||||||
|
{
|
||||||
|
if ($group === null) {
|
||||||
|
$this->primary->remove($actionName);
|
||||||
|
$this->secondary->remove($actionName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match ($group) {
|
||||||
|
ActionGroup::primary => $this->primary->remove($actionName),
|
||||||
|
ActionGroup::secondary => $this->secondary->remove($actionName),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function moveActionTo(
|
||||||
|
string $actionName,
|
||||||
|
ActionGroup $group,
|
||||||
|
string $before = '',
|
||||||
|
string $after = '',
|
||||||
|
): void {
|
||||||
|
if (!$this->hasAction($actionName)) {
|
||||||
|
throw new \RuntimeException('The action "' . $actionName . '" does not exist and therefore cannot be moved.', 1761646465);
|
||||||
|
}
|
||||||
|
$action = $this->getAction($actionName);
|
||||||
|
$this->removeAction($actionName);
|
||||||
|
$this->setAction($action, $actionName, $group, $before, $after);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actions of a specific group
|
||||||
|
*/
|
||||||
|
public function getActionGroup(ActionGroup $group): ComponentGroup
|
||||||
|
{
|
||||||
|
return match ($group) {
|
||||||
|
ActionGroup::primary => $this->primary,
|
||||||
|
ActionGroup::secondary => $this->secondary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequest(): RequestInterface
|
||||||
|
{
|
||||||
|
return $this->request;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\EventListener;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Backend\Controller\Event\AfterBackendPageRenderEvent;
|
||||||
|
use TYPO3\CMS\Core\Attribute\AsEventListener;
|
||||||
|
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||||
|
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds locallang labels for file information used in a modal window (thus in "global" scope)
|
||||||
|
*/
|
||||||
|
final readonly class AfterBackendPageRenderEventListener
|
||||||
|
{
|
||||||
|
public function __construct(private PageRenderer $pageRenderer) {}
|
||||||
|
|
||||||
|
#[AsEventListener(event: AfterBackendPageRenderEvent::class)]
|
||||||
|
public function __invoke(): void
|
||||||
|
{
|
||||||
|
$this->pageRenderer->addInlineLanguageLabelFile('EXT:filelist/Resources/Private/Language/locallang.xlf');
|
||||||
|
$this->pageRenderer->addInlineLanguageLabelArray([
|
||||||
|
'file_info_filename' => $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:c_name'),
|
||||||
|
'file_info_filesize' => $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:c_size'),
|
||||||
|
'file_info_creation_date' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.crdate'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLanguageService(): LanguageService
|
||||||
|
{
|
||||||
|
return $GLOBALS['LANG'];
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,368 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\LinkHandler;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use TYPO3\CMS\Backend\Controller\AbstractLinkBrowserController;
|
||||||
|
use TYPO3\CMS\Backend\LinkHandler\LinkHandlerInterface;
|
||||||
|
use TYPO3\CMS\Backend\LinkHandler\LinkHandlerVariableProviderInterface;
|
||||||
|
use TYPO3\CMS\Backend\LinkHandler\LinkHandlerViewProviderInterface;
|
||||||
|
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface;
|
||||||
|
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||||
|
use TYPO3\CMS\Backend\Tree\View\LinkParameterProviderInterface;
|
||||||
|
use TYPO3\CMS\Backend\View\BackendViewFactory;
|
||||||
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||||
|
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||||
|
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||||
|
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
|
||||||
|
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
|
||||||
|
use TYPO3\CMS\Core\Resource\File;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
|
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\View\ViewInterface;
|
||||||
|
use TYPO3\CMS\Filelist\FileList;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\LinkType;
|
||||||
|
use TYPO3\CMS\Filelist\Type\SortDirection;
|
||||||
|
use TYPO3\CMS\Filelist\Type\ViewMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
abstract class AbstractResourceLinkHandler implements LinkHandlerInterface, LinkHandlerVariableProviderInterface, LinkHandlerViewProviderInterface, LinkParameterProviderInterface
|
||||||
|
{
|
||||||
|
protected ?string $expandFolder = null;
|
||||||
|
protected int $currentPage = 1;
|
||||||
|
protected string $moduleStorageIdentifier = 'media_management';
|
||||||
|
|
||||||
|
protected ?FileList $filelist = null;
|
||||||
|
protected string $sortField = 'name';
|
||||||
|
protected ?SortDirection $sortDirection = null;
|
||||||
|
protected ?ViewMode $viewMode = null;
|
||||||
|
protected bool $displayThumbs = true;
|
||||||
|
|
||||||
|
protected ?Folder $selectedFolder = null;
|
||||||
|
protected ?Matcher $resourceDisplayMatcher = null;
|
||||||
|
protected ?Matcher $resourceSelectableMatcher = null;
|
||||||
|
|
||||||
|
protected LinkType $type;
|
||||||
|
protected array $linkParts = [];
|
||||||
|
|
||||||
|
protected ViewInterface $view;
|
||||||
|
protected LanguageService $languageService;
|
||||||
|
protected AbstractLinkBrowserController $linkBrowser;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected readonly IconFactory $iconFactory,
|
||||||
|
protected readonly ResourceFactory $resourceFactory,
|
||||||
|
protected readonly PageRenderer $pageRenderer,
|
||||||
|
protected readonly UriBuilder $uriBuilder,
|
||||||
|
protected readonly TcaSchemaFactory $tcaSchemaFactory,
|
||||||
|
protected readonly LanguageServiceFactory $languageServiceFactory,
|
||||||
|
protected readonly ComponentFactory $componentFactory,
|
||||||
|
) {
|
||||||
|
$this->languageService = $this->languageServiceFactory->createFromUserPreferences($this->getBackendUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canHandleLink(array $linkParts): bool
|
||||||
|
{
|
||||||
|
if (!$linkParts['url']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isset($linkParts['url'][$this->type->value]) && $linkParts['url'][$this->type->value] instanceof ($this->type->getResourceType())) {
|
||||||
|
$this->linkParts = $linkParts;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formatCurrentUrl(): string
|
||||||
|
{
|
||||||
|
$resource = $this->linkParts['url'][$this->type->value];
|
||||||
|
if (!$resource->checkActionPermission('read')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if ($resource->getStorage()->isFallbackStorage()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return $this->linkParts['url'][$this->type->value]->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createView(BackendViewFactory $backendViewFactory, ServerRequestInterface $request): ViewInterface
|
||||||
|
{
|
||||||
|
return $backendViewFactory->create($request, ['typo3/cms-filelist']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setView(ViewInterface $view): self
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getView(): ViewInterface
|
||||||
|
{
|
||||||
|
return $this->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLinkAttributes(): array
|
||||||
|
{
|
||||||
|
return ['target', 'title', 'class', 'params', 'rel', 'download'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initialize(AbstractLinkBrowserController $linkBrowser, $identifier, array $configuration)
|
||||||
|
{
|
||||||
|
$this->linkBrowser = $linkBrowser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initializeVariables(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/backend/tree/file-storage-browser.js');
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/filelist/file-list-actions.js');
|
||||||
|
|
||||||
|
$this->currentPage = (int)($request->getParsedBody()['currentPage'] ?? $request->getQueryParams()['currentPage'] ?? 1);
|
||||||
|
$this->sortField = ($request->getParsedBody()['sortField'] ?? $request->getQueryParams()['sortField'] ?? 'name');
|
||||||
|
$this->sortDirection = SortDirection::tryFrom($request->getParsedBody()['sortDirection'] ?? $request->getQueryParams()['sortDirection'] ?? '') ?? SortDirection::ASCENDING;
|
||||||
|
|
||||||
|
$this->viewMode = ViewMode::tryFrom($request->getParsedBody()['viewMode'] ?? $request->getQueryParams()['viewMode'] ?? '');
|
||||||
|
if ($this->viewMode !== null) {
|
||||||
|
$this->getBackendUser()->pushModuleData(
|
||||||
|
$this->moduleStorageIdentifier,
|
||||||
|
array_merge($this->getBackendUser()->getModuleData($this->moduleStorageIdentifier) ?? [], ['viewMode' => $this->viewMode->value])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->viewMode = ViewMode::tryFrom($this->getBackendUser()->getModuleData($this->moduleStorageIdentifier)['viewMode'] ?? '')
|
||||||
|
?? ViewMode::tryFrom($this->getBackendUser()->getTSConfig()['options.']['defaultResourcesViewMode'] ?? '')
|
||||||
|
?? ViewMode::TILES;
|
||||||
|
}
|
||||||
|
|
||||||
|
$displayThumbs = $request->getParsedBody()['displayThumbs'] ?? $request->getQueryParams()['displayThumbs'] ?? null;
|
||||||
|
if ($displayThumbs !== null) {
|
||||||
|
$this->displayThumbs = (bool)$displayThumbs;
|
||||||
|
$this->getBackendUser()->pushModuleData(
|
||||||
|
$this->moduleStorageIdentifier,
|
||||||
|
array_merge($this->getBackendUser()->getModuleData($this->moduleStorageIdentifier) ?? [], ['displayThumbs' => $this->displayThumbs])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->displayThumbs = (bool)($this->getBackendUser()->getModuleData($this->moduleStorageIdentifier)['displayThumbs'] ?? true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selected Folder folder
|
||||||
|
$this->expandFolder = $request->getParsedBody()['expandFolder'] ?? $request->getQueryParams()['expandFolder'] ?? null;
|
||||||
|
if ($this->expandFolder === null) {
|
||||||
|
if (!empty($this->linkParts)) {
|
||||||
|
$resource = $this->linkParts['url'][$this->type->value];
|
||||||
|
if ($resource instanceof File) {
|
||||||
|
$resource = $resource->getParentFolder();
|
||||||
|
}
|
||||||
|
if ($resource instanceof Folder) {
|
||||||
|
$this->expandFolder = $resource->getCombinedIdentifier();
|
||||||
|
if ($this->type === LinkType::FOLDER) {
|
||||||
|
// Select the parent folder of selected folder as entry point.
|
||||||
|
$this->expandFolder = $resource->getParentFolder()->getCombinedIdentifier();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Look up in the user's session which folder was opened the last time
|
||||||
|
$moduleSessionData = $this->getBackendUser()->getModuleData('browse_links.php', 'ses');
|
||||||
|
$this->expandFolder = $moduleSessionData['expandFolder'] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->expandFolder) {
|
||||||
|
try {
|
||||||
|
$selectedFolder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($this->expandFolder);
|
||||||
|
if ($selectedFolder->checkActionPermission('read') && !$selectedFolder->getStorage()->isFallbackStorage()) {
|
||||||
|
$this->selectedFolder = $selectedFolder;
|
||||||
|
}
|
||||||
|
} catch (FolderDoesNotExistException|InsufficientFolderAccessPermissionsException) {
|
||||||
|
// Outdated module session data: Last used folder has been removed meanwhile, or
|
||||||
|
// access to last used folder has been removed. Do not set a preselected folder.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->filelist = GeneralUtility::makeInstance(FileList::class, $request);
|
||||||
|
$this->filelist->viewMode = $this->viewMode;
|
||||||
|
$this->filelist->thumbs = ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] ?? false) && $this->displayThumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function modifyLinkAttributes(array $fieldDefinitions): array
|
||||||
|
{
|
||||||
|
return $fieldDefinitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUpdateSupported(): bool
|
||||||
|
{
|
||||||
|
$resource = $this->linkParts['url'][$this->type->value];
|
||||||
|
if (!$resource->checkActionPermission('read')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($resource->getStorage()->isFallbackStorage()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[] Array of body-tag attributes
|
||||||
|
*/
|
||||||
|
public function getBodyTagAttributes(): array
|
||||||
|
{
|
||||||
|
$resource = $this->linkParts['url'][$this->type->value] ?? null;
|
||||||
|
if (!$resource instanceof ($this->type->getResourceType())) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!$resource->checkActionPermission('read')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if ($resource->getStorage()->isFallbackStorage()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'data-linkbrowser-current-link' => GeneralUtility::makeInstance(LinkService::class)->asString([
|
||||||
|
'type' => $this->type->getLinkServiceType(),
|
||||||
|
$this->type->value => $resource,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createUri(ServerRequestInterface $request, array $parameters = []): string
|
||||||
|
{
|
||||||
|
return (string)$this->uriBuilder->buildUriFromRequest($request, $this->getUrlParameters($parameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSortingModeButtons(ServerRequestInterface $request): ButtonInterface
|
||||||
|
{
|
||||||
|
$sortingButton = $this->componentFactory->createDropDownButton()
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting'))
|
||||||
|
->setIcon($this->iconFactory->getIcon($this->sortDirection->getIconIdentifier()));
|
||||||
|
|
||||||
|
$sortingModeButtons = [];
|
||||||
|
$sortableFields = $this->filelist->getSortableFields();
|
||||||
|
if (count($sortableFields) > 1) {
|
||||||
|
foreach ($sortableFields as $field) {
|
||||||
|
$label = $this->filelist->getFieldLabel($field);
|
||||||
|
|
||||||
|
$sortingModeButtons[] = $this->componentFactory->createDropDownRadio()
|
||||||
|
->setActive($this->sortField === $field)
|
||||||
|
->setHref($this->createUri($request, [
|
||||||
|
'sortField' => $field,
|
||||||
|
'sortDirection' => SortDirection::ASCENDING->value,
|
||||||
|
'currentPage' => 1,
|
||||||
|
]))
|
||||||
|
->setLabel($label);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sortingModeButtons[] = $this->componentFactory->createDropDownDivider();
|
||||||
|
}
|
||||||
|
$defaultSortingDirectionParams = ['sortField' => $this->sortField, 'currentPage' => 1];
|
||||||
|
$sortingModeButtons[] = $this->componentFactory->createDropDownRadio()
|
||||||
|
->setActive($this->sortDirection === SortDirection::ASCENDING)
|
||||||
|
->setHref($this->createUri($request, array_merge($defaultSortingDirectionParams, ['sortDirection' => SortDirection::ASCENDING->value])))
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting.asc'));
|
||||||
|
$sortingModeButtons[] = $this->componentFactory->createDropDownRadio()
|
||||||
|
->setActive($this->sortDirection === SortDirection::DESCENDING)
|
||||||
|
->setHref($this->createUri($request, array_merge($defaultSortingDirectionParams, ['sortDirection' => SortDirection::DESCENDING->value])))
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.sorting.desc'));
|
||||||
|
|
||||||
|
foreach ($sortingModeButtons as $sortingModeButton) {
|
||||||
|
$sortingButton->addItem($sortingModeButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sortingButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getViewModeButton(ServerRequestInterface $request): ButtonInterface
|
||||||
|
{
|
||||||
|
$viewModeItems = [];
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownRadio()
|
||||||
|
->setActive($this->viewMode === ViewMode::TILES)
|
||||||
|
->setHref($this->createUri($request, ['viewMode' => ViewMode::TILES->value]))
|
||||||
|
->setLabel($this->getLanguageService()->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->viewMode === ViewMode::LIST)
|
||||||
|
->setHref($this->createUri($request, ['viewMode' => ViewMode::LIST->value]))
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.list'))
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-viewmode-list'));
|
||||||
|
if (!($this->getBackendUser()->getTSConfig()['options.']['noThumbsInEB'] ?? false)) {
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownDivider();
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownToggle()
|
||||||
|
->setActive($this->displayThumbs)
|
||||||
|
->setHref($this->createUri($request, ['displayThumbs' => $this->displayThumbs ? 0 : 1]))
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view.showThumbnails'))
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-image'));
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
($this->getBackendUser()->getTSConfig()['options.']['file_list.']['displayColumnSelector'] ?? true)
|
||||||
|
&& $this->viewMode === ViewMode::LIST
|
||||||
|
&& ($request->getQueryParams()['act'] ?? '') === 'file'
|
||||||
|
) {
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/backend/column-selector-button.js');
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownDivider();
|
||||||
|
$viewModeItems[] = $this->componentFactory->createDropDownItem()
|
||||||
|
->setTag('typo3-backend-column-selector-button')
|
||||||
|
->setLabel($this->getLanguageService()->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(
|
||||||
|
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:showColumnsSelection'),
|
||||||
|
$this->tcaSchemaFactory->get('sys_file')->getTitle($this->getLanguageService()->sL(...)),
|
||||||
|
),
|
||||||
|
'data-button-ok' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:updateColumnView'),
|
||||||
|
'data-button-close' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel'),
|
||||||
|
'data-error-message' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:updateColumnView.error'),
|
||||||
|
])
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-options'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewModeButton = $this->componentFactory->createDropDownButton()
|
||||||
|
->setLabel($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view'))
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-cog'));
|
||||||
|
foreach ($viewModeItems as $viewModeItem) {
|
||||||
|
$viewModeButton->addItem($viewModeItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $viewModeButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUrlParameters(array $values): array
|
||||||
|
{
|
||||||
|
$values = array_replace_recursive([
|
||||||
|
'expandFolder' => $values['identifier'] ?? $this->expandFolder,
|
||||||
|
], $values);
|
||||||
|
|
||||||
|
return array_merge($this->linkBrowser->getUrlParameters($values), $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLanguageService(): LanguageService
|
||||||
|
{
|
||||||
|
return $this->languageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getBackendUser(): BackendUserAuthentication
|
||||||
|
{
|
||||||
|
return $GLOBALS['BE_USER'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\LinkHandler;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||||
|
use TYPO3\CMS\Backend\View\RecordSearchBoxComponent;
|
||||||
|
use TYPO3\CMS\Backend\View\ResourceUtilityRenderer;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
use TYPO3\CMS\Core\Resource\Search\FileSearchDemand;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFileExtensionMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFileTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFolderTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\LinkType;
|
||||||
|
use TYPO3\CMS\Filelist\Type\Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
#[Autoconfigure(public: true, shared: false)]
|
||||||
|
class FileLinkHandler extends AbstractResourceLinkHandler
|
||||||
|
{
|
||||||
|
protected LinkType $type = LinkType::FILE;
|
||||||
|
|
||||||
|
public function initializeVariables(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initializeVariables($request);
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/filelist/linkbrowser-file-handler.js');
|
||||||
|
|
||||||
|
$this->resourceDisplayMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$this->resourceDisplayMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class));
|
||||||
|
|
||||||
|
// @todo Deprecate "allowedExtensions", see LinkPopup for further information
|
||||||
|
$allowedExtensions = GeneralUtility::trimExplode(',', (string)($this->linkBrowser->getParameters()['params']['allowedExtensions'] ?? ''), true);
|
||||||
|
$allowedFileExtensions = GeneralUtility::trimExplode(',', (string)($this->linkBrowser->getParameters()['params']['allowedFileExtensions'] ?? ''), true);
|
||||||
|
$allowedFileExtensions = array_unique(array_merge($allowedExtensions, $allowedFileExtensions));
|
||||||
|
if (count($allowedFileExtensions) >= 1) {
|
||||||
|
$fileExtensionMatcher = GeneralUtility::makeInstance(ResourceFileExtensionMatcher::class);
|
||||||
|
$fileExtensionMatcher->setExtensions($allowedFileExtensions);
|
||||||
|
} else {
|
||||||
|
$fileExtensionMatcher = GeneralUtility::makeInstance(ResourceFileTypeMatcher::class);
|
||||||
|
}
|
||||||
|
$this->resourceDisplayMatcher->addMatcher($fileExtensionMatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(ServerRequestInterface $request): string
|
||||||
|
{
|
||||||
|
$contentHtml = '';
|
||||||
|
if ($this->selectedFolder !== null) {
|
||||||
|
|
||||||
|
// store the selected folder
|
||||||
|
$backendUser = $this->getBackendUser();
|
||||||
|
$modData = $backendUser->getModuleData('browse_links.php', 'ses');
|
||||||
|
$modData['expandFolder'] = $this->selectedFolder->getCombinedIdentifier();
|
||||||
|
$backendUser->pushModuleData('browse_links.php', $modData);
|
||||||
|
|
||||||
|
// Create the filelist
|
||||||
|
$this->filelist->start(
|
||||||
|
$this->selectedFolder,
|
||||||
|
MathUtility::forceIntegerInRange($this->currentPage, 1, 100000),
|
||||||
|
$this->sortField,
|
||||||
|
$this->sortDirection,
|
||||||
|
Mode::BROWSE
|
||||||
|
);
|
||||||
|
$this->filelist->setResourceDisplayMatcher($this->resourceDisplayMatcher);
|
||||||
|
$this->filelist->setResourceSelectableMatcher($this->resourceSelectableMatcher);
|
||||||
|
|
||||||
|
// 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'] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
$searchWord = trim((string)($request->getParsedBody()['searchTerm'] ?? $request->getQueryParams()['searchTerm'] ?? ''));
|
||||||
|
$searchDemand = $searchWord !== '' ? FileSearchDemand::createForSearchTerm($searchWord)->withFolder($this->selectedFolder)->withRecursive() : null;
|
||||||
|
|
||||||
|
$resource = $this->linkParts['url']['file'] ?? null;
|
||||||
|
if ($resource instanceof ResourceInterface) {
|
||||||
|
$resourceSelectedMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$resourceMatcher = GeneralUtility::makeInstance(ResourceMatcher::class);
|
||||||
|
$resourceMatcher->addResource($resource);
|
||||||
|
$resourceSelectedMatcher->addMatcher($resourceMatcher);
|
||||||
|
$this->filelist->setResourceSelectedMatcher($resourceSelectedMatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
$markup = [];
|
||||||
|
|
||||||
|
// Render the filelist search box
|
||||||
|
$markup[] = '<div class="mb-4">';
|
||||||
|
$markup[] = GeneralUtility::makeInstance(RecordSearchBoxComponent::class)
|
||||||
|
->setSearchWord($searchWord)
|
||||||
|
->render($request, $this->filelist->createModuleUri($this->getUrlParameters([])));
|
||||||
|
$markup[] = '</div>';
|
||||||
|
|
||||||
|
// Render the filelist header bar
|
||||||
|
$markup[] = '<div class="row justify-content-between mb-2">';
|
||||||
|
$markup[] = ' <div class="col-auto"></div>';
|
||||||
|
$markup[] = ' <div class="col-auto">';
|
||||||
|
$markup[] = ' ' . $this->getSortingModeButtons($request);
|
||||||
|
$markup[] = ' ' . $this->getViewModeButton($request);
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = '</div>';
|
||||||
|
|
||||||
|
// Render the filelist
|
||||||
|
$markup[] = $this->filelist->render($searchDemand, $this->view);
|
||||||
|
|
||||||
|
// Render the file upload and folder creation form
|
||||||
|
$resourceUtilityRenderer = GeneralUtility::makeInstance(ResourceUtilityRenderer::class, $this);
|
||||||
|
$markup[] = $resourceUtilityRenderer->uploadForm($request, $this->selectedFolder);
|
||||||
|
$markup[] = $resourceUtilityRenderer->addOnlineMedia($request, $this->selectedFolder);
|
||||||
|
$markup[] = $resourceUtilityRenderer->createFolder($request, $this->selectedFolder);
|
||||||
|
|
||||||
|
$contentHtml = implode(PHP_EOL, $markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->assign('selectedFolder', $this->selectedFolder);
|
||||||
|
$this->view->assign('content', $contentHtml);
|
||||||
|
$this->view->assign('contentOnly', (bool)($request->getQueryParams()['contentOnly'] ?? false));
|
||||||
|
$this->view->assign('treeActions', ($this->type === LinkType::FOLDER) ? ['link'] : []);
|
||||||
|
$this->view->assign('currentIdentifier', !empty($this->linkParts) ? $this->linkParts['url']['file']->getUid() : '');
|
||||||
|
|
||||||
|
return $this->view->render('LinkHandler/File');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\LinkHandler;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||||
|
use TYPO3\CMS\Backend\View\ResourceUtilityRenderer;
|
||||||
|
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\Matcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceFolderTypeMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Matcher\ResourceMatcher;
|
||||||
|
use TYPO3\CMS\Filelist\Type\LinkType;
|
||||||
|
use TYPO3\CMS\Filelist\Type\Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
#[Autoconfigure(public: true, shared: false)]
|
||||||
|
class FolderLinkHandler extends AbstractResourceLinkHandler
|
||||||
|
{
|
||||||
|
protected LinkType $type = LinkType::FOLDER;
|
||||||
|
|
||||||
|
public function initializeVariables(ServerRequestInterface $request): void
|
||||||
|
{
|
||||||
|
parent::initializeVariables($request);
|
||||||
|
$this->pageRenderer->loadJavaScriptModule('@typo3/filelist/linkbrowser-folder-handler.js');
|
||||||
|
|
||||||
|
$this->resourceDisplayMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$this->resourceDisplayMatcher->addMatcher(GeneralUtility::makeInstance(ResourceFolderTypeMatcher::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(ServerRequestInterface $request): string
|
||||||
|
{
|
||||||
|
$contentHtml = '';
|
||||||
|
if ($this->selectedFolder !== null) {
|
||||||
|
// Create the filelist
|
||||||
|
$this->filelist->start(
|
||||||
|
$this->selectedFolder,
|
||||||
|
MathUtility::forceIntegerInRange($this->currentPage, 1, 100000),
|
||||||
|
$this->sortField,
|
||||||
|
$this->sortDirection,
|
||||||
|
Mode::BROWSE
|
||||||
|
);
|
||||||
|
|
||||||
|
$markup = [];
|
||||||
|
|
||||||
|
// Create the filelist header bar
|
||||||
|
$markup[] = '<div class="row justify-content-between mb-2">';
|
||||||
|
$markup[] = ' <div class="col-auto"></div>';
|
||||||
|
$markup[] = ' <div class="col-auto">';
|
||||||
|
$markup[] = ' ' . $this->getSortingModeButtons($request);
|
||||||
|
$markup[] = ' ' . $this->getViewModeButton($request);
|
||||||
|
$markup[] = ' </div>';
|
||||||
|
$markup[] = '</div>';
|
||||||
|
|
||||||
|
$this->filelist->setResourceDisplayMatcher($this->resourceDisplayMatcher);
|
||||||
|
$this->filelist->setResourceSelectableMatcher($this->resourceSelectableMatcher);
|
||||||
|
|
||||||
|
$resource = $this->linkParts['url']['folder'] ?? null;
|
||||||
|
if ($resource instanceof ResourceInterface) {
|
||||||
|
$resourceSelectedMatcher = GeneralUtility::makeInstance(Matcher::class);
|
||||||
|
$resourceMatcher = GeneralUtility::makeInstance(ResourceMatcher::class);
|
||||||
|
$resourceMatcher->addResource($resource);
|
||||||
|
$resourceSelectedMatcher->addMatcher($resourceMatcher);
|
||||||
|
$this->filelist->setResourceSelectedMatcher($resourceSelectedMatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
$markup[] = $this->filelist->render(null, $this->view);
|
||||||
|
|
||||||
|
// Build the file upload and folder creation form
|
||||||
|
$resourceUtilityRenderer = GeneralUtility::makeInstance(ResourceUtilityRenderer::class, $this);
|
||||||
|
$markup[] = $resourceUtilityRenderer->createFolder($request, $this->selectedFolder);
|
||||||
|
|
||||||
|
$contentHtml = implode(PHP_EOL, $markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->assign('selectedFolder', $this->selectedFolder);
|
||||||
|
$this->view->assign('selectedFolderLink', (GeneralUtility::makeInstance(LinkService::class))->asString(['type' => LinkService::TYPE_FOLDER, 'folder' => $this->selectedFolder]));
|
||||||
|
$this->view->assign('content', $contentHtml);
|
||||||
|
$this->view->assign('contentOnly', (bool)($request->getQueryParams()['contentOnly'] ?? false));
|
||||||
|
$this->view->assign('treeActions', ['link']);
|
||||||
|
$this->view->assign('currentIdentifier', !empty($this->linkParts) ? $this->linkParts['url']['folder']->getCombinedIdentifier() : '');
|
||||||
|
|
||||||
|
return $this->view->render('LinkHandler/Folder');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Matcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final readonly class AndMatcher implements MatcherInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var MatcherInterface[]
|
||||||
|
*/
|
||||||
|
private array $matchers;
|
||||||
|
|
||||||
|
public function __construct(MatcherInterface ...$matchers)
|
||||||
|
{
|
||||||
|
$this->matchers = $matchers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports(mixed $item): bool
|
||||||
|
{
|
||||||
|
if ($this->matchers === []) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->matchers as $matcher) {
|
||||||
|
if (!$matcher->supports($item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function match(mixed $item): bool
|
||||||
|
{
|
||||||
|
if ($this->matchers === []) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->matchers as $matcher) {
|
||||||
|
if (!$matcher->match($item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Matcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class Matcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var MatcherInterface[]
|
||||||
|
*/
|
||||||
|
protected array $matchers = [];
|
||||||
|
|
||||||
|
public function addMatcher(MatcherInterface $matcher): self
|
||||||
|
{
|
||||||
|
$this->matchers[] = $matcher;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function match(mixed $item): bool
|
||||||
|
{
|
||||||
|
foreach ($this->matchers as $matcher) {
|
||||||
|
if ($matcher->supports($item) && $matcher->match($item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Matcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface MatcherInterface
|
||||||
|
{
|
||||||
|
public function supports(mixed $item): bool;
|
||||||
|
public function match(mixed $item): bool;
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Matcher;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\Resource\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ResourceFileExtensionMatcher implements MatcherInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected array $extensions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected array $ignoredExtensions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $extensions
|
||||||
|
*/
|
||||||
|
public function setExtensions(array $extensions): self
|
||||||
|
{
|
||||||
|
$this->extensions = array_map(strtolower(...), $extensions);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addExtension(string $extension): self
|
||||||
|
{
|
||||||
|
$this->extensions[] = strtolower($extension);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $ignoredExtensions
|
||||||
|
*/
|
||||||
|
public function setIgnoredExtensions(array $ignoredExtensions): self
|
||||||
|
{
|
||||||
|
$this->ignoredExtensions = array_map(strtolower(...), $ignoredExtensions);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addIgnoredExtension(string $ignoredExtension): self
|
||||||
|
{
|
||||||
|
$this->ignoredExtensions[] = strtolower($ignoredExtension);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports(mixed $item): bool
|
||||||
|
{
|
||||||
|
return $item instanceof File;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function match(mixed $item): bool
|
||||||
|
{
|
||||||
|
if (!$item instanceof File) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($item->getExtension(), $this->ignoredExtensions, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('*', $this->extensions, true) || in_array($item->getExtension(), $this->extensions, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Matcher;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\Resource\File;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ResourceFileTypeMatcher implements MatcherInterface
|
||||||
|
{
|
||||||
|
public function supports(mixed $item): bool
|
||||||
|
{
|
||||||
|
return $item instanceof ResourceInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function match(mixed $item): bool
|
||||||
|
{
|
||||||
|
return $item instanceof File;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Matcher;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ResourceFolderTypeMatcher implements MatcherInterface
|
||||||
|
{
|
||||||
|
public function supports(mixed $item): bool
|
||||||
|
{
|
||||||
|
return $item instanceof ResourceInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function match(mixed $item): bool
|
||||||
|
{
|
||||||
|
return $item instanceof Folder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Matcher;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ResourceMatcher implements MatcherInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ResourceInterface[]
|
||||||
|
*/
|
||||||
|
protected array $resources = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ResourceInterface[] $resources
|
||||||
|
*/
|
||||||
|
public function setResources(array $resources): self
|
||||||
|
{
|
||||||
|
$this->resources = $resources;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addResource(ResourceInterface $resource): self
|
||||||
|
{
|
||||||
|
$this->resources[] = $resource;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports(mixed $item): bool
|
||||||
|
{
|
||||||
|
return $item instanceof ResourceInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function match(mixed $item): bool
|
||||||
|
{
|
||||||
|
return in_array($item, $this->resources);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Pagination;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\Pagination\AbstractPaginator;
|
||||||
|
use TYPO3\CMS\Filelist\Dto\ResourceCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class ResourceCollectionPaginator extends AbstractPaginator
|
||||||
|
{
|
||||||
|
private ResourceCollection $paginatedItems;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ResourceCollection $items,
|
||||||
|
int $currentPageNumber = 1,
|
||||||
|
int $itemsPerPage = 10
|
||||||
|
) {
|
||||||
|
$this->paginatedItems = new ResourceCollection();
|
||||||
|
$this->setCurrentPageNumber($currentPageNumber);
|
||||||
|
$this->setItemsPerPage($itemsPerPage);
|
||||||
|
|
||||||
|
$this->updateInternalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPaginatedItems(): ResourceCollection
|
||||||
|
{
|
||||||
|
return $this->paginatedItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updatePaginatedItems(int $itemsPerPage, int $offset): void
|
||||||
|
{
|
||||||
|
$this->paginatedItems = new ResourceCollection(array_slice($this->items->getResources(), $offset, $itemsPerPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTotalAmountOfItems(): int
|
||||||
|
{
|
||||||
|
return count($this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAmountOfItemsOnCurrentPage(): int
|
||||||
|
{
|
||||||
|
return count($this->paginatedItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Search\LiveSearch;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||||
|
use TYPO3\CMS\Backend\Search\LiveSearch\ResultItem;
|
||||||
|
use TYPO3\CMS\Backend\Search\LiveSearch\ResultItemAction;
|
||||||
|
use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\SearchDemand;
|
||||||
|
use TYPO3\CMS\Backend\Search\LiveSearch\SearchProviderInterface;
|
||||||
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||||
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||||
|
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||||
|
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||||
|
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
|
||||||
|
use TYPO3\CMS\Core\Resource\Exception;
|
||||||
|
use TYPO3\CMS\Core\Resource\File;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||||
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
|
use TYPO3\CMS\Core\Resource\Search\FileSearchDemand;
|
||||||
|
use TYPO3\CMS\Core\Resource\Search\FileSearchQuery;
|
||||||
|
use TYPO3\CMS\Core\Resource\Search\QueryRestrictions\FolderMountsRestriction;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search provider to query files (sys_file + sys_file_metadata) for the
|
||||||
|
* backend live search. Results respect the user's file mounts and are
|
||||||
|
* deduplicated per file.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class FileProvider implements SearchProviderInterface
|
||||||
|
{
|
||||||
|
private LanguageService $languageService;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly IconFactory $iconFactory,
|
||||||
|
private readonly UriBuilder $uriBuilder,
|
||||||
|
private readonly ResourceFactory $resourceFactory,
|
||||||
|
LanguageServiceFactory $languageServiceFactory,
|
||||||
|
) {
|
||||||
|
$this->languageService = $languageServiceFactory->createFromUserPreferences($this->getBackendUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFilterLabel(): string
|
||||||
|
{
|
||||||
|
return $this->languageService->sL('filelist.messages:live_search.file_provider.filter_label');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(SearchDemand $searchDemand): int
|
||||||
|
{
|
||||||
|
$query = FileSearchQuery::createCountForSearchDemand($this->buildFileSearchDemand($searchDemand));
|
||||||
|
$query->additionalRestriction(new FolderMountsRestriction($this->getBackendUser()));
|
||||||
|
return (int)$query->execute()->fetchOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResultItem[]
|
||||||
|
*/
|
||||||
|
public function find(SearchDemand $searchDemand): array
|
||||||
|
{
|
||||||
|
$fileSearchDemand = $this->buildFileSearchDemand($searchDemand)
|
||||||
|
->withStartResult($searchDemand->getOffset())
|
||||||
|
->withMaxResults($searchDemand->getLimit());
|
||||||
|
|
||||||
|
$query = FileSearchQuery::createForSearchDemand($fileSearchDemand);
|
||||||
|
$result = $query->execute();
|
||||||
|
|
||||||
|
$items = [];
|
||||||
|
while ($row = $result->fetchAssociative()) {
|
||||||
|
try {
|
||||||
|
$file = $this->resourceFactory->getFileObject((int)$row['uid'], $row);
|
||||||
|
} catch (Exception) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parentFolder = $file->getParentFolder();
|
||||||
|
$parentFolderIdentifier = $parentFolder->getCombinedIdentifier();
|
||||||
|
} catch (Exception) {
|
||||||
|
// Orphaned sys_file row referring to a folder that no longer exists on disk
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$actions = [];
|
||||||
|
$editAction = $this->buildEditMetadataAction($file);
|
||||||
|
if ($editAction !== null) {
|
||||||
|
$actions['edit'] = $editAction;
|
||||||
|
}
|
||||||
|
$actions['show'] = $this->buildShowInListAction($parentFolderIdentifier, $searchDemand->getQuery());
|
||||||
|
|
||||||
|
$resultItem = (new ResultItem(self::class))
|
||||||
|
->setItemTitle($file->getName())
|
||||||
|
->setTypeLabel($this->languageService->sL('filelist.messages:live_search.file_provider.type_label'))
|
||||||
|
->setIcon($this->iconFactory->getIconForResource($file, IconSize::SMALL))
|
||||||
|
->setThumbnailUrl($this->buildThumbnailUrl($file))
|
||||||
|
->setActions(...array_values($actions))
|
||||||
|
->setDefaultAction($actions['edit'] ?? $actions['show'])
|
||||||
|
->setExtraData([
|
||||||
|
'breadcrumb' => $this->buildBreadcrumb($parentFolder),
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($this->buildProperties($file, $parentFolder) as $label => $value) {
|
||||||
|
$resultItem->addProperty($label, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$items[] = $resultItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFileSearchDemand(SearchDemand $searchDemand): FileSearchDemand
|
||||||
|
{
|
||||||
|
return FileSearchDemand::createForSearchTerm($searchDemand->getQuery())->withRecursive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildEditMetadataAction(File $file): ?ResultItemAction
|
||||||
|
{
|
||||||
|
if (!$file->isIndexed() || !$file->checkActionPermission('editMeta')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$metaDataUid = $file->getMetaData()->offsetGet('uid');
|
||||||
|
if (!$metaDataUid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
|
||||||
|
'edit' => ['sys_file_metadata' => [$metaDataUid => 'edit']],
|
||||||
|
'module' => 'media_management',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (new ResultItemAction('edit'))
|
||||||
|
->setLabel($this->languageService->sL('core.core:cm.editMetadata'))
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-open', IconSize::SMALL))
|
||||||
|
->setUrl($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildShowInListAction(string $parentFolderIdentifier, string $query): ResultItemAction
|
||||||
|
{
|
||||||
|
$url = (string)$this->uriBuilder->buildUriFromRoute('media_management', [
|
||||||
|
'id' => $parentFolderIdentifier,
|
||||||
|
'searchTerm' => $query,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (new ResultItemAction('show'))
|
||||||
|
->setLabel($this->languageService->sL('filelist.messages:live_search.file_provider.show_in_list'))
|
||||||
|
->setIcon($this->iconFactory->getIcon('actions-list', IconSize::SMALL))
|
||||||
|
->setUrl($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildBreadcrumb(Folder $parentFolder): string
|
||||||
|
{
|
||||||
|
return $parentFolder->getStorage()->getName() . $parentFolder->getReadablePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
private function buildProperties(File $file, Folder $parentFolder): array
|
||||||
|
{
|
||||||
|
$items = [
|
||||||
|
$this->languageService->sL('filelist.messages:live_search.file_provider.property.location') => $this->buildBreadcrumb($parentFolder),
|
||||||
|
$this->languageService->sL('filelist.messages:live_search.file_provider.property.size') => GeneralUtility::formatSize(
|
||||||
|
(int)$file->getSize(),
|
||||||
|
$this->languageService->sL('core.common:byteSizeUnits')
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if ($file->getModificationTime() > 0) {
|
||||||
|
$items[$this->languageService->sL('filelist.messages:live_search.file_provider.property.modified')] = BackendUtility::datetime($file->getModificationTime());
|
||||||
|
}
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildThumbnailUrl(File $file): ?string
|
||||||
|
{
|
||||||
|
if (!$file->isImage() && !$file->isMediaFile()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$processedFile = $file->process(
|
||||||
|
ProcessedFile::CONTEXT_IMAGECROPSCALEMASK,
|
||||||
|
['maxWidth' => 166, 'maxHeight' => 115]
|
||||||
|
);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $processedFile->getPublicUrl() ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBackendUser(): BackendUserAuthentication
|
||||||
|
{
|
||||||
|
return $GLOBALS['BE_USER'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Type;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||||
|
use TYPO3\CMS\Core\Resource\File;
|
||||||
|
use TYPO3\CMS\Core\Resource\Folder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
enum LinkType: string
|
||||||
|
{
|
||||||
|
case FILE = 'file';
|
||||||
|
case FOLDER = 'folder';
|
||||||
|
|
||||||
|
public function getResourceType(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
LinkType::FILE => File::class,
|
||||||
|
LinkType::FOLDER => Folder::class,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLinkServiceType(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
LinkType::FILE => LinkService::TYPE_FILE,
|
||||||
|
LinkType::FOLDER => LinkService::TYPE_FOLDER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
enum Mode: string
|
||||||
|
{
|
||||||
|
case MANAGE = 'manage';
|
||||||
|
case BROWSE = 'browse';
|
||||||
|
|
||||||
|
public function fieldArray(): array
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
Mode::MANAGE => ['_SELECTOR_', 'icon', 'name', '_CONTROL_', 'record_type', 'size', 'rw', '_REF_'],
|
||||||
|
Mode::BROWSE => ['_SELECTOR_', 'icon', 'name', '_CONTROL_', 'record_type', 'size'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
enum SortDirection: string
|
||||||
|
{
|
||||||
|
case ASCENDING = 'asc';
|
||||||
|
case DESCENDING = 'desc';
|
||||||
|
|
||||||
|
public function getIconIdentifier(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::ASCENDING => 'actions-sort-amount-up',
|
||||||
|
self::DESCENDING => 'actions-sort-amount-down',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TYPO3\CMS\Filelist\Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
enum ViewMode: string
|
||||||
|
{
|
||||||
|
case LIST = 'list';
|
||||||
|
case TILES = 'tiles';
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use TYPO3\CMS\Filelist\Controller\FileListController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definitions for modules provided by EXT:filelist
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'media_management' => [
|
||||||
|
'parent' => 'media',
|
||||||
|
'access' => 'user',
|
||||||
|
'path' => '/module/file/list',
|
||||||
|
'iconIdentifier' => 'module-file',
|
||||||
|
'labels' => 'filelist.module',
|
||||||
|
'aliases' => ['file_FilelistList'],
|
||||||
|
'routes' => [
|
||||||
|
'_default' => [
|
||||||
|
'target' => FileListController::class . '::handleRequest',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'moduleData' => [
|
||||||
|
'displayThumbs' => true,
|
||||||
|
'clipBoard' => true,
|
||||||
|
'sortField' => 'name',
|
||||||
|
'sortDirection' => \TYPO3\CMS\Filelist\Type\SortDirection::ASCENDING->value,
|
||||||
|
'viewMode' => null,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definitions for routes provided by EXT:backend
|
||||||
|
* Contains all "regular" routes for entry points
|
||||||
|
*
|
||||||
|
* Please note that this setup is preliminary until all core use-cases are set up here.
|
||||||
|
* Especially some more properties regarding modules will be added until TYPO3 CMS 7 LTS, and might change.
|
||||||
|
*
|
||||||
|
* Currently the "access" property is only used so no token creation + validation is made,
|
||||||
|
* but will be extended further.
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
|
||||||
|
// Editing the contents of a file
|
||||||
|
'file_edit' => [
|
||||||
|
'path' => '/file/editcontent',
|
||||||
|
'target' => \TYPO3\CMS\Filelist\Controller\File\EditFileController::class . '::mainAction',
|
||||||
|
],
|
||||||
|
|
||||||
|
'file_download' => [
|
||||||
|
'path' => '/file/download',
|
||||||
|
'methods' => ['POST'],
|
||||||
|
'target' => \TYPO3\CMS\Filelist\Controller\FileDownloadController::class . '::handleRequest',
|
||||||
|
],
|
||||||
|
|
||||||
|
'file_update_online_media' => [
|
||||||
|
'path' => '/file/update-online-media',
|
||||||
|
'methods' => ['POST'],
|
||||||
|
'target' => \TYPO3\CMS\Filelist\Controller\FileUpdateOnlineMediaController::class . '::handleRequest',
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'dependencies' => [
|
||||||
|
'backend',
|
||||||
|
'core',
|
||||||
|
],
|
||||||
|
'imports' => [
|
||||||
|
'@typo3/filelist/' => 'EXT:filelist/Resources/Public/JavaScript/',
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
services:
|
||||||
|
_defaults:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
public: false
|
||||||
|
|
||||||
|
TYPO3\CMS\Filelist\:
|
||||||
|
resource: '../Classes/*'
|
||||||
|
|
||||||
|
TYPO3\CMS\Filelist\ElementBrowser\FileBrowser:
|
||||||
|
shared: false
|
||||||
|
|
||||||
|
TYPO3\CMS\Filelist\ElementBrowser\FolderBrowser:
|
||||||
|
shared: false
|
||||||
|
|
||||||
|
TYPO3\CMS\Filelist\Search\LiveSearch\FileProvider:
|
||||||
|
tags:
|
||||||
|
- { name: 'livesearch.provider', priority: 40 }
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Register link handlers
|
||||||
|
TCEMAIN.linkHandler {
|
||||||
|
file {
|
||||||
|
handler = TYPO3\CMS\Filelist\LinkHandler\FileLinkHandler
|
||||||
|
label = LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:file
|
||||||
|
displayAfter = page
|
||||||
|
scanAfter = page
|
||||||
|
}
|
||||||
|
folder {
|
||||||
|
handler = TYPO3\CMS\Filelist\LinkHandler\FolderLinkHandler
|
||||||
|
label = LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:folder
|
||||||
|
displayAfter = page,file
|
||||||
|
scanAfter = page,file
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
options.file_list {
|
||||||
|
enableDisplayThumbnails = selectable
|
||||||
|
enableClipBoard = selectable
|
||||||
|
thumbnail {
|
||||||
|
width = 64
|
||||||
|
height = 64
|
||||||
|
}
|
||||||
|
}
|
||||||
+339
@@ -0,0 +1,339 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
============================
|
||||||
|
TYPO3 extension ``filelist``
|
||||||
|
============================
|
||||||
|
|
||||||
|
This TYPO3 backend module "Media" is used for managing files.
|
||||||
|
|
||||||
|
It makes files in the defined storages available in the backend (upload, delete,
|
||||||
|
copy etc.). The default storage is fileadmin/.
|
||||||
|
|
||||||
|
:Repository: https://github.com/typo3/typo3
|
||||||
|
:Issues: https://forge.typo3.org/
|
||||||
|
:Read online: https://docs.typo3.org/
|
||||||
|
:Packagist: https://packagist.org/packages/typo3/cms-filelist
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
|
<file source-language="en" datatype="plaintext" original="EXT:filelist/Resources/Private/Language/locallang.xlf" date="2015-08-21T11:45:35Z" product-name="filelist">
|
||||||
|
<header/>
|
||||||
|
<body>
|
||||||
|
<trans-unit id="search">
|
||||||
|
<source>Search "%s" in %s</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="live_search.file_provider.filter_label">
|
||||||
|
<source>Files</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="live_search.file_provider.type_label">
|
||||||
|
<source>File</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="live_search.file_provider.show_in_list">
|
||||||
|
<source>Show in file list</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="live_search.file_provider.property.location">
|
||||||
|
<source>Location</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="live_search.file_provider.property.size">
|
||||||
|
<source>Size</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="live_search.file_provider.property.modified">
|
||||||
|
<source>Last modified</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="search.reset">
|
||||||
|
<source>Reset search</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="flashmessage.no_results">
|
||||||
|
<source>No results found</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="flashmessage.no_results.message">
|
||||||
|
<source>This folder does not contain any files for "%s"</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="flashmessage.no_items">
|
||||||
|
<source>This folder is empty</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="flashmessage.no_items.message">
|
||||||
|
<source>Drag files here to upload them</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_upload.php.pagetitle">
|
||||||
|
<source>Upload files</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_upload.php.submit">
|
||||||
|
<source>Upload files</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_upload.php.files">
|
||||||
|
<source>files</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.exists.title">
|
||||||
|
<source>File exists already</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.exists.description">
|
||||||
|
<source>You want to rename the file "{0}" to "{1}", but the file "{1}" already exists. How do you want to proceed?</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.actions.cancel">
|
||||||
|
<source>Cancel</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.actions.rename">
|
||||||
|
<source>Rename with unique name</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.actions.override">
|
||||||
|
<source>Overwrite</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.php.pagetitle">
|
||||||
|
<source>Rename</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.php.submit">
|
||||||
|
<source>Rename</source>
|
||||||
|
<note from="developer">This label is not used since TYPO3 v9.</note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.php.label.target.file">
|
||||||
|
<source>New file name</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_rename.php.label.target.folder">
|
||||||
|
<source>New folder name</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_replace.title">
|
||||||
|
<source>Replace file "%s"</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_replace.intro">
|
||||||
|
<source>You're about to replace the file "%s".</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_replace.new_file.label">
|
||||||
|
<source>Select new file</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_replace.keepFilename.label">
|
||||||
|
<source>Keep current filename "%s"?</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_replace.button.replace">
|
||||||
|
<source>Replace</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_edit.php.pagetitle">
|
||||||
|
<source>Edit</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_edit.php.submit">
|
||||||
|
<source>Save</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_edit.php.saveAndClose">
|
||||||
|
<source>Save and Close</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_edit.php.coundNot">
|
||||||
|
<source>This filetype cannot be edited.<br />The file must have an extension like:<br /><br
|
||||||
|
/> <b>%s</b>
|
||||||
|
</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_newfolder.php.pagetitle">
|
||||||
|
<source>New file or folder</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_newfolder.php.label_newfolder">
|
||||||
|
<source>Folder</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_newfolder.php.submit">
|
||||||
|
<source>Create folders</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_newfolder.php.folders">
|
||||||
|
<source>folders</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_newfolder.php.newfile_submit">
|
||||||
|
<source>Create file</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_newfolder.php.newfile">
|
||||||
|
<source>Create new textfile</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_newfolder.php.label_newfile">
|
||||||
|
<source>File name</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="file_newfolder.php.number_of_folders">
|
||||||
|
<source>Number of folders</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="download">
|
||||||
|
<source>Download</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="actions.new_folder">
|
||||||
|
<source>New Folder</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="actions.new_file">
|
||||||
|
<source>New File</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="translations">
|
||||||
|
<source>Translations</source>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
|
<file source-language="en" datatype="plaintext" original="EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf" date="2011-10-17T20:22:34Z" product-name="lang">
|
||||||
|
<header/>
|
||||||
|
<body>
|
||||||
|
<trans-unit id="clipBoard">
|
||||||
|
<source>Show clipboard</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="clip_paste">
|
||||||
|
<source>Paste in clipboard content</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="clip_pasteInto">
|
||||||
|
<source>Paste into: Clipboard content is inserted into this folder</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="editMarked">
|
||||||
|
<source>Edit Metadata</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="editColumns">
|
||||||
|
<source>Edit specific Metadata</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="deleteMarked">
|
||||||
|
<source>Delete marked items</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="clip_selectMarked">
|
||||||
|
<source>Transfer to clipboard</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="clip_deleteMarked">
|
||||||
|
<source>Remove from clipboard</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="deleteMarkedWarning">
|
||||||
|
<source>Are you sure you want to delete all marked items from this folder?</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="c_name">
|
||||||
|
<source>Name</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="c_size">
|
||||||
|
<source>Size</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="c_record_type">
|
||||||
|
<source>Type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="c_rw">
|
||||||
|
<source>RW</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="files">
|
||||||
|
<source>files</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="temp">
|
||||||
|
<source>Temporary files</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="read">
|
||||||
|
<source>R</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="recycler">
|
||||||
|
<source>Recycler</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="write">
|
||||||
|
<source>W</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="storageNotBrowsableTitle">
|
||||||
|
<source>Access denied.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="storageNotBrowsableMessage">
|
||||||
|
<source>You are trying to access a folder in a storage that is not browsable.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="missingFolderPermissionsTitle">
|
||||||
|
<source>Missing folder permissions</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="missingFolderPermissionsMessage">
|
||||||
|
<source>You have no access to the folder "%s".</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="localStorageOfflineTitle">
|
||||||
|
<source>Base folder for local storage missing or not allowed</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="localStorageOfflineMessage">
|
||||||
|
<source>Verify that the base folder for the storage "%s" exists and is allowed to be accessed.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="folderNotFoundTitle">
|
||||||
|
<source>Folder not found.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="folderNotFoundMessage">
|
||||||
|
<source>The folder "%s" cannot be accessed. Trying to use parent folder(s).</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="translateMetadata">
|
||||||
|
<source>Translate metadata</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="createMetadataForLanguage">
|
||||||
|
<source>Create metadata of this file for %s</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="editMetadataForLanguage">
|
||||||
|
<source>Edit metadata of this file for %s</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="paramError">
|
||||||
|
<source>Parameter Error</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="targetNoDir">
|
||||||
|
<source>Target was not a directory!</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="reloadMetadata">
|
||||||
|
<source>Reload Metadata</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="rangeIndicator">
|
||||||
|
<source>Media %1$d - %2$d</source>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
|
<file source-language="en" datatype="plaintext" original="EXT:filelist/Resources/Private/Language/locallang_transfer_handler.xlf" date="2023-02-24T00:00:00Z" product-name="cms">
|
||||||
|
<header/>
|
||||||
|
<body>
|
||||||
|
<trans-unit id="message.transfer_resource.title">
|
||||||
|
<source>Transfer Resource</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="message.transfer_resource.text">
|
||||||
|
<source>Transfer "%s" to "%s"?</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="message.transfer_resources.title">
|
||||||
|
<source>Transfer Resources</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="message.transfer_resources.text">
|
||||||
|
<source>Transfer %d resources to "%s"?</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="message.button.cancel">
|
||||||
|
<source>Cancel</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="message.button.copy">
|
||||||
|
<source>Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="message.button.move">
|
||||||
|
<source>Move</source>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||||
|
<file source-language="en" datatype="plaintext" original="EXT:filelist/Resources/Private/Language/module.xlf" date="2026-11-10T13:37:37Z" product-name="filelist">
|
||||||
|
<header/>
|
||||||
|
<body>
|
||||||
|
<trans-unit id="short_description">
|
||||||
|
<source>Listing of media resources in registered storages</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="description">
|
||||||
|
<source>This is the media administration system. Through this module you can upload, copy, move and delete files on the system.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="title">
|
||||||
|
<source>Media</source>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<html
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
<f:if condition="{paginator.numberOfPages} > 1">
|
||||||
|
<nav aria-labelledby="filelist-pagination">
|
||||||
|
<ul class="pagination mb-0">
|
||||||
|
<li class="page-item">
|
||||||
|
<span id="filelist-pagination" class="page-link">
|
||||||
|
<f:translate key="rangeIndicator" domain="filelist.mod_file_list" arguments="{0: firstElement, 1: lastElement}" />
|
||||||
|
<span class="visually-hidden">, <f:translate key="pageIndicator" domain="core.mod_web_list" arguments="{0: currentPage, 1: totalPages}"/></span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<f:comment><!--First page and previous page--></f:comment>
|
||||||
|
<f:if condition="{currentPage} > 1">
|
||||||
|
<f:then>
|
||||||
|
<li class="page-item"><a class="page-link" href="{currentUrl}¤tPage=1" aria-label="{f:translate(key: 'first', domain: 'core.common')}" title="{f:translate(key: 'first', domain: 'core.common')}"><core:icon identifier="actions-view-paging-first"/></a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="{currentUrl}¤tPage={currentPage -1}" aria-label="{f:translate(key: 'previous', domain: 'core.common')}" title="{f:translate(key: 'previous', domain: 'core.common')}"><core:icon identifier="actions-view-paging-previous" /></a></li>
|
||||||
|
</f:then>
|
||||||
|
<f:else>
|
||||||
|
<li class="page-item disabled" aria-hidden="true"><span class="page-link"><core:icon identifier="actions-view-paging-first" /></span></li>
|
||||||
|
<li class="page-item disabled" aria-hidden="true"><span class="page-link"><core:icon identifier="actions-view-paging-previous" /></span></li>
|
||||||
|
</f:else>
|
||||||
|
</f:if>
|
||||||
|
<f:variable name="pageInput">
|
||||||
|
<input type="number" autocomplete="off" name="currentPage" min="1" max="{totalPages}" value="{currentPage}" size="3" class="t3js-filelist-paging form-control form-control-sm paginator-input" />
|
||||||
|
<span aria-hidden="true">
|
||||||
|
</f:variable>
|
||||||
|
<li class="page-item">
|
||||||
|
<span class="page-link">
|
||||||
|
<f:format.raw><f:translate key="pageIndicator" domain="core.mod_web_list" arguments="{0: pageInput, 1: totalPages}"/></f:format.raw></span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<f:comment><!--Next page and last page--></f:comment>
|
||||||
|
<f:if condition="{currentPage} < {totalPages}">
|
||||||
|
<f:then>
|
||||||
|
<li class="page-item"><a class="page-link" href="{currentUrl}¤tPage={currentPage + 1}" aria-label="{f:translate(key: 'next', domain: 'core.common')}" title="{f:translate(key: 'next', domain: 'core.common')}"><core:icon identifier="actions-view-paging-next" /></a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="{currentUrl}¤tPage={totalPages}" aria-label="{f:translate(key: 'last', domain: 'core.common')}" title="{f:translate(key: 'last', domain: 'core.common')}"><core:icon identifier="actions-view-paging-last" /></a></li>
|
||||||
|
</f:then>
|
||||||
|
<f:else>
|
||||||
|
<li class="page-item disabled" aria-hidden="true"><span class="page-link"><core:icon identifier="actions-view-paging-next" /></span></li>
|
||||||
|
<li class="page-item disabled" aria-hidden="true"><span class="page-link"><core:icon identifier="actions-view-paging-last" /></span></li>
|
||||||
|
</f:else>
|
||||||
|
</f:if>
|
||||||
|
<li class="page-item"><a class="page-link" href="{currentUrl}¤tPage={currentPage}" aria-label="{f:translate(key: 'reload', domain: 'core.common')}" title="{f:translate(key: 'reload', domain: 'core.common')}"><core:icon identifier="actions-refresh" /></a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</f:if>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<html
|
||||||
|
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<f:layout name="ElementBrowserWithNavigation" />
|
||||||
|
|
||||||
|
<f:section name="Navigation">
|
||||||
|
<typo3-backend-component-filestorage-browser
|
||||||
|
active-folder="{f:if(condition: selectedFolder, then: selectedFolder.combinedIdentifier, else: '') -> f:format.htmlentities()}"
|
||||||
|
>
|
||||||
|
</typo3-backend-component-filestorage-browser>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
<f:section name="Content">
|
||||||
|
<f:if condition="{selectedFolder}">
|
||||||
|
<h4 class="text-truncate mb-4">
|
||||||
|
<core:iconForResource resource="{selectedFolder}" />
|
||||||
|
{selectedFolder.storage.name}: {selectedFolder.identifier}
|
||||||
|
</h4>
|
||||||
|
</f:if>
|
||||||
|
<f:flashMessages queueIdentifier="core.template.flashMessages"/>
|
||||||
|
<f:format.raw>{content}</f:format.raw>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<html
|
||||||
|
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<f:layout name="ElementBrowserWithNavigation" />
|
||||||
|
|
||||||
|
<f:section name="Navigation">
|
||||||
|
<typo3-backend-component-filestorage-browser
|
||||||
|
active-folder="{f:if(condition: selectedFolder, then: selectedFolder.combinedIdentifier, else: '') -> f:format.htmlentities()}"
|
||||||
|
>
|
||||||
|
</typo3-backend-component-filestorage-browser>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
<f:section name="Content">
|
||||||
|
<f:if condition="{selectedFolder}">
|
||||||
|
<h4 class="text-truncate mb-4">
|
||||||
|
<core:iconForResource resource="{selectedFolder}" />
|
||||||
|
{selectedFolder.storage.name}: {selectedFolder.identifier}
|
||||||
|
</h4>
|
||||||
|
</f:if>
|
||||||
|
<f:flashMessages queueIdentifier="core.template.flashMessages"/>
|
||||||
|
|
||||||
|
<f:if condition="{selectedFolder}">
|
||||||
|
<div
|
||||||
|
data-filelist-element="true"
|
||||||
|
data-filelist-type="folder"
|
||||||
|
data-filelist-identifier="{selectedFolder.combinedIdentifier}"
|
||||||
|
>
|
||||||
|
<button title="{selectedFolder.storage.name}" class="btn btn-default" data-filelist-action="primary">
|
||||||
|
<core:iconForResource resource="{selectedFolder}" />
|
||||||
|
<f:variable name="folderLabel" value="{f:if(condition: '{selectedFolder.name} !== \'\'', then: '{selectedFolder.name}', else: '{selectedFolder.storage.name}')}" />
|
||||||
|
<f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:linkTo" arguments="{0: '/{folderLabel}'}">{selectedFolder.storage.name}: {selectedFolder.identifier}</f:translate>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</f:if>
|
||||||
|
|
||||||
|
<f:format.raw>{content}</f:format.raw>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<html
|
||||||
|
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<f:layout name="ElementBrowserWithNavigation" />
|
||||||
|
|
||||||
|
<f:section name="Navigation">
|
||||||
|
<typo3-backend-component-filestorage-browser
|
||||||
|
active-folder="{f:if(condition: selectedFolder, then: selectedFolder.combinedIdentifier, else: '') -> f:format.htmlentities()}"
|
||||||
|
>
|
||||||
|
</typo3-backend-component-filestorage-browser>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
<f:section name="Content">
|
||||||
|
<f:if condition="{dragUploader}">
|
||||||
|
<div class="t3js-drag-uploader" data-target-folder="{selectedFolder.combinedIdentifier}" data-progress-container="#typo3-filelist"
|
||||||
|
data-dropzone-trigger=".t3js-drag-uploader-trigger" data-dropzone-target=".element-browser-body"
|
||||||
|
data-file-deny-pattern="{dragUploader.fileDenyPattern}" data-max-file-size="{dragUploader.maxFileSize}"
|
||||||
|
data-default-action="{dragUploader.defaultDuplicationBehaviourAction}" data-manual-table
|
||||||
|
></div>
|
||||||
|
</f:if>
|
||||||
|
<f:if condition="{selectedFolder}">
|
||||||
|
<h4 class="text-truncate mb-4">
|
||||||
|
<core:iconForResource resource="{selectedFolder}" />
|
||||||
|
{selectedFolder.storage.name}: {selectedFolder.identifier}
|
||||||
|
</h4>
|
||||||
|
</f:if>
|
||||||
|
<f:flashMessages queueIdentifier="core.template.flashMessages"/>
|
||||||
|
<f:format.raw>{content}</f:format.raw>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<html
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<f:layout name="Module" />
|
||||||
|
|
||||||
|
<f:section name="Content">
|
||||||
|
|
||||||
|
<form action="{moduleUrlTceFile}" method="post" id="EditFileController" name="editform">
|
||||||
|
<h1><f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:file_edit.php.pagetitle" /> {fileName}</h1>
|
||||||
|
<f:format.raw>{hookContent}</f:format.raw>
|
||||||
|
<f:format.raw>{form}</f:format.raw>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<html
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<f:layout name="Module" />
|
||||||
|
|
||||||
|
<f:section name="Content">
|
||||||
|
|
||||||
|
<f:comment><!-- identifier initializes tree state --></f:comment>
|
||||||
|
<div class="filelist-main" data-filelist-current-identifier="{currentIdentifier -> f:format.htmlspecialchars()}">
|
||||||
|
<f:if condition="{folderIdentifier}">
|
||||||
|
<f:render section="headline" arguments="{_all}" />
|
||||||
|
<f:render section="content" arguments="{_all}" />
|
||||||
|
</f:if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<f:if condition="{dragUploader}">
|
||||||
|
<div class="t3js-drag-uploader" data-target-folder="{folderIdentifier}" data-progress-container="#typo3-filelist"
|
||||||
|
data-dropzone-trigger=".t3js-drag-uploader-trigger" data-dropzone-target=".t3js-module-body h1:first-child"
|
||||||
|
data-file-deny-pattern="{dragUploader.fileDenyPattern}" data-max-file-size="{dragUploader.maxFileSize}"
|
||||||
|
data-default-action="{dragUploader.defaultDuplicationBehaviourAction}" data-reload-url="{listUrl}"
|
||||||
|
></div>
|
||||||
|
</f:if>
|
||||||
|
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
|
||||||
|
<f:section name="headline">
|
||||||
|
<h1>
|
||||||
|
<f:if condition="{searchTerm}">
|
||||||
|
<f:then>
|
||||||
|
<f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:search" arguments="{0: searchTerm, 1: headline}" />
|
||||||
|
</f:then>
|
||||||
|
<f:else>
|
||||||
|
{headline}
|
||||||
|
</f:else>
|
||||||
|
</f:if>
|
||||||
|
</h1>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
<f:section name="content">
|
||||||
|
<form method="post" name="fileListForm" action="{listUrl}">
|
||||||
|
<input type="hidden" name="cmd"/>
|
||||||
|
<f:if condition="{searchTerm} || {totalItems}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
autocomplete="off"
|
||||||
|
id="filelist-searchterm"
|
||||||
|
name="searchTerm"
|
||||||
|
class="form-control"
|
||||||
|
value="{searchTerm}"
|
||||||
|
placeholder="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')}"
|
||||||
|
/>
|
||||||
|
<label for="filelist-searchterm" class="visually-hidden">
|
||||||
|
<f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.searchString"/>
|
||||||
|
</label>
|
||||||
|
<input type="hidden" name="currentPage" value="0" />
|
||||||
|
<button type="submit" class="btn btn-default" name="search">
|
||||||
|
<f:translate id="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f:if>
|
||||||
|
<f:if condition="!{totalItems}">
|
||||||
|
<div class="t3-filelist-info-container">
|
||||||
|
<f:if condition="{searchTerm}">
|
||||||
|
<f:then>
|
||||||
|
<core:icon identifier="actions-question" size="large" />
|
||||||
|
<h5 class="mt-2">
|
||||||
|
{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:flashmessage.no_results')}
|
||||||
|
</h5>
|
||||||
|
<p>
|
||||||
|
<f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:flashmessage.no_results.message" arguments="{0: searchTerm}"/>
|
||||||
|
</p>
|
||||||
|
<button type="submit" class="btn btn-info" name="searchTerm" value="">
|
||||||
|
<f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:search.reset" />
|
||||||
|
</button>
|
||||||
|
</f:then>
|
||||||
|
<f:else>
|
||||||
|
<core:icon identifier="apps-pagetree-folder-contains" size="large" />
|
||||||
|
<h5 class="mt-2">
|
||||||
|
{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:flashmessage.no_items')}
|
||||||
|
</h5>
|
||||||
|
<p>
|
||||||
|
<f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:flashmessage.no_items.message" arguments="{0: searchTerm}"/>
|
||||||
|
</p>
|
||||||
|
</f:else>
|
||||||
|
</f:if>
|
||||||
|
</div>
|
||||||
|
</f:if>
|
||||||
|
<div class="t3-filelist-container {f:if(condition: '!{totalItems}', then: 'hidden')}">
|
||||||
|
<f:if condition="{listHtml}">
|
||||||
|
<div class="multi-record-selection-actions-wrapper">
|
||||||
|
<div class="t3js-multi-record-selection-actions row row-cols-auto gx-2 align-items-center hidden">
|
||||||
|
<div class="col">
|
||||||
|
<strong><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selection"/></strong>
|
||||||
|
</div>
|
||||||
|
<f:if condition="{editActionConfiguration}">
|
||||||
|
<div class="col">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default btn-sm" disabled="disabled" data-multi-record-selection-action="edit" data-multi-record-selection-action-config="{editActionConfiguration -> f:format.raw()}">
|
||||||
|
<span title="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:editMarked')}">
|
||||||
|
<core:icon identifier="actions-open" size="small" /> <f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:editMarked" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<f:if condition="{editColumnsActionConfiguration}">
|
||||||
|
<button type="button" class="btn btn-default btn-sm" disabled="disabled" data-multi-record-selection-action="edit" data-multi-record-selection-action-config="{editColumnsActionConfiguration -> f:format.raw()}">
|
||||||
|
<span title="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:editColumns')}">
|
||||||
|
<core:icon identifier="actions-open" size="small" /> <f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:editColumns" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</f:if>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f:if>
|
||||||
|
<f:if condition="{downloadActionConfiguration}">
|
||||||
|
<div class="col">
|
||||||
|
<button type="button" class="btn btn-default btn-sm" data-multi-record-selection-action="download" data-multi-record-selection-action-config="{downloadActionConfiguration -> f:format.raw()}">
|
||||||
|
<span title="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:download')}">
|
||||||
|
<core:icon identifier="actions-download" size="small" /> <f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:download" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</f:if>
|
||||||
|
<f:if condition="{showClipboardPanel} && {enableClipBoard.mode} != 'normal'">
|
||||||
|
<div class="col">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default btn-sm" {f:if(condition: '{enableClipBoard.mode} == normal', then: 'disabled')} data-multi-record-selection-action="copyMarked">
|
||||||
|
<span title="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_selectMarked')}">
|
||||||
|
<core:icon identifier="actions-edit-copy" size="small" /> <f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_selectMarked" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-sm" {f:if(condition: '{enableClipBoard.mode} == normal', then: 'disabled')} data-multi-record-selection-action="removeMarked">
|
||||||
|
<span title="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_deleteMarked')}">
|
||||||
|
<core:icon identifier="actions-minus" size="small" /> <f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_deleteMarked" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f:if>
|
||||||
|
<div class="col">
|
||||||
|
<button type="button" class="btn btn-default btn-sm" data-multi-record-selection-action="delete" data-multi-record-selection-action-config="{deleteActionConfiguration -> f:format.raw()}">
|
||||||
|
<span title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete')}">
|
||||||
|
<core:icon identifier="actions-edit-delete" size="small" /> <f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f:if>
|
||||||
|
{listHtml -> f:format.raw() -> f:if(condition: totalItems)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<f:if condition="{listHtml}">
|
||||||
|
<f:if condition="{showClipboardPanel}">
|
||||||
|
<hr class="spacer">
|
||||||
|
<typo3-backend-clipboard-panel return-url="{listUrl}" table="_FILE"></typo3-backend-clipboard-panel>
|
||||||
|
</f:if>
|
||||||
|
</f:if>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<html
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="table-fit">
|
||||||
|
<table class="table table-striped table-hover" id="typo3-filelist" data-mode="{mode}">
|
||||||
|
<thead>
|
||||||
|
{tableHeader -> f:format.raw()}
|
||||||
|
</thead>
|
||||||
|
<tbody data-multi-record-selection-row-selection="true">
|
||||||
|
{tableBody -> f:format.raw()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<f:render partial="Pagination" arguments="{
|
||||||
|
paginator:paginator,
|
||||||
|
pagination:pagination,
|
||||||
|
currentPage:currentPage,
|
||||||
|
totalPages:totalPages,
|
||||||
|
currentUrl:currentUrl,
|
||||||
|
firstElement:firstElement,
|
||||||
|
lastElement:lastElement
|
||||||
|
}" />
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<html
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
<div class="resource-tiles-container">
|
||||||
|
<div class="resource-tiles my-3">
|
||||||
|
<f:for each="{resources}" as="resource">
|
||||||
|
<f:render section="ResourceTile" arguments="{resource: resource, displayThumbs: displayThumbs, displayCheckbox: displayCheckbox, defaultLanguageAccess: defaultLanguageAccess}" />
|
||||||
|
</f:for>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<f:render partial="Pagination" arguments="{
|
||||||
|
paginator:paginator,
|
||||||
|
pagination:pagination,
|
||||||
|
currentPage:currentPage,
|
||||||
|
totalPages:totalPages,
|
||||||
|
currentUrl:currentUrl,
|
||||||
|
firstElement:firstElement,
|
||||||
|
lastElement:lastElement
|
||||||
|
}" />
|
||||||
|
|
||||||
|
<f:section name="ResourceTile">
|
||||||
|
<div
|
||||||
|
class="resource-tile{f:if(condition: resource.isSelected, then: ' selected')}"
|
||||||
|
aria-labelledby="resource-tile-label-{resource.uid}"
|
||||||
|
data-filelist-element="true"
|
||||||
|
data-filelist-type="{resource.type}"
|
||||||
|
data-filelist-identifier="{resource.identifier}"
|
||||||
|
data-filelist-name="{resource.name}"
|
||||||
|
data-filelist-icon="{resource.iconIdentifier}"
|
||||||
|
data-filelist-preview="{f:if(condition: resource.preview, then: 'true', else: 'false')}"
|
||||||
|
data-filelist-uid="{resource.uid}"
|
||||||
|
data-filelist-meta-uid="{resource.metaDataUid}"
|
||||||
|
data-filelist-url="{resource.publicUrl}"
|
||||||
|
data-filelist-mime-type="{resource.mimeType}"
|
||||||
|
data-filelist-selectable="{f:if(condition: resource.isSelectable, then: 'true', else: 'false')}"
|
||||||
|
data-filelist-selected="{f:if(condition: resource.isSelected, then: 'true', else: 'false')}"
|
||||||
|
data-multi-record-selection-element="true"
|
||||||
|
{f:if(condition: defaultLanguageAccess, then: 'data-default-language-access="true"')}
|
||||||
|
draggable="{resource.canMove ? 'true' : 'false'}"
|
||||||
|
>
|
||||||
|
<button type="button" title="{resource.name}" data-filelist-action="primary">
|
||||||
|
<span class="resource-tile-label" id="resource-tile-label-{resource.uid}">{resource.name}</span>
|
||||||
|
<span class="resource-tile-preview" role="presentation">
|
||||||
|
<span class="resource-tile-preview-content">
|
||||||
|
<f:if condition="{displayThumbs} && {resource.preview}">
|
||||||
|
<f:then>
|
||||||
|
<span class="resource-tile-image">
|
||||||
|
<f:image image="{resource.preview}" maxHeight="115" maxWidth="166" additionalAttributes="{draggable: 'false'}" loading="lazy" alt="" />
|
||||||
|
</span>
|
||||||
|
</f:then>
|
||||||
|
<f:else>
|
||||||
|
<span class="resource-tile-icon">
|
||||||
|
{resource.iconLarge -> f:format.raw()}
|
||||||
|
</span>
|
||||||
|
</f:else>
|
||||||
|
</f:if>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="resource-tile-nameplate">
|
||||||
|
<f:if condition="{resource.missing}">
|
||||||
|
<span class="resource-tile-nameplate-badge">
|
||||||
|
<span class="badge badge-danger"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing" /></span><br>
|
||||||
|
</span>
|
||||||
|
</f:if>
|
||||||
|
<span class="resource-tile-nameplate-label">{resource.name}</span>
|
||||||
|
<span class="resource-tile-nameplate-activity">{resource.updatedAt -> f:format.date()}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<f:if condition="{resource.isSelectable} && {resource.checkboxConfig} && {displayCheckbox}">
|
||||||
|
<span class="resource-tile-checkbox" role="checkbox" aria-label="{resource.name}">
|
||||||
|
<div class="form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input {resource.checkboxConfig.class}"
|
||||||
|
type="checkbox"
|
||||||
|
name="{resource.checkboxConfig.name}"
|
||||||
|
value="{resource.checkboxConfig.value}"
|
||||||
|
{f:if(condition: resource.checkboxConfig.checked, then: 'checked')}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</f:if>
|
||||||
|
</div>
|
||||||
|
</f:section>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<html
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<f:layout name="LinkBrowser" />
|
||||||
|
|
||||||
|
<f:section name="Navigation">
|
||||||
|
<typo3-backend-component-filestorage-browser
|
||||||
|
active-folder="{f:if(condition: selectedFolder, then: selectedFolder.combinedIdentifier, else: '') -> f:format.htmlentities()}"
|
||||||
|
>
|
||||||
|
</typo3-backend-component-filestorage-browser>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
<f:section name="Content">
|
||||||
|
<f:if condition="{selectedFolder}">
|
||||||
|
<h4 class="text-truncate mb-4">
|
||||||
|
<core:iconForResource resource="{selectedFolder}" />
|
||||||
|
{selectedFolder.storage.name}: {selectedFolder.identifier}
|
||||||
|
</h4>
|
||||||
|
</f:if>
|
||||||
|
<f:format.raw>{content}</f:format.raw>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<html
|
||||||
|
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||||
|
data-namespace-typo3-fluid="true"
|
||||||
|
>
|
||||||
|
|
||||||
|
<f:layout name="LinkBrowser" />
|
||||||
|
|
||||||
|
<f:section name="Navigation">
|
||||||
|
<typo3-backend-component-filestorage-browser
|
||||||
|
tree-actions="{treeActions -> f:format.json()}"
|
||||||
|
active-folder="{f:if(condition: selectedFolder, then: selectedFolder.combinedIdentifier, else: '') -> f:format.htmlentities()}"
|
||||||
|
>
|
||||||
|
</typo3-backend-component-filestorage-browser>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
<f:section name="Content">
|
||||||
|
<f:if condition="{selectedFolder}">
|
||||||
|
<h4 class="text-truncate mb-4">
|
||||||
|
<core:iconForResource resource="{selectedFolder}" />
|
||||||
|
<f:render section="LinkWrap" contentAs="linkText" arguments="{_all}">
|
||||||
|
{selectedFolder.storage.name}: {selectedFolder.identifier}
|
||||||
|
</f:render>
|
||||||
|
</h4>
|
||||||
|
</f:if>
|
||||||
|
<f:format.raw>{content}</f:format.raw>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
<f:section name="LinkWrap">
|
||||||
|
<f:if condition="{selectedFolderLink}">
|
||||||
|
<f:then>
|
||||||
|
<a href="#" class="element-browser-link" data-linkbrowser-link="{selectedFolderLink}">
|
||||||
|
{linkText -> f:format.raw()}
|
||||||
|
</a>
|
||||||
|
</f:then>
|
||||||
|
<f:else>
|
||||||
|
{linkText -> f:format.raw()}
|
||||||
|
</f:else>
|
||||||
|
</f:if>
|
||||||
|
</f:section>
|
||||||
|
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 350 B |
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import{MessageUtility as u}from"@typo3/backend/utility/message-utility.js";import f from"@typo3/backend/element-browser.js";import a from"@typo3/core/event/regular-event.js";import m from"@typo3/backend/icons.js";import{FileListActionSelector as g,FileListActionUtility as h,FileListActionEvent as r}from"@typo3/filelist/file-list-actions.js";import y from"@typo3/backend/info-window.js";import w from"@typo3/core/ajax/ajax-request.js";class l{constructor(){this.importSelection=e=>{e.preventDefault();const t=e.target,n=e.detail.checkboxes;if(!n.length)return;const o=[];n.forEach(i=>{if(i.checked){const s=i.closest(g.elementSelector),c=h.getResourceForElement(s);c.type==="file"&&c.uid&&o.unshift(c)}}),o.length&&(m.getIcon("spinner-circle",m.sizes.small,null,null,m.markupIdentifiers.inline).then(i=>{t.classList.add("disabled"),t.innerHTML=i}),l.handleNext(o),new a("message",i=>{if(!u.verifyOrigin(i.origin))throw"Denied message sent by "+i.origin;i.data.actionName==="typo3:foreignRelation:inserted"&&(o.length>0?l.handleNext(o):f.focusOpenerAndClose())}).bindTo(window))},new a(r.primary,e=>{e.preventDefault();const t=e.detail;t.originalAction=r.primary,t.action=r.select,document.dispatchEvent(new CustomEvent(r.select,{detail:t}))}).bindTo(document),new a(r.select,e=>{e.preventDefault();const t=e.detail,n=t.resources[0];n.type==="file"&&l.insertElement(n.name,n.uid,t.originalAction===r.primary),n.type==="folder"&&this.loadContent(n)}).bindTo(document),new a(r.show,e=>{e.preventDefault();const n=e.detail.resources[0];y.showItem("_"+n.type.toUpperCase(),n.identifier)}).bindTo(document),new a("multiRecordSelection:action:import",this.importSelection).bindTo(document)}static insertElement(e,t,n){return f.insertElement("sys_file",String(t),e,String(t),n)}static handleNext(e){if(e.length>0){const t=e.pop();l.insertElement(t.name,Number(t.uid))}}async loadContent(e){if(e.type!=="folder")return;const t=document.location.href+"&contentOnly=1&expandFolder="+e.identifier,o=await(await new w(t).get()).resolve(),i=document.querySelector(".element-browser-main-content .element-browser-body");i.innerHTML=o;const s=document.querySelector("typo3-backend-component-filestorage-browser-tree");if(s){const c=encodeURIComponent(e.identifier),d=s.nodes.find(p=>p.identifier===c);d&&(await s.expandNodeParents(d),s.selectNode(d,!1))}}}var b=new l;export{b as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import l from"@typo3/backend/element-browser.js";import r from"@typo3/core/event/regular-event.js";import{FileListActionSelector as a,FileListActionUtility as d,FileListActionEvent as o}from"@typo3/filelist/file-list-actions.js";import u from"@typo3/backend/info-window.js";class c{constructor(){this.importSelection=e=>{e.preventDefault();const t=e.detail.checkboxes;if(!t.length)return;const i=[];t.forEach(n=>{if(n.checked){const m=n.closest(a.elementSelector),s=d.getResourceForElement(m);s.type==="folder"&&s.identifier&&i.unshift(s)}}),i.length&&(i.forEach(function(n){c.insertElement(n.identifier)}),l.focusOpenerAndClose())},new r(o.primary,e=>{e.preventDefault();const t=e.detail;t.originalAction=o.primary,t.action=o.select,document.dispatchEvent(new CustomEvent(o.select,{detail:t}))}).bindTo(document),new r(o.select,e=>{e.preventDefault();const t=e.detail,i=t.resources[0];i.type==="folder"&&c.insertElement(i.identifier,t.originalAction===o.primary)}).bindTo(document),new r(o.show,e=>{e.preventDefault();const i=e.detail.resources[0];u.showItem("_"+i.type.toUpperCase(),i.identifier)}).bindTo(document),new r("multiRecordSelection:action:import",this.importSelection).bindTo(document)}static insertElement(e,t){return l.insertElement("",e,e,e,t)}}var f=new c;export{f as default};
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import{SeverityEnum as c}from"@typo3/backend/enum/severity.js";import m from"@typo3/core/event/regular-event.js";import f from"@typo3/core/document-service.js";import p from"@typo3/backend/modal.js";import o from"~labels/backend.alt_doc";class u{constructor(){f.ready().then(()=>{new m("click",(i,e)=>{i.preventDefault();let t=e.dataset.redirectUrl;t=encodeURIComponent(t||top.list_frame.document.location.pathname+top.list_frame.document.location.search);const d=e.dataset.filelistDeleteIdentifier,r=e.dataset.filelistDeleteType,n=e.dataset.filelistDeleteUrl+"&data[delete][0][data]="+encodeURIComponent(d)+"&data[delete][0][redirect]="+t;if(e.dataset.filelistDeleteCheck){const l=p.confirm(e.dataset.title,e.dataset.content,c.warning,[{text:o.get("buttons.confirm.delete_file.no"),active:!0,btnClass:"btn-default",name:"no"},{text:r==="delete_folder"?o.get("buttons.confirm.delete_folder.yes"):o.get("buttons.confirm.delete_file.yes"),btnClass:"btn-warning",name:"yes"}]);l.addEventListener("button.clicked",s=>{const a=s.target.name;a==="no"?l.hideModal():a==="yes"&&(l.hideModal(),top.list_frame.location.href=n)})}else top.list_frame.location.href=n}).delegateTo(document,'[data-filelist-delete="true"]')})}}var b=new u;export{b as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import r from"@typo3/core/event/regular-event.js";var a;(function(t){t.primary="typo3:filelist:resource:action:primary",t.primaryContextmenu="typo3:filelist:resource:action:primaryContextmenu",t.show="typo3:filelist:resource:action:show",t.rename="typo3:filelist:resource:action:rename",t.replace="typo3:filelist:resource:action:replace",t.select="typo3:filelist:resource:action:select",t.download="typo3:filelist:resource:action:download",t.updateOnlineMedia="typo3:filelist:resource:action:updateOnlineMedia"})(a||(a={}));var n;(function(t){t.elementSelector="[data-filelist-element]",t.actionSelector="[data-filelist-action]"})(n||(n={}));class o{static createResourceFromContextDataset(e){return{type:e.filecontextType,identifier:e.filecontextIdentifier,name:e.filecontextName,hasPreview:!1,uid:e.filecontextUid?parseInt(e.filecontextUid,10):null,metaUid:e.filecontextMetaUid?parseInt(e.filecontextMetaUid,10):null,url:e.filecontextUid?e.url:null,createdAt:e.filecontextCreatedAt?parseInt(e.filecontextCreatedAt,10):null,size:e.filecontextSize?parseInt(e.filecontextSize,10):null}}static getResourceForElement(e){return{type:e.dataset.filelistType,identifier:e.dataset.filelistIdentifier,name:e.dataset.filelistName,hasPreview:"filelistPreview"in e.dataset&&e.dataset.filelistPreview.trim()==="true",uid:e.dataset.filelistUid?parseInt(e.dataset.filelistUid,10):null,metaUid:e.dataset.filelistMetaUid?parseInt(e.dataset.filelistMetaUid,10):null,url:e.dataset.filelistUrl?e.dataset.filelistUrl:null,createdAt:e.dataset.filelistCreatedAt?parseInt(e.dataset.filelistCreatedAt,10):null,size:e.dataset.filelistSize?parseInt(e.dataset.filelistSize,10):null}}}class d{constructor(){new r("contextmenu",(e,l)=>{e.preventDefault(),e.stopImmediatePropagation();const i=this.getActionDetail(e,l);switch(i.action){case"primary":document.dispatchEvent(new CustomEvent(a.primaryContextmenu,{detail:i}));break;default:break}}).delegateTo(document,n.actionSelector),new r("click",(e,l)=>{e.preventDefault();const i=this.getActionDetail(e,l);switch(i.action){case"primary":document.dispatchEvent(new CustomEvent(a.primary,{detail:i}));break;case"show":document.dispatchEvent(new CustomEvent(a.show,{detail:i}));break;case"select":document.dispatchEvent(new CustomEvent(a.select,{detail:i}));break;case"rename":document.dispatchEvent(new CustomEvent(a.rename,{detail:i}));break;case"replace":document.dispatchEvent(new CustomEvent(a.replace,{detail:i}));break;case"download":document.dispatchEvent(new CustomEvent(a.download,{detail:i}));break;case"updateOnlineMedia":document.dispatchEvent(new CustomEvent(a.updateOnlineMedia,{detail:i}));break;default:break}}).delegateTo(document,n.actionSelector)}getActionDetail(e,l){const i=l.dataset.filelistAction,s=l.closest(n.elementSelector),c=o.getResourceForElement(s);return{event:e,trigger:l,action:i,resources:[c],url:l.dataset.filelistActionUrl??null,originalAction:null}}}var u=new d;export{a as FileListActionEvent,n as FileListActionSelector,o as FileListActionUtility,u as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import i from"@typo3/core/event/regular-event.js";import{MultiRecordSelectionSelectors as v}from"@typo3/backend/multi-record-selection.js";import{FileListActionSelector as l,FileListActionUtility as n}from"@typo3/filelist/file-list-actions.js";import{DataTransferTypes as f}from"@typo3/backend/enum/data-transfer-types.js";var c;(function(m){m.transfer="typo3:filelist:resource:dragdrop:transfer"})(c||(c={}));class w{constructor(){this.previewSize=32;const t=l.elementSelector+'[draggable="true"]';new i("dragstart",(e,r)=>{const s=[];let d="",u="";const g=document.querySelectorAll(v.checkboxSelector+":checked");if(g.length)g.forEach(a=>{if(a.checked){const o=a.closest(l.elementSelector);o.dataset.filelistDragdropTransferItem="true";const h=n.getResourceForElement(o);s.push(h),u=o.dataset.filelistName,d=o.dataset.filelistIcon}});else{const a=r.closest(l.elementSelector);a.dataset.filelistDragdropTransferItem="true";const o=n.getResourceForElement(a);s.push(o),u=a.dataset.filelistName,d=a.dataset.filelistIcon}e.dataTransfer.effectAllowed="move",e.dataTransfer.setData(f.falResources,JSON.stringify(s));const p={tooltipIconIdentifier:s.length>1?"apps-clipboard-images":d,tooltipLabel:s.length>1?this.getPreviewLabel(s):u,thumbnails:this.getPreviewItems(s)};e.dataTransfer.setData(f.dragTooltip,JSON.stringify(p))}).delegateTo(document,t),new i("dragover",(e,r)=>{const s=n.getResourceForElement(r);this.isDropAllowedOnResoruce(s)&&(e.dataTransfer.dropEffect="move",e.preventDefault(),r.classList.add("success"))},{capture:!0}).delegateTo(document,t),new i("drop",(e,r)=>{const s={action:"transfer",resources:JSON.parse(e.dataTransfer.getData(f.falResources)??"{}"),target:n.getResourceForElement(r)};top.document.dispatchEvent(new CustomEvent(c.transfer,{detail:s}))},{capture:!0,passive:!0}).delegateTo(document,t),new i("dragend",()=>{this.reset()},{capture:!0,passive:!0}).delegateTo(document,t),new i("dragleave",(e,r)=>{r.classList.remove("success")},{capture:!0,passive:!0}).delegateTo(document,t)}getPreviewItems(t){return t.filter(e=>e.hasPreview).map(e=>{const r=new URL(top.TYPO3.settings.Resource.thumbnailUrl,window.origin);return r.searchParams.set("identifier",e.uid.toString(10)),{src:r.toString(),width:this.previewSize,height:this.previewSize}})}getPreviewLabel(t){const e=t.filter(s=>s.hasPreview),r=t.length-e.length;return r>0?(e.length>0?"+":"")+r.toString():""}reset(){document.querySelectorAll(l.elementSelector).forEach(t=>{delete t.dataset.filelistDragdropTransferItem,t.classList.remove("success")})}isDropAllowedOnResoruce(t){return!("filelistDragdropTransferItem"in document.querySelector(l.elementSelector+'[data-filelist-identifier="'+t.identifier+'"]').dataset)&&t.type==="folder"}}var S=new w;export{c as FileListDragDropEvent,S as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import g from"@typo3/core/event/regular-event.js";import{html as h}from"lit";import{FileListActionEvent as w}from"@typo3/filelist/file-list-actions.js";import l from"@typo3/backend/modal.js";import y from"@typo3/core/ajax/ajax-request.js";import d from"@typo3/backend/notification.js";import s from"@typo3/backend/viewport.js";import n from"~labels/core.core";class C{constructor(){new g(w.rename,o=>{const a=o.detail.resources[0],c=l.advanced({title:n.get("file_rename.title"),type:l.types.default,size:l.sizes.small,content:this.composeEditForm(a),buttons:[{text:n.get("file_rename.button.cancel"),btnClass:"btn-default",name:"cancel",trigger:()=>{c.hideModal()}},{text:n.get("file_rename.button.rename"),btnClass:"btn-primary",name:"rename",trigger:()=>{c.querySelector("form")?.requestSubmit()}}],callback:function(i){const f=i.querySelector("form");f.addEventListener("submit",t=>{t.preventDefault();const p=new FormData(t.target),u=Object.fromEntries(p).name.toString();a.name!==u&&new y(TYPO3.settings.ajaxUrls.resource_rename).post({identifier:a.identifier,resourceName:u}).then(async b=>{const r=await b.resolve();if(r.status.length>0&&r.status.forEach(e=>{r.success?d.success(e.title,e.message):d.error(e.title,e.message)}),r.resource?.type==="folder"){const e=s.ContentContainer.getUrl();new URL(e,window.location.origin).searchParams.get("id")===r.origin.identifier?s.ContentContainer.setUrl(e+"&id="+r.resource.identifier):s.ContentContainer.refresh()}else s.ContentContainer.refresh();top.document.dispatchEvent(new CustomEvent("typo3:filestoragetree:refresh")),i.hideModal()})}),i.addEventListener("typo3-modal-shown",()=>{const t=f.querySelector("input");t!==null&&(t.focus(),t.setSelectionRange(0,a.name.lastIndexOf(".")))})}})}).bindTo(document)}composeEditForm(o){const m=o?.type==="folder"?n.get("folder_rename.label"):n.get("file_rename.label");return h`<form><label class=form-label for=rename_target>${m}</label> <input id=rename_target name=name class=form-control value=${o.name} required></form>`}}var v=new C;export{v as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import{html as l,nothing as h}from"lit";import{until as v}from"lit/directives/until.js";import p from"@typo3/core/event/regular-event.js";import{FileListActionEvent as y}from"@typo3/filelist/file-list-actions.js";import n from"@typo3/backend/modal.js";import f from"@typo3/core/ajax/ajax-request.js";import"@typo3/core/ajax/ajax-response.js";import u from"@typo3/backend/notification.js";import k from"@typo3/backend/viewport.js";import{FormatUtility as w}from"@typo3/backend/utility/format-utility.js";import{ThumbnailSize as _}from"@typo3/backend/element/thumbnail-element.js";import{topLevelModuleImport as $}from"@typo3/backend/utility/top-level-module-import.js";import a from"~labels/filelist.messages";import F from"~labels/core.common";import L from"~labels/core.core";import b from"~labels/filelist.mod_file_list";class q{constructor(){new p(y.replace,e=>{const t=e.detail.resources[0],m=n.advanced({title:a.get("file_replace.title",[t.name]),type:n.types.default,size:n.sizes.small,content:l`${v(this.loadEditor(t.identifier),l`<typo3-backend-spinner></typo3-backend-spinner>`)}`,buttons:[{text:F.get("cancel"),btnClass:"btn-default",name:"cancel",trigger:()=>{m.hideModal()}},{text:a.get("file_replace.button.replace"),btnClass:"btn-primary",name:"rename",trigger:()=>{m.querySelector("form")?.requestSubmit()}}],callback:function(o){new p("submit",c=>{c.preventDefault();const d=new FormData(c.target);d.set("uid",t.uid.toString()),new f(TYPO3.settings.ajaxUrls.resource_replace).post(d).then(async g=>{const s=await g.resolve();s.status.length>0&&s.status.forEach(r=>{s.success?u.success(r.title,r.message):u.error(r.title,r.message)}),k.ContentContainer.refresh(),o.hideModal()})}).delegateTo(o,"form")}})}).bindTo(document)}async loadEditor(e){const t=await(await new f(TYPO3.settings.ajaxUrls.resource_gather).withQueryArguments({identifier:e}).get()).resolve();return await $("@typo3/backend/element/datetime-element.js"),this.composeEditForm(t)}composeEditForm(e){const i=new URL(top.TYPO3.settings.Resource.thumbnailUrl,window.origin);return i.searchParams.set("identifier",e.uid.toString(10)),l`<div class=file-replace-dialog>${a.get("file_replace.intro",[e.name])}<div class=file-replace-dialog-summary>${e.hasPreview?l`<div class=file-replace-dialog-summary-thumbnail><typo3-backend-thumbnail url=${i} size=${_.large} width=96 keepaspectratio></typo3-backend-thumbnail></div>`:h}<div class=file-replace-dialog-summary-info><dl><dt>${b.get("c_name")}</dt><dd>${e.name}</dd><dt>${b.get("c_size")}</dt><dd>${w.fileSizeAsString(e.size)}</dd><dt>${L.get("labels.crdate")}</dt><dd><typo3-backend-datetime format=datetime datetime=${e.createdAt}></typo3-backend-datetime></dd></dl></div></div><form><div class=form-group><label class=form-label for=file_replace>${a.get("file_replace.new_file.label")}</label> <input id=file_replace type=file class=form-control name=replace_1></div><div class=form-check><input type=checkbox value=1 id=keepFilename name=keepFilename class=form-check-input checked> <label class=form-check-label for=keepFilename>${a.get("file_replace.keepFilename.label",[e.name])}</label></div></form></div>`}}var x=new q;export{x as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import{SeverityEnum as g}from"@typo3/backend/enum/severity.js";import d from"@typo3/backend/modal.js";import p from"@typo3/core/ajax/ajax-request.js";import u from"@typo3/core/event/regular-event.js";import h from"@typo3/backend/notification.js";import l from"@typo3/backend/viewport.js";import r from"~labels/filelist.transfer_handler";import{FileListDragDropEvent as v}from"@typo3/filelist/file-list-dragdrop.js";var c;(function(m){m.move="move",m.copy="copy"})(c||(c={}));class y{constructor(){new u(v.transfer,n=>{const e=n.detail,a=e.target,o=e.resources;let i,t;if(e.resources.length===1){const f=e.resources[0];i=r.get("message.transfer_resource.title"),t=r.get("message.transfer_resource.text",[f.name,a.name])}else i=r.get("message.transfer_resources.title"),t=r.get("message.transfer_resources.text",[o.length,a.name]);const s=d.confirm(i,t,g.notice,[{text:r.get("message.button.cancel"),active:!0,btnClass:"btn-default",name:"cancel",trigger:()=>{s.hideModal()}},{text:r.get("message.button.copy"),btnClass:"btn-primary",name:"copy",trigger:()=>{this.transfer(c.copy,o,a),s.hideModal()}},{text:r.get("message.button.move"),btnClass:"btn-primary",name:"move",trigger:()=>{this.transfer(c.move,o,a),s.hideModal()}}])}).bindTo(top.document)}transfer(n,e,a){const o=[];e.forEach(t=>{const s={data:t.identifier,target:a.identifier};o.push(s)});const i={data:{[n]:o}};new p(top.TYPO3.settings.ajaxUrls.file_process).post(i).then(async t=>{const s=await t.resolve();this.handleMessages(s.messages??[]),l.ContentContainer.refresh(),top.document.dispatchEvent(new CustomEvent("typo3:filestoragetree:refresh"))}).catch(async t=>{const s=await t.resolve();this.handleMessages(s.messages??[]),l.ContentContainer.refresh(),top.document.dispatchEvent(new CustomEvent("typo3:filestoragetree:refresh"))})}handleMessages(n){n.forEach(e=>{h.showMessage(e.title||"",e.message||"",e.severity)})}}var b=new y;export{b as default};
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import m from"@typo3/backend/link-browser.js";import c from"@typo3/core/event/regular-event.js";import{FileListActionEvent as r}from"@typo3/filelist/file-list-actions.js";import d from"@typo3/core/ajax/ajax-request.js";import p from"@typo3/backend/info-window.js";import u from"@typo3/backend/notification.js";class w{constructor(){new c(r.primary,e=>{e.preventDefault();const n=e.detail;n.action=r.select,document.dispatchEvent(new CustomEvent(r.select,{detail:n}))}).bindTo(document),new c(r.select,e=>{e.preventDefault();const t=e.detail.resources[0];t.type==="file"&&this.insertLink(t),t.type==="folder"&&this.loadContent(t)}).bindTo(document),new c(r.show,e=>{e.preventDefault();const t=e.detail.resources[0];p.showItem("_"+t.type.toUpperCase(),t.identifier)}).bindTo(document)}insertLink(e){new d(TYPO3.settings.ajaxUrls.link_resource).post({identifier:e.identifier}).then(async t=>{const o=await t.resolve();o.status.forEach(i=>{u.showMessage(i.title,i.message,i.severity)}),o.success&&m.finalizeFunction(o.link)})}async loadContent(e){if(e.type!=="folder")return;const n=document.location.href+"&contentOnly=1&expandFolder="+e.identifier,o=await(await new d(n).get()).resolve(),i=document.querySelector(".element-browser-main-content .element-browser-body");i.innerHTML=o;const s=document.querySelector("typo3-backend-component-filestorage-browser-tree");if(s){const l=encodeURIComponent(e.identifier),a=s.nodes.find(f=>f.identifier===l);a&&(await s.expandNodeParents(a),s.selectNode(a,!1))}}}var y=new w;export{y as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import a from"@typo3/backend/link-browser.js";import o from"@typo3/core/event/regular-event.js";import{FileListActionEvent as n}from"@typo3/filelist/file-list-actions.js";import l from"@typo3/backend/info-window.js";import c from"@typo3/core/ajax/ajax-request.js";import d from"@typo3/backend/notification.js";class u{constructor(){new o("click",(e,t)=>{e.preventDefault(),a.finalizeFunction(t.dataset.linkbrowserLink)}).delegateTo(document,"[data-linkbrowser-link]"),new o(n.primary,e=>{e.preventDefault();const t=e.detail;t.action=n.select,document.dispatchEvent(new CustomEvent(n.select,{detail:t}))}).bindTo(document),new o(n.select,e=>{e.preventDefault();const i=e.detail.resources[0];i.type==="folder"&&this.insertLink(i)}).bindTo(document),new o(n.show,e=>{e.preventDefault();const i=e.detail.resources[0];l.showItem("_"+i.type.toUpperCase(),i.identifier)}).bindTo(document)}insertLink(e){new c(TYPO3.settings.ajaxUrls.link_resource).post({identifier:e.identifier}).then(async i=>{const r=await i.resolve();r.status.forEach(s=>{d.showMessage(s.title,s.message,s.severity)}),r.success&&a.finalizeFunction(r.link)})}}var f=new u;export{f as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import{SeverityEnum as g}from"@typo3/backend/enum/severity.js";import v from"@typo3/core/ajax/ajax-request.js";import b from"@typo3/backend/modal.js";import h from"@typo3/core/document-service.js";import n from"~labels/filelist.messages";class x{constructor(){h.ready().then(()=>{this.initialize()})}initialize(){const e=document.querySelector(".t3js-submit-file-rename");e!==null&&e.addEventListener("click",this.checkForDuplicate)}checkForDuplicate(e){e.preventDefault();const t=e.currentTarget.form,a=t.querySelector('input[name="data[rename][0][target]"]'),i=t.querySelector('input[name="data[rename][0][destination]"]'),r=t.querySelector('input[name="data[rename][0][conflictMode]"]'),l={fileName:a.value};i!==null&&(l.fileTarget=i.value),new v(TYPO3.settings.ajaxUrls.file_exists).withQueryArguments(l).get({cache:"no-cache"}).then(async u=>{const d=typeof(await u.resolve()).uid<"u",o=a.dataset.original,s=a.value;if(d&&o!==s){const f=n.get("file_rename.exists.description",{0:o,1:s}),c=b.confirm(n.get("file_rename.exists.title"),f,g.warning,[{active:!0,btnClass:"btn-default",name:"cancel",text:n.get("file_rename.actions.cancel")},{btnClass:"btn-primary",name:"rename",text:n.get("file_rename.actions.rename")},{btnClass:"btn-default",name:"replace",text:n.get("file_rename.actions.override")}]);c.addEventListener("button.clicked",p=>{const m=p.target;m.name!=="cancel"&&(r!==null&&(r.value=m.name),t.submit()),c.hideModal()})}else t.submit()})}}var y=new x;export{y as default};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the TYPO3 CMS project.
|
||||||
|
*
|
||||||
|
* It is free software; you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License, either version 2
|
||||||
|
* of the License, or any later version.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please read the
|
||||||
|
* LICENSE.txt file that was distributed with this source code.
|
||||||
|
*
|
||||||
|
* The TYPO3 project - inspiring people to share!
|
||||||
|
*/
|
||||||
|
import s from"@typo3/core/event/regular-event.js";import f from"@typo3/core/ajax/ajax-request.js";import{FileListActionEvent as n}from"@typo3/filelist/file-list-actions.js";import m from"@typo3/backend/info-window.js";class p{constructor(){new s(n.primary,e=>{e.preventDefault();const t=e.detail;t.action=n.select,document.dispatchEvent(new CustomEvent(n.select,{detail:t}))}).bindTo(document),new s(n.select,e=>{e.preventDefault();const o=e.detail.resources[0];o.type==="folder"&&this.loadContent(o)}).bindTo(document),new s(n.show,e=>{e.preventDefault();const o=e.detail.resources[0];m.showItem("_"+o.type.toUpperCase(),o.identifier)}).bindTo(document)}async loadContent(e){if(e.type!=="folder")return;const t=document.location.href+"&contentOnly=1&expandFolder="+e.identifier,c=await(await new f(t).get()).resolve(),d=document.querySelector(".element-browser-main-content .element-browser-body");d.innerHTML=c;const r=document.querySelector("typo3-backend-component-filestorage-browser-tree");if(r){const a=encodeURIComponent(e.identifier),i=r.nodes.find(l=>l.identifier===a);i&&(await r.expandNodeParents(i),r.selectNode(i,!1))}}}var u=new p;export{u as default};
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "typo3/cms-filelist",
|
||||||
|
"type": "typo3-cms-framework",
|
||||||
|
"description": "TYPO3 CMS Filelist - TYPO3 backend module 'Media' used for managing files.",
|
||||||
|
"homepage": "https://typo3.community/",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "membership",
|
||||||
|
"url": "https://typo3.org/membership"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": [
|
||||||
|
"GPL-2.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "TYPO3 Core Team",
|
||||||
|
"email": "typo3cms@typo3.org",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://forge.typo3.org/issues/",
|
||||||
|
"forum": "https://talk.typo3.org/",
|
||||||
|
"source": "https://github.com/TYPO3/typo3/",
|
||||||
|
"docs": "https://docs.typo3.org/",
|
||||||
|
"rss": "https://news.typo3.com/rss/",
|
||||||
|
"chat": "https://typo3.community/meet/slack/",
|
||||||
|
"security": "https://typo3.org/security/"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"typo3/cms-core": "15.0.*@dev"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"typo3/cms": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "15.0.x-dev"
|
||||||
|
},
|
||||||
|
"typo3/cms": {
|
||||||
|
"Package": {
|
||||||
|
"protected": true,
|
||||||
|
"partOfFactoryDefault": true,
|
||||||
|
"partOfMinimalUsableSystem": true
|
||||||
|
},
|
||||||
|
"extension-key": "filelist"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"TYPO3\\CMS\\Filelist\\": "Classes/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user