Files

236 lines
9.3 KiB
PHP

<?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\Backend\Controller\FileStorage;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Attribute\AsController;
use TYPO3\CMS\Backend\Controller\Event\AfterFileStorageTreeItemsPreparedEvent;
use TYPO3\CMS\Backend\Dto\Tree\FileTreeItem;
use TYPO3\CMS\Backend\Dto\Tree\Label\Label;
use TYPO3\CMS\Backend\Dto\Tree\TreeItem;
use TYPO3\CMS\Backend\Tree\FileStorageTreeProvider;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\Localization\LanguageService;
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\FolderInterface;
use TYPO3\CMS\Core\Resource\ResourceFactory;
/**
* Controller providing data to the file storage tree.
*
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
*/
#[AsController]
readonly class TreeController
{
public function __construct(
protected IconFactory $iconFactory,
protected FileStorageTreeProvider $treeProvider,
protected ResourceFactory $resourceFactory,
protected EventDispatcherInterface $eventDispatcher,
) {}
/**
* Loads data for the first time, or when expanding a folder.
*/
public function fetchDataAction(ServerRequestInterface $request): ResponseInterface
{
$parentIdentifier = $request->getQueryParams()['parent'] ?? null;
if ($parentIdentifier) {
$currentDepth = (int)($request->getQueryParams()['depth'] ?? 1);
$parentIdentifier = rawurldecode($parentIdentifier);
$folder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($parentIdentifier);
$items = $this->treeProvider->getSubfolders($folder, $currentDepth + 1);
} else {
$items = $this->treeProvider->getRootNodes($this->getBackendUser());
}
return new JsonResponse($this->getPreparedItemsForOutput($request, $items));
}
/**
* Returns JSON representing page rootline
*/
public function fetchRootlineAction(ServerRequestInterface $request): ResponseInterface
{
$identifier = (string)($request->getQueryParams()['identifier'] ?? '');
if ($identifier === '') {
return new JsonResponse(null, 400);
}
try {
$folder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($identifier);
} catch (InsufficientFolderAccessPermissionsException) {
return new JsonResponse(null, 403);
} catch (FolderDoesNotExistException) {
return new JsonResponse(null, 404);
}
$rootline = [];
while (true) {
$identifier = $folder->getCombinedIdentifier();
$rootline[] = $identifier;
try {
$parent = $folder->getParentFolder();
} catch (InsufficientFolderAccessPermissionsException) {
break;
}
if ($parent->getCombinedIdentifier() === $identifier) {
// parent folder of root folder is the root folder => break
break;
}
$folder = $parent;
}
return new JsonResponse([
'rootline' => array_reverse($rootline),
]);
}
/**
* Used when the search / filter is used.
*
* @throws \Exception
*/
public function filterDataAction(ServerRequestInterface $request): ResponseInterface
{
$search = $request->getQueryParams()['q'] ?? '';
$foundFolders = $this->treeProvider->getFilteredTree($this->getBackendUser(), $search);
$items = [];
foreach ($foundFolders as $folder) {
if (!$folder instanceof Folder) {
continue;
}
$storage = $folder->getStorage();
$itemsInRootLine = [];
// Go back the root folder structure until the root folder
$nextFolder = $folder;
$isParent = false;
do {
$itemsInRootLine[$nextFolder->getCombinedIdentifier()] = array_merge(
$this->treeProvider->prepareFolderInformation($nextFolder),
[
'expanded' => $isParent,
'loaded' => true,
]
);
$isParent = true;
try {
$nextFolder = $nextFolder->getParentFolder();
} catch (InsufficientFolderAccessPermissionsException) {
$nextFolder = null;
}
} while ($nextFolder instanceof FolderInterface && $nextFolder->getIdentifier() !== '/');
// Add the storage / sys_filemount itself
$storageData = $this->treeProvider->prepareFolderInformation(
$storage->getRootLevelFolder(true),
$storage->getName()
);
$storageData = array_merge($storageData, [
'depth' => 0,
'expanded' => true,
]);
$itemsInRootLine[$storage->getUid() . ':/'] = $storageData;
$itemsInRootLine = array_reverse($itemsInRootLine);
$depth = 0;
foreach ($itemsInRootLine as $k => $itm) {
$itm['depth'] = $depth++;
$items[$k] = $itm;
}
}
ksort($items);
$items = array_values($items);
return new JsonResponse($this->getPreparedItemsForOutput($request, $items));
}
/**
* Adds information for the JSON result to be rendered. Additionally, dispatches event for modification.
*/
protected function getPreparedItemsForOutput(ServerRequestInterface $request, array $items): array
{
foreach ($items as &$item) {
$folder = $item['resource'];
$isStorage = $item['recordType'] !== 'sys_file';
$item['resourceType'] = $isStorage ? 'storage' : 'folder';
if ($isStorage && !$folder->getStorage()->isOnline()) {
$item['name'] .= ' (' . $this->getLanguageService()->translate('is_offline', 'core.db.sys_file_storage') . ')';
}
$icon = $this->iconFactory->getIconForResource($folder, IconSize::SMALL, null, $isStorage ? ['mount-root' => true] : []);
$item['icon'] = $icon->getIdentifier();
$item['overlayIcon'] = $icon->getOverlayIcon() ? $icon->getOverlayIcon()->getIdentifier() : '';
$tsConfigLabels = $this->getBackendUser()->getTSConfig()['options.']['folderTree.']['label.'] ?? [];
if (trim($tsConfigLabels[$folder->getCombinedIdentifier() . '.']['label'] ?? '') !== '') {
$item['labels'][] = new Label(
label: $this->getLanguageService()->sL($tsConfigLabels[$folder->getCombinedIdentifier() . '.']['label']),
color: (string)($tsConfigLabels[$folder->getCombinedIdentifier() . '.']['color'] ?? '#ff8722'),
);
}
}
return array_map(
static function (array $item): FileTreeItem {
return new FileTreeItem(
item: new TreeItem(
identifier: $item['identifier'],
parentIdentifier: (string)($item['parentIdentifier'] ?? ''),
recordType: (string)($item['recordType'] ?? ''),
name: (string)($item['name'] ?? ''),
prefix: (string)($item['prefix'] ?? ''),
suffix: (string)($item['suffix'] ?? ''),
tooltip: (string)($item['tooltip'] ?? ''),
depth: (int)($item['depth'] ?? 0),
hasChildren: (bool)($item['hasChildren'] ?? false),
loaded: (bool)($item['loaded'] ?? false),
icon: $item['icon'],
overlayIcon: $item['overlayIcon'],
statusInformation: (array)($item['statusInformation'] ?? []),
labels: (array)($item['labels'] ?? []),
),
pathIdentifier: (string)($item['pathIdentifier'] ?? ''),
storage: (int)($item['storage'] ?? 0),
resourceType: $item['resourceType'],
);
},
$this->eventDispatcher->dispatch(
new AfterFileStorageTreeItemsPreparedEvent($request, $items)
)->getItems()
);
}
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}