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

FileStorageTreeProvider   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 247
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 38
eloc 129
c 1
b 0
f 0
dl 0
loc 247
rs 9.36

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getMountsInStorage() 0 21 3
A getFoldersInStorage() 0 25 4
A getStateIdentifier() 0 3 1
B getSubfoldersRecursively() 0 37 7
B getFilteredTree() 0 49 10
A isExpanded() 0 8 3
A getRootNodes() 0 8 2
B prepareFolderInformation() 0 34 8
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\Tree;
19
20
use TYPO3\CMS\Backend\Configuration\BackendUserConfiguration;
21
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22
use TYPO3\CMS\Core\Resource\Driver\DriverInterface;
23
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
24
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
25
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderReadPermissionsException;
26
use TYPO3\CMS\Core\Resource\Folder;
27
use TYPO3\CMS\Core\Resource\FolderInterface;
28
use TYPO3\CMS\Core\Resource\InaccessibleFolder;
29
use TYPO3\CMS\Core\Resource\ResourceStorage;
30
use TYPO3\CMS\Core\Resource\Utility\ListUtility;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33
/**
34
 * Responsible for fetching a tree-structure of folders.
35
 *
36
 * @internal not part of TYPO3 Core API due to the specific use case for the FileStorageTree component.
37
 */
38
class FileStorageTreeProvider
39
{
40
    protected ?array $expandedState = null;
41
    protected string $userSettingsIdentifier = 'BackendComponents.States.FileStorageTree';
42
43
    public function prepareFolderInformation(Folder $folder, ?string $alternativeName = null): array
44
    {
45
        $name = $alternativeName ?? $folder->getName();
46
        $storage = $folder->getStorage();
47
        try {
48
            $parentFolder = $folder->getParentFolder();
49
        } catch (FolderDoesNotExistException | InsufficientFolderAccessPermissionsException $e) {
50
            $parentFolder = null;
51
        }
52
        if (strpos($folder->getRole(), FolderInterface::ROLE_MOUNT) !== false) {
53
            $tableName = 'sys_filemount';
54
            $isStorage = true;
55
        } elseif ($parentFolder === null || $folder->getIdentifier() === $storage->getRootLevelFolder(true)->getIdentifier()) {
56
            $tableName = 'sys_file_storage';
57
            $isStorage = true;
58
        } else {
59
            $tableName = 'sys_file';
60
            $isStorage = false;
61
        }
62
63
        try {
64
            $hasSubfolders = !empty($folder->getSubfolders());
65
        } catch (\InvalidArgumentException | InsufficientFolderReadPermissionsException $e) {
66
            $hasSubfolders = false;
67
        }
68
69
        return [
70
            'resource' => $folder,
71
            'stateIdentifier' => $this->getStateIdentifier($folder),
72
            'identifier' => rawurlencode($folder->getCombinedIdentifier()),
73
            'name' => $name,
74
            'hasChildren' => $hasSubfolders,
75
            'parentIdentifier' => $parentFolder instanceof Folder && !$isStorage ? rawurlencode($parentFolder->getCombinedIdentifier()) : null,
76
            'itemType' => $tableName,
77
        ];
78
    }
79
80
    /**
81
     * Fetch all file storages / file mounts visible for a user.
82
     *
83
     * @param BackendUserAuthentication $user
84
     * @return array
85
     */
86
    public function getRootNodes(BackendUserAuthentication $user): array
87
    {
88
        $items = [];
89
        $storages = $user->getFileStorages();
90
        foreach ($storages as $storageObject) {
91
            $items = array_merge($items, $this->getFoldersInStorage($storageObject, $user));
92
        }
93
        return $items;
94
    }
95
96
    /**
97
     * Fetch all folders recursively in a single store.
98
     *
99
     * @param ResourceStorage $resourceStorage
100
     * @param BackendUserAuthentication $user
101
     * @return array
102
     */
103
    protected function getFoldersInStorage(ResourceStorage $resourceStorage, BackendUserAuthentication $user): array
104
    {
105
        $rootLevelFolders = $this->getMountsInStorage($resourceStorage, $user);
106
        $items = [];
107
        foreach ($rootLevelFolders as $i => $rootLevelFolderInfo) {
108
            /** @var Folder $rootLevelFolder */
109
            $rootLevelFolder = $rootLevelFolderInfo['folder'];
110
            // Root level is always expanded if not defined otherwise
111
            $expanded = $this->isExpanded($rootLevelFolder, true);
112
113
            $itm = $this->prepareFolderInformation($rootLevelFolder, $rootLevelFolderInfo['name']);
114
            $itm['depth'] = 0;
115
            $itm['expanded'] = $expanded;
116
            $itm['loaded'] = $expanded;
117
            $itm['siblingsCount'] = count($rootLevelFolders) - 1;
118
            $itm['siblingsPosition'] = $i;
119
            $items[] = $itm;
120
121
            // If the mount is expanded, go down:
122
            if ($expanded && $resourceStorage->isBrowsable()) {
123
                $childItems = $this->getSubfoldersRecursively($rootLevelFolder, 1);
124
                array_push($items, ...$childItems);
125
            }
126
        }
127
        return $items;
128
    }
129
130
    /**
131
     * Filter a tree by a search word
132
     *
133
     * @param BackendUserAuthentication $user
134
     * @param string $search
135
     * @return array
136
     * @throws \Exception
137
     */
138
    public function getFilteredTree(BackendUserAuthentication $user, string $search): array
139
    {
140
        $foundFolders = [];
141
        $storages = $user->getFileStorages();
142
        foreach ($storages as $resourceStorage) {
143
            $processingFolders = $resourceStorage->getProcessingFolders();
144
            $processingFolderIdentifiers = array_map(function ($folder) {
145
                return $folder->getIdentifier();
146
            }, $processingFolders);
147
            $resourceStorage->addFileAndFolderNameFilter(function ($itemName, $itemIdentifier, $parentIdentifier, array $additionalInformation, DriverInterface $driver) use ($resourceStorage, $search, $processingFolderIdentifiers) {
148
                // Skip items in processing folders
149
                $isInProcessingFolder = array_filter($processingFolderIdentifiers, function ($processingFolderIdentifier) use ($parentIdentifier) {
150
                    return stripos($parentIdentifier, $processingFolderIdentifier) !== false;
151
                });
152
                if (!empty($isInProcessingFolder)) {
153
                    return -1;
154
                }
155
                if ($itemName instanceof Folder) {
156
                    if ($resourceStorage->isProcessingFolder($itemName)) {
157
                        return -1;
158
                    }
159
                    $name = $itemName->getName();
160
                } elseif (is_string($itemName)) {
161
                    $name = $itemName;
162
                } else {
163
                    return -1;
164
                }
165
                if (stripos($name, $search) !== false) {
166
                    return true;
167
                }
168
                return -1;
169
            });
170
            try {
171
                $files = $resourceStorage->getFilesInFolder($resourceStorage->getRootLevelFolder(), 0, 0, true, true);
172
                foreach ($files as $file) {
173
                    $folder = $file->getParentFolder();
174
                    $foundFolders[$folder->getCombinedIdentifier()] = $folder;
175
                }
176
                $folders = $resourceStorage->getFolderIdentifiersInFolder($resourceStorage->getRootLevelFolder()->getIdentifier(), true, true);
177
                foreach ($folders as $folder) {
178
                    $folderObj = $resourceStorage->getFolder($folder);
179
                    $foundFolders[$folderObj->getCombinedIdentifier()] = $folderObj;
180
                }
181
            } catch (InsufficientFolderAccessPermissionsException $e) {
182
                // do nothing
183
            }
184
            $resourceStorage->resetFileAndFolderNameFiltersToDefault();
185
        }
186
        return $foundFolders;
187
    }
188
189
    public function getSubfoldersRecursively(Folder $folderObject, int $currentDepth): array
190
    {
191
        $items = [];
192
        if ($folderObject instanceof InaccessibleFolder) {
193
            $subFolders = [];
194
        } else {
195
            $subFolders = $folderObject->getSubfolders();
196
            $subFolders = ListUtility::resolveSpecialFolderNames($subFolders);
197
            uksort($subFolders, 'strnatcasecmp');
198
        }
199
200
        $subFolderCounter = 0;
201
        foreach ($subFolders as $subFolderName => $subFolder) {
202
            $expanded = $this->isExpanded($subFolder);
203
            if (!($subFolder instanceof InaccessibleFolder)) {
204
                $children = $subFolder->getSubfolders();
205
            } else {
206
                $children = [];
207
            }
208
209
            $items[] = array_merge(
210
                $this->prepareFolderInformation($subFolder, is_string($subFolderName) ? $subFolderName : null),
211
                [
212
                    'depth' => $currentDepth,
213
                    'expanded' => $expanded,
214
                    'loaded' => $expanded,
215
                    'siblingsCount' => count($subFolders) - 1,
216
                    'siblingsPosition' => ++$subFolderCounter,
217
                ]
218
            );
219
220
            if ($expanded && !empty($children)) {
221
                $childItems = $this->getSubfoldersRecursively($subFolder, $currentDepth + 1);
222
                array_push($items, ...$childItems);
223
            }
224
        }
225
        return $items;
226
    }
227
228
    /**
229
     * Fetches all "root level folders" of a storage. If a user has filemounts in this storage, they are properly resolved.
230
     *
231
     * @param ResourceStorage $resourceStorage
232
     * @param BackendUserAuthentication $user
233
     * @return array|array[]
234
     */
235
    protected function getMountsInStorage(ResourceStorage $resourceStorage, BackendUserAuthentication $user): array
236
    {
237
        $fileMounts = $resourceStorage->getFileMounts();
238
        if (!empty($fileMounts)) {
239
            return array_map(function ($fileMountInfo) {
240
                return [
241
                    'folder' => $fileMountInfo['folder'],
242
                    'name' => $fileMountInfo['title'],
243
                ];
244
            }, $fileMounts);
245
        }
246
247
        if ($user->isAdmin()) {
248
            return [
249
                [
250
                'folder' => $resourceStorage->getRootLevelFolder(),
251
                'name' => $resourceStorage->getName(),
252
                ]
253
            ];
254
        }
255
        return [];
256
    }
257
258
    /**
259
     * The state identifier is the folder stored in the user settings, and also used to uniquely identify
260
     * a folder throughout the folder tree structure.
261
     *
262
     * @param Folder $folder
263
     * @return string
264
     */
265
    protected function getStateIdentifier(Folder $folder): string
266
    {
267
        return $folder->getStorage()->getUid() . '_' . GeneralUtility::md5int($folder->getIdentifier());
268
    }
269
270
    /**
271
     * Checks if a folder was previously opened by the user.
272
     *
273
     * @param Folder $folder
274
     * @param bool $fallback
275
     * @return bool
276
     */
277
    protected function isExpanded(Folder $folder, bool $fallback = false): bool
278
    {
279
        $stateIdentifier = $this->getStateIdentifier($folder);
280
        if (!is_array($this->expandedState)) {
281
            $this->expandedState = GeneralUtility::makeInstance(BackendUserConfiguration::class)->get($this->userSettingsIdentifier);
282
            $this->expandedState = $this->expandedState['stateHash'] ?: [];
283
        }
284
        return (bool)($this->expandedState[$stateIdentifier] ?? $fallback);
285
    }
286
}
287