Passed
Push — master ( a15053...89cce6 )
by
unknown
18:50
created

TreeController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nop 1
nc 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Backend\Controller\FileStorage;
19
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
use TYPO3\CMS\Backend\Tree\FileStorageTreeProvider;
23
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24
use TYPO3\CMS\Core\Http\JsonResponse;
25
use TYPO3\CMS\Core\Imaging\Icon;
26
use TYPO3\CMS\Core\Imaging\IconFactory;
27
use TYPO3\CMS\Core\Localization\LanguageService;
28
use TYPO3\CMS\Core\Resource\Folder;
29
use TYPO3\CMS\Core\Resource\ResourceFactory;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
32
/**
33
 * Controller providing data to the file storage tree.
34
 *
35
 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
36
 */
37
class TreeController
38
{
39
    protected IconFactory $iconFactory;
40
    protected FileStorageTreeProvider $treeProvider;
41
    protected ResourceFactory $resourceFactory;
42
43
    public function __construct(IconFactory $iconFactory = null)
44
    {
45
        $this->iconFactory = $iconFactory ?? GeneralUtility::makeInstance(IconFactory::class);
46
        $this->treeProvider = GeneralUtility::makeInstance(FileStorageTreeProvider::class);
47
        $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
48
    }
49
50
    /**
51
     * Loads data for the first time, or when expanding a folder.
52
     *
53
     * @param ServerRequestInterface $request
54
     * @return ResponseInterface
55
     */
56
    public function fetchDataAction(ServerRequestInterface $request): ResponseInterface
57
    {
58
        $parentIdentifier = $request->getQueryParams()['parent'] ?? null;
59
        if ($parentIdentifier) {
60
            $currentDepth = (int)($request->getQueryParams()['currentDepth'] ?? 1);
61
            $parentIdentifier = rawurldecode($parentIdentifier);
62
            $folder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($parentIdentifier);
63
            $items = $this->treeProvider->getSubfoldersRecursively($folder, $currentDepth + 1);
64
        } else {
65
            $items = $this->treeProvider->getRootNodes($this->getBackendUser());
66
        }
67
        $items = array_map(function (array $item) {
68
            return $this->prepareItemForOutput($item);
69
        }, $items);
70
        return new JsonResponse($items);
71
    }
72
73
    /**
74
     * Used when the search / filter is used.
75
     *
76
     * @param ServerRequestInterface $request
77
     * @return ResponseInterface
78
     * @throws \Exception
79
     */
80
    public function filterDataAction(ServerRequestInterface $request): ResponseInterface
81
    {
82
        $search = $request->getQueryParams()['q'] ?? '';
83
        $foundFolders = $this->treeProvider->getFilteredTree($this->getBackendUser(), $search);
84
85
        $items = [];
86
        foreach ($foundFolders as $folder) {
87
            $storage = $folder->getStorage();
88
            $itemsInRootLine = [];
89
90
            // Go back the root folder structure until the root folder
91
            $nextFolder = $folder;
92
            $isParent = false;
93
            do {
94
                $itemsInRootLine[$nextFolder->getCombinedIdentifier()] = array_merge(
95
                    $this->treeProvider->prepareFolderInformation($nextFolder),
96
                    [
97
                        'expanded' => $isParent,
98
                    ]
99
                );
100
                $isParent = true;
101
                $nextFolder = $nextFolder->getParentFolder();
102
            } while ($nextFolder instanceof Folder && $nextFolder->getIdentifier() !== '/');
103
            // Add the storage / sys_filemount itself
104
            $storageData = $this->treeProvider->prepareFolderInformation(
105
                $storage->getRootLevelFolder(true),
106
                $storage->getName()
107
            );
108
            $storageData = array_merge($storageData, [
109
                'depth' => 0,
110
                'expanded' => true,
111
                'siblingsCount' => 1,
112
                'siblingsPosition' => 1,
113
            ]);
114
            $itemsInRootLine[$storage->getUid() . ':/'] = $storageData;
115
116
            $itemsInRootLine = array_reverse($itemsInRootLine);
117
            $depth = 0;
118
            foreach ($itemsInRootLine as $k => $itm) {
119
                $itm['depth'] = $depth++;
120
                $items[$k] = $itm;
121
            }
122
        }
123
124
        ksort($items);
125
        // Make sure siblingsCount and siblingsPosition works
126
        $finalItems = [];
127
        $items = array_values($items);
128
        foreach ($items as $item) {
129
            $stateIdentifier = $item['stateIdentifier'];
130
            $parentIdentifier = $item['parentIdentifier'];
131
            $siblings = array_filter($items, function ($itemInArray) use ($parentIdentifier) {
132
                if ($itemInArray['parentIdentifier'] === $parentIdentifier) {
133
                    return true;
134
                }
135
                return false;
136
            });
137
            $positionFound = false;
138
            $siblingsBeforeInSameDepth = array_filter($siblings, function ($itemInArray) use ($stateIdentifier, &$positionFound) {
139
                if ($itemInArray['stateIdentifier'] === $stateIdentifier) {
140
                    $positionFound = true;
141
                }
142
                return !$positionFound;
143
            });
144
            $item['siblingsCount'] = count($siblings);
145
            $item['siblingsPosition'] = count($siblingsBeforeInSameDepth) + 1;
146
            $finalItems[] = $this->prepareItemForOutput($item);
147
        }
148
        // now lets do the siblingsCount
149
        return (new JsonResponse())->setPayload($finalItems);
150
    }
151
152
    /**
153
     * Adds information for the JSON result to be rendered.
154
     *
155
     * @param array $item
156
     * @return array
157
     */
158
    protected function prepareItemForOutput(array $item): array
159
    {
160
        $folder = $item['resource'];
161
        $isStorage = $item['itemType'] !== 'sys_file';
162
        if ($isStorage && !$folder->getStorage()->isOnline()) {
163
            $item['name'] .= ' (' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_file.xlf:sys_file_storage.isOffline') . ')';
164
        }
165
        $icon = $this->iconFactory->getIconForResource($folder, Icon::SIZE_SMALL, null, $isStorage ? ['mount-root' => true] : []);
166
        $item['icon'] = $icon->getIdentifier();
167
        $item['overlayIcon'] = $icon->getOverlayIcon() ? $icon->getOverlayIcon()->getIdentifier() : '';
168
        unset($item['resource']);
169
        return $item;
170
    }
171
172
    protected function getBackendUser(): BackendUserAuthentication
173
    {
174
        return $GLOBALS['BE_USER'];
175
    }
176
177
    protected function getLanguageService(): LanguageService
178
    {
179
        return $GLOBALS['LANG'];
180
    }
181
}
182