Passed
Branch master (6c65a4)
by Christian
16:31
created

FolderTreeView::printTree()   F

Complexity

Conditions 30
Paths 3086

Size

Total Lines 103
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 30
eloc 65
nc 3086
nop 1
dl 0
loc 103
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace TYPO3\CMS\Backend\Tree\View;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use TYPO3\CMS\Backend\Utility\BackendUtility;
18
use TYPO3\CMS\Core\Imaging\Icon;
19
use TYPO3\CMS\Core\Imaging\IconFactory;
20
use TYPO3\CMS\Core\Localization\LanguageService;
21
use TYPO3\CMS\Core\Messaging\FlashMessage;
22
use TYPO3\CMS\Core\Messaging\FlashMessageService;
23
use TYPO3\CMS\Core\Resource\Folder;
24
use TYPO3\CMS\Core\Resource\FolderInterface;
25
use TYPO3\CMS\Core\Resource\InaccessibleFolder;
26
use TYPO3\CMS\Core\Resource\ResourceStorage;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
29
/**
30
 * Generate a folder tree,
31
 * specially made for browsing folders in the File module
32
 */
33
class FolderTreeView extends AbstractTreeView
34
{
35
    /**
36
     * The users' file Storages
37
     *
38
     * @var ResourceStorage[]
39
     */
40
    protected $storages = null;
41
42
    /**
43
     * @var array
44
     */
45
    protected $storageHashNumbers;
46
47
    /**
48
     * Indicates, whether the AJAX call was successful,
49
     * i.e. the requested page has been found
50
     *
51
     * @var bool
52
     */
53
    protected $ajaxStatus = false;
54
55
    /**
56
     * @var array
57
     */
58
    protected $scope;
59
60
    /**
61
     * @var IconFactory
62
     */
63
    protected $iconFactory;
64
65
    /**
66
     * If file-drag mode is set, temp and recycler folders are filtered out.
67
     * @var bool
68
     */
69
    public $ext_noTempRecyclerDirs = false;
70
71
    /**
72
     * override to not use a title attribute
73
     * @var string
74
     */
75
    public $titleAttrib = '';
76
77
    /**
78
     * override to use this treeName
79
     * does not need to be set in __construct()
80
     * @var string
81
     */
82
    public $treeName = 'folder';
83
84
    /**
85
     * override to use this domIdPrefix
86
     * @var string
87
     */
88
    public $domIdPrefix = 'folder';
89
90
    /**
91
     * Constructor function of the class
92
     */
93
    public function __construct()
94
    {
95
        parent::__construct();
96
        $this->init();
97
        $this->storages = $this->BE_USER->getFileStorages();
98
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
99
    }
100
101
    /**
102
     * Generate the plus/minus icon for the browsable tree.
103
     *
104
     * @param Folder $folderObject Entry folder object
105
     * @param int $subFolderCounter The current entry number
106
     * @param int $totalSubFolders The total number of entries. If equal to $a, a "bottom" element is returned.
107
     * @param int $nextCount The number of sub-elements to the current element.
108
     * @param bool $isExpanded The element was expanded to render subelements if this flag is set.
109
     *
110
     * @return string Image tag with the plus/minus icon.
111
     * @internal
112
     * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
113
     */
114
    public function PMicon($folderObject, $subFolderCounter, $totalSubFolders, $nextCount, $isExpanded)
115
    {
116
        $icon = '';
117
        if ($nextCount) {
118
            $cmd = $this->generateExpandCollapseParameter($this->bank, !$isExpanded, $folderObject);
119
            $icon = $this->PMiconATagWrap($icon, $cmd, !$isExpanded);
120
        }
121
        return $icon;
122
    }
123
124
    /**
125
     * Wrap the plus/minus icon in a link
126
     *
127
     * @param string $icon HTML string to wrap, probably an image tag.
128
     * @param string $cmd Command for 'PM' get var
129
     * @param bool $isExpand Whether to be expanded
130
     * @return string Link-wrapped input string
131
     * @internal
132
     */
133
    public function PMiconATagWrap($icon, $cmd, $isExpand = true)
134
    {
135
        if (empty($this->scope)) {
136
            $this->scope = [
137
                'class' => static::class,
138
                'script' => $this->thisScript,
139
                'ext_noTempRecyclerDirs' => $this->ext_noTempRecyclerDirs
140
            ];
141
        }
142
143
        if ($this->thisScript) {
144
            // Activates dynamic AJAX based tree
145
            $scopeData = serialize($this->scope);
146
            $scopeHash = GeneralUtility::hmac($scopeData);
147
            $js = htmlspecialchars('Tree.load(' . GeneralUtility::quoteJSvalue($cmd) . ', ' . (int)$isExpand . ', this, ' . GeneralUtility::quoteJSvalue($scopeData) . ', ' . GeneralUtility::quoteJSvalue($scopeHash) . ');');
148
            return '<a class="list-tree-control' . (!$isExpand ? ' list-tree-control-open' : ' list-tree-control-closed') . '" onclick="' . $js . '"><i class="fa"></i></a>';
149
        }
150
        return $icon;
151
    }
152
153
    /**
154
     * @param string $cmd
155
     * @param bool $isOpen
156
     * @return string
157
     */
158
    protected function renderPMIconAndLink($cmd, $isOpen)
159
    {
160
        $link = $this->thisScript ? ' href="' . htmlspecialchars($this->getThisScript() . 'PM=' . $cmd) . '"' : '';
161
        return '<a class="list-tree-control list-tree-control-' . ($isOpen ? 'open' : 'closed') . '"' . $link . '><i class="fa"></i></a>';
162
    }
163
164
    /**
165
     * Wrapping the folder icon
166
     *
167
     * @param string $icon The image tag for the icon
168
     * @param Folder $folderObject The row for the current element
169
     *
170
     * @return string The processed icon input value.
171
     * @internal
172
     */
173
    public function wrapIcon($icon, $folderObject)
174
    {
175
        // Add title attribute to input icon tag
176
        $theFolderIcon = '';
177
        // Wrap icon in click-menu link.
178
        if (!$this->ext_IconMode) {
179
            // Check storage access to wrap with click menu
180
            if (!$folderObject instanceof InaccessibleFolder) {
181
                $tableName = $this->getTableNameForClickMenu($folderObject);
182
                $theFolderIcon = BackendUtility::wrapClickMenuOnIcon($icon, $tableName, $folderObject->getCombinedIdentifier(), 'tree');
183
            }
184
        } elseif ($this->ext_IconMode === 'titlelink') {
185
            $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ',' . $this->bank . ');';
186
            $theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
187
        }
188
        return $theFolderIcon;
189
    }
190
191
    /**
192
     * Wrapping $title in a-tags.
193
     *
194
     * @param string $title Title string
195
     * @param Folder $folderObject the folder record
196
     * @param int $bank Bank pointer (which mount point number)
197
     *
198
     * @return string
199
     * @internal
200
     */
201
    public function wrapTitle($title, $folderObject, $bank = 0)
202
    {
203
        // Check storage access to wrap with click menu
204
        if ($folderObject instanceof InaccessibleFolder) {
205
            return $title;
206
        }
207
        $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ', this, ' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ', ' . $bank . ');';
208
        $tableName = $this->getTableNameForClickMenu($folderObject);
209
        $clickMenuParts = BackendUtility::wrapClickMenuOnIcon('', $tableName, $folderObject->getCombinedIdentifier(), 'tree', '', '', true);
210
211
        return '<a href="#" title="' . htmlspecialchars(strip_tags($title)) . '" onclick="' . htmlspecialchars($aOnClick) . '" ' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
0 ignored issues
show
Bug introduced by
$clickMenuParts of type string is incompatible with the type array expected by parameter $arr of TYPO3\CMS\Core\Utility\G...ty::implodeAttributes(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

211
        return '<a href="#" title="' . htmlspecialchars(strip_tags($title)) . '" onclick="' . htmlspecialchars($aOnClick) . '" ' . GeneralUtility::implodeAttributes(/** @scrutinizer ignore-type */ $clickMenuParts) . '>' . $title . '</a>';
Loading history...
212
    }
213
214
    /**
215
     * Returns the id from the record - for folders, this is an md5 hash.
216
     *
217
     * @param Folder $folderObject The folder object
218
     *
219
     * @return int The "uid" field value.
220
     */
221
    public function getId($folderObject)
222
    {
223
        return GeneralUtility::md5int($folderObject->getCombinedIdentifier());
224
    }
225
226
    /**
227
     * Returns jump-url parameter value.
228
     *
229
     * @param Folder $folderObject The folder object
230
     *
231
     * @return string The jump-url parameter.
232
     */
233
    public function getJumpToParam($folderObject)
234
    {
235
        return rawurlencode($folderObject->getCombinedIdentifier());
236
    }
237
238
    /**
239
     * Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
240
     * '_title' is used for setting an alternative title for folders.
241
     *
242
     * @param array $row The input row array (where the key "_title" is used for the title)
243
     * @param int $titleLen Title length (30)
244
     * @return string The title
245
     */
246
    public function getTitleStr($row, $titleLen = 30)
247
    {
248
        return $row['_title'] ?? parent::getTitleStr($row, $titleLen);
249
    }
250
251
    /**
252
     * Returns the value for the image "title" attribute
253
     *
254
     * @param Folder $folderObject The folder to be used
255
     *
256
     * @return string The attribute value (is htmlspecialchared() already)
257
     */
258
    public function getTitleAttrib($folderObject)
259
    {
260
        return htmlspecialchars($folderObject->getName());
261
    }
262
263
    /**
264
     * Will create and return the HTML code for a browsable tree of folders.
265
     * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
266
     *
267
     * @return string HTML code for the browsable tree
268
     */
269
    public function getBrowsableTree()
270
    {
271
        // Get stored tree structure AND updating it if needed according to incoming PM GET var.
272
        $this->initializePositionSaving();
273
        // Init done:
274
        $treeItems = [];
275
        // Traverse mounts:
276
        foreach ($this->storages as $storageObject) {
277
            $this->getBrowseableTreeForStorage($storageObject);
278
            // Add tree:
279
            $treeItems = array_merge($treeItems, $this->tree);
280
        }
281
        return $this->printTree($treeItems);
282
    }
283
284
    /**
285
     * Get a tree for one storage
286
     *
287
     * @param ResourceStorage $storageObject
288
     */
289
    public function getBrowseableTreeForStorage(ResourceStorage $storageObject)
290
    {
291
        // If there are filemounts, show each, otherwise just the rootlevel folder
292
        $fileMounts = $storageObject->getFileMounts();
293
        $rootLevelFolders = [];
294
        if (!empty($fileMounts)) {
295
            foreach ($fileMounts as $fileMountInfo) {
296
                $rootLevelFolders[] = [
297
                    'folder' => $fileMountInfo['folder'],
298
                    'name' => $fileMountInfo['title']
299
                ];
300
            }
301
        } elseif ($this->BE_USER->isAdmin()) {
302
            $rootLevelFolders[] = [
303
                'folder' => $storageObject->getRootLevelFolder(),
304
                'name' => $storageObject->getName()
305
            ];
306
        }
307
        // Clean the tree
308
        $this->reset();
309
        // Go through all "root level folders" of this tree (can be the rootlevel folder or any file mount points)
310
        foreach ($rootLevelFolders as $rootLevelFolderInfo) {
311
            /** @var $rootLevelFolder Folder */
312
            $rootLevelFolder = $rootLevelFolderInfo['folder'];
313
            $rootLevelFolderName = $rootLevelFolderInfo['name'];
314
            $folderHashSpecUID = GeneralUtility::md5int($rootLevelFolder->getCombinedIdentifier());
315
            $this->specUIDmap[$folderHashSpecUID] = $rootLevelFolder->getCombinedIdentifier();
316
            // Hash key
317
            $storageHashNumber = $this->getShortHashNumberForStorage($storageObject, $rootLevelFolder);
318
            // Set first:
319
            $this->bank = $storageHashNumber;
320
            $isOpen = $this->stored[$storageHashNumber][$folderHashSpecUID] || $this->expandFirst;
321
            // Set PM icon:
322
            $cmd = $this->generateExpandCollapseParameter($this->bank, !$isOpen, $rootLevelFolder);
323
            // Only show and link icon if storage is browseable
324
            if (!$storageObject->isBrowsable() || $this->getNumberOfSubfolders($rootLevelFolder) === 0) {
325
                $firstHtml = '';
326
            } else {
327
                $firstHtml = $this->renderPMIconAndLink($cmd, $isOpen);
328
            }
329
            // Mark a storage which is not online, as offline
330
            // maybe someday there will be a special icon for this
331
            if ($storageObject->isOnline() === false) {
332
                $rootLevelFolderName .= ' (' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_file.xlf:sys_file_storage.isOffline') . ')';
333
            }
334
            // Preparing rootRec for the mount
335
            $icon = $this->iconFactory->getIconForResource($rootLevelFolder, Icon::SIZE_SMALL, null, ['mount-root' => true]);
336
            $firstHtml .= $this->wrapIcon($icon, $rootLevelFolder);
337
            $row = [
338
                'uid' => $folderHashSpecUID,
339
                'title' => $rootLevelFolderName,
340
                'path' => $rootLevelFolder->getCombinedIdentifier(),
341
                'folder' => $rootLevelFolder
342
            ];
343
            // Add the storage root to ->tree
344
            $this->tree[] = [
345
                'HTML' => $firstHtml,
346
                'row' => $row,
347
                'bank' => $this->bank,
348
                // hasSub is TRUE when the root of the storage is expanded
349
                'hasSub' => $isOpen && $storageObject->isBrowsable(),
350
                'invertedDepth' => 1000,
351
            ];
352
            // If the mount is expanded, go down:
353
            if ($isOpen && $storageObject->isBrowsable()) {
354
                // Set depth:
355
                $this->getFolderTree($rootLevelFolder, 999);
356
            }
357
        }
358
    }
359
360
    /**
361
     * Fetches the data for the tree
362
     *
363
     * @param Folder $folderObject the folderobject
364
     * @param int $depth Max depth (recursivity limit)
365
     * @param string $type HTML-code prefix for recursive calls.
366
     *
367
     * @return int The count of items on the level
368
     * @see getBrowsableTree()
369
     */
370
    public function getFolderTree(Folder $folderObject, $depth = 999, $type = '')
371
    {
372
        $depth = (int)$depth;
373
374
        // This generates the directory tree
375
        /* array of \TYPO3\CMS\Core\Resource\Folder */
376
        if ($folderObject instanceof InaccessibleFolder) {
377
            $subFolders = [];
378
        } else {
379
            $subFolders = $folderObject->getSubfolders();
380
            $subFolders = \TYPO3\CMS\Core\Resource\Utility\ListUtility::resolveSpecialFolderNames($subFolders);
381
            uksort($subFolders, 'strnatcasecmp');
382
        }
383
384
        $totalSubFolders = count($subFolders);
385
        $HTML = '';
386
        $subFolderCounter = 0;
387
        $treeKey = '';
388
        /** @var Folder $subFolder */
389
        foreach ($subFolders as $subFolderName => $subFolder) {
390
            $subFolderCounter++;
391
            // Reserve space.
392
            $this->tree[] = [];
393
            // Get the key for this space
394
            end($this->tree);
395
            $isLocked = $subFolder instanceof InaccessibleFolder;
396
            $treeKey = key($this->tree);
397
            $specUID = GeneralUtility::md5int($subFolder->getCombinedIdentifier());
398
            $this->specUIDmap[$specUID] = $subFolder->getCombinedIdentifier();
399
            $row = [
400
                'uid' => $specUID,
401
                'path' => $subFolder->getCombinedIdentifier(),
402
                'title' => $subFolderName,
403
                'folder' => $subFolder
404
            ];
405
            // Make a recursive call to the next level
406
            if (!$isLocked && $depth > 1 && $this->expandNext($specUID)) {
407
                $nextCount = $this->getFolderTree($subFolder, $depth - 1, $type);
408
                // Set "did expand" flag
409
                $isOpen = 1;
410
            } else {
411
                $nextCount = $isLocked ? 0 : $this->getNumberOfSubfolders($subFolder);
412
                // Clear "did expand" flag
413
                $isOpen = 0;
414
            }
415
            // Set HTML-icons, if any:
416
            if ($this->makeHTML) {
417
                $HTML = $this->PMicon($subFolder, $subFolderCounter, $totalSubFolders, $nextCount, $isOpen);
0 ignored issues
show
Bug introduced by
$isOpen of type integer is incompatible with the type boolean expected by parameter $isExpanded of TYPO3\CMS\Backend\Tree\V...olderTreeView::PMicon(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

417
                $HTML = $this->PMicon($subFolder, $subFolderCounter, $totalSubFolders, $nextCount, /** @scrutinizer ignore-type */ $isOpen);
Loading history...
418
                $type = '';
419
420
                $role = $subFolder->getRole();
421
                if ($role !== FolderInterface::ROLE_DEFAULT) {
422
                    $row['_title'] = '<strong>' . $subFolderName . '</strong>';
423
                }
424
                $icon = '<span title="' . htmlspecialchars($subFolderName) . '">'
425
                    . $this->iconFactory->getIconForResource($subFolder, Icon::SIZE_SMALL, null, ['folder-open' => (bool)$isOpen])
426
                    . '</span>';
427
                $HTML .= $this->wrapIcon($icon, $subFolder);
428
            }
429
            // Finally, add the row/HTML content to the ->tree array in the reserved key.
430
            $this->tree[$treeKey] = [
431
                'row' => $row,
432
                'HTML' => $HTML,
433
                'hasSub' => $nextCount && $this->expandNext($specUID),
434
                'isFirst' => $subFolderCounter == 1,
435
                'isLast' => false,
436
                'invertedDepth' => $depth,
437
                'bank' => $this->bank
438
            ];
439
        }
440
        if ($subFolderCounter > 0) {
441
            $this->tree[$treeKey]['isLast'] = true;
442
        }
443
        return $totalSubFolders;
444
    }
445
446
    /**
447
     * Compiles the HTML code for displaying the structure found inside the ->tree array
448
     *
449
     * @param array|string $treeItems "tree-array" - if blank string, the internal ->tree array is used.
450
     * @return string The HTML code for the tree
451
     */
452
    public function printTree($treeItems = '')
453
    {
454
        $doExpand = false;
455
        $doCollapse = false;
456
        $ajaxOutput = '';
457
        $titleLength = (int)$this->BE_USER->uc['titleLen'];
458
        if (!is_array($treeItems)) {
459
            $treeItems = $this->tree;
460
        }
461
462
        if (empty($treeItems)) {
463
            $message = GeneralUtility::makeInstance(
464
                FlashMessage::class,
465
                $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.message'),
0 ignored issues
show
Bug introduced by
$this->getLanguageServic...iew.noFolders.message') of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

465
                /** @scrutinizer ignore-type */ $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.message'),
Loading history...
466
                $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.title'),
467
                FlashMessage::INFO
468
            );
469
            /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
470
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
471
            /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
472
            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
473
            $defaultFlashMessageQueue->enqueue($message);
474
            return $defaultFlashMessageQueue->renderFlashMessages();
475
        }
476
477
        $expandedFolderHash = '';
478
        $invertedDepthOfAjaxRequestedItem = 0;
479
        $out = '<ul class="list-tree list-tree-root">';
480
        // Evaluate AJAX request
481
        if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
482
            list(, $expandCollapseCommand, $expandedFolderHash, ) = $this->evaluateExpandCollapseParameter();
483
            if ($expandCollapseCommand == 1) {
484
                $doExpand = true;
485
            } else {
486
                $doCollapse = true;
487
            }
488
        }
489
        // We need to count the opened <ul>'s every time we dig into another level,
490
        // so we know how many we have to close when all children are done rendering
491
        $closeDepth = [];
492
        foreach ($treeItems as $treeItem) {
493
            /** @var $folderObject Folder */
494
            $folderObject = $treeItem['row']['folder'];
495
            $classAttr = $treeItem['row']['_CSSCLASS'] ?? '';
496
            $folderIdentifier = $folderObject->getCombinedIdentifier();
497
            // this is set if the AJAX request has just opened this folder (via the PM command)
498
            $isExpandedFolderIdentifier = $expandedFolderHash == GeneralUtility::md5int($folderIdentifier);
499
            $idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($folderObject) . '_' . $treeItem['bank']);
500
            $itemHTML = '';
501
            // If this item is the start of a new level,
502
            // then a new level <ul> is needed, but not in ajax mode
503
            if (!empty($treeItem['isFirst']) && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
504
                $itemHTML = '<ul class="list-tree">';
505
            }
506
            // Add CSS classes to the list item
507
            if (!empty($treeItem['hasSub'])) {
508
                $classAttr .= ' list-tree-control-open';
509
            }
510
            $itemHTML .= '
511
				<li id="' . $idAttr . '" ' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '') . '><span class="list-tree-group">' . $treeItem['HTML'] . $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLength), $folderObject, $treeItem['bank']) . '</span>';
512
            if (empty($treeItem['hasSub'])) {
513
                $itemHTML .= '</li>';
514
            }
515
            // We have to remember if this is the last one
516
            // on level X so the last child on level X+1 closes the <ul>-tag
517
            if (!empty($treeItem['isLast']) && !($doExpand && $isExpandedFolderIdentifier)) {
518
                $closeDepth[$treeItem['invertedDepth']] = 1;
519
            }
520
            // If this is the last one and does not have subitems, we need to close
521
            // the tree as long as the upper levels have last items too
522
            if (!empty($treeItem['isLast']) && empty($treeItem['hasSub']) && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
523
                for ($i = $treeItem['invertedDepth']; !empty($closeDepth[$i]); $i++) {
524
                    $closeDepth[$i] = 0;
525
                    $itemHTML .= '</ul></li>';
526
                }
527
            }
528
            // Ajax request: collapse
529
            if ($doCollapse && $isExpandedFolderIdentifier) {
530
                $this->ajaxStatus = true;
531
                return $itemHTML;
532
            }
533
            // Ajax request: expand
534
            if ($doExpand && $isExpandedFolderIdentifier) {
535
                $ajaxOutput .= $itemHTML;
536
                $invertedDepthOfAjaxRequestedItem = $treeItem['invertedDepth'];
537
            } elseif ($invertedDepthOfAjaxRequestedItem) {
538
                if ($treeItem['invertedDepth'] && ($treeItem['invertedDepth'] < $invertedDepthOfAjaxRequestedItem)) {
539
                    $ajaxOutput .= $itemHTML;
540
                } else {
541
                    $this->ajaxStatus = true;
542
                    return $ajaxOutput;
543
                }
544
            }
545
            $out .= $itemHTML;
546
        }
547
        // If this is an AJAX request, output directly
548
        if ($ajaxOutput) {
549
            $this->ajaxStatus = true;
550
            return $ajaxOutput;
551
        }
552
        // Finally close the first ul
553
        $out .= '</ul>';
554
        return $out;
555
    }
556
557
    /**
558
     * Returns table name for click menu
559
     *
560
     * @param Folder $folderObject
561
     * @return string
562
     */
563
    protected function getTableNameForClickMenu(Folder $folderObject)
564
    {
565
        if (strpos($folderObject->getRole(), FolderInterface::ROLE_MOUNT) !== false) {
566
            $tableName = 'sys_filemounts';
567
        } elseif ($folderObject->getIdentifier() === $folderObject->getStorage()->getRootLevelFolder()->getIdentifier()) {
568
            $tableName = 'sys_file_storage';
569
        } else {
570
            $tableName = 'sys_file';
571
        }
572
        return $tableName;
573
    }
574
575
    /**
576
     * Counts the number of directories in a file path.
577
     *
578
     * @param Folder $folderObject File path.
579
     *
580
     * @return int
581
     */
582
    public function getNumberOfSubfolders(Folder $folderObject)
583
    {
584
        $subFolders = $folderObject->getSubfolders();
585
        return count($subFolders);
586
    }
587
588
    /**
589
     * Get stored tree structure AND updating it if needed according to incoming PM GET var.
590
     *
591
     * @access private
592
     */
593
    public function initializePositionSaving()
594
    {
595
        // Get stored tree structure:
596
        $this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName]);
597
        $this->getShortHashNumberForStorage();
598
        // PM action:
599
        // (If an plus/minus icon has been clicked,
600
        // the PM GET var is sent and we must update the stored positions in the tree):
601
        // 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
602
        list($storageHashNumber, $doExpand, $numericFolderHash, $treeName) = $this->evaluateExpandCollapseParameter();
603
        if ($treeName && $treeName == $this->treeName) {
604
            if (in_array($storageHashNumber, $this->storageHashNumbers)) {
605
                if ($doExpand == 1) {
606
                    // Set
607
                    $this->stored[$storageHashNumber][$numericFolderHash] = 1;
608
                } else {
609
                    // Clear
610
                    unset($this->stored[$storageHashNumber][$numericFolderHash]);
611
                }
612
                $this->savePosition();
613
            }
614
        }
615
    }
616
617
    /**
618
     * Helper method to map md5-hash to shorter number
619
     *
620
     * @param ResourceStorage $storageObject
621
     * @param Folder $startingPointFolder
622
     *
623
     * @return int
624
     */
625
    protected function getShortHashNumberForStorage(ResourceStorage $storageObject = null, Folder $startingPointFolder = null)
626
    {
627
        if (!$this->storageHashNumbers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->storageHashNumbers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
628
            $this->storageHashNumbers = [];
629
            foreach ($this->storages as $storageUid => $storage) {
630
                $fileMounts = $storage->getFileMounts();
631
                if (!empty($fileMounts)) {
632
                    foreach ($fileMounts as $fileMount) {
633
                        $nkey = hexdec(substr(GeneralUtility::md5int($fileMount['folder']->getCombinedIdentifier()), 0, 4));
634
                        $this->storageHashNumbers[$storageUid . $fileMount['folder']->getCombinedIdentifier()] = $nkey;
635
                    }
636
                } else {
637
                    $folder = $storage->getRootLevelFolder();
638
                    $nkey = hexdec(substr(GeneralUtility::md5int($folder->getCombinedIdentifier()), 0, 4));
639
                    $this->storageHashNumbers[$storageUid . $folder->getCombinedIdentifier()] = $nkey;
640
                }
641
            }
642
        }
643
        if ($storageObject) {
644
            if ($startingPointFolder) {
645
                return $this->storageHashNumbers[$storageObject->getUid() . $startingPointFolder->getCombinedIdentifier()];
646
            }
647
            return $this->storageHashNumbers[$storageObject->getUid()];
648
        }
649
        return null;
650
    }
651
652
    /**
653
     * Gets the values from the Expand/Collapse Parameter (&PM)
654
     * previously known as "PM" (plus/minus)
655
     * PM action:
656
     * (If an plus/minus icon has been clicked,
657
     * the PM GET var is sent and we must update the stored positions in the tree):
658
     * 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
659
     *
660
     * @param string $PM The "plus/minus" command
661
     * @return array
662
     */
663
    protected function evaluateExpandCollapseParameter($PM = null)
664
    {
665
        if ($PM === null) {
666
            $PM = GeneralUtility::_GP('PM');
667
            // IE takes anchor as parameter
668
            if (($PMpos = strpos($PM, '#')) !== false) {
669
                $PM = substr($PM, 0, $PMpos);
670
            }
671
        }
672
        // Take the first three parameters
673
        list($mountKey, $doExpand, $folderIdentifier) = array_pad(explode('_', $PM, 3), 3, null);
674
        // In case the folder identifier contains "_", we just need to get the fourth/last parameter
675
        list($folderIdentifier, $treeName) = array_pad(GeneralUtility::revExplode('_', $folderIdentifier, 2), 2, null);
676
        return [
677
            $mountKey,
678
            $doExpand,
679
            $folderIdentifier,
680
            $treeName
681
        ];
682
    }
683
684
    /**
685
     * Generates the "PM" string to sent to expand/collapse items
686
     *
687
     * @param string $mountKey The mount key / storage UID
688
     * @param bool $doExpand Whether to expand/collapse
689
     * @param Folder $folderObject The folder object
690
     * @param string $treeName The name of the tree
691
     *
692
     * @return string
693
     */
694
    protected function generateExpandCollapseParameter($mountKey = null, $doExpand = false, Folder $folderObject = null, $treeName = null)
695
    {
696
        $parts = [
697
            $mountKey ?? $this->bank,
698
            $doExpand == 1 ? 1 : 0,
699
            $folderObject !== null ? GeneralUtility::md5int($folderObject->getCombinedIdentifier()) : '',
700
            $treeName ?? $this->treeName
701
        ];
702
        return implode('_', $parts);
703
    }
704
705
    /**
706
     * Gets the AJAX status.
707
     *
708
     * @return bool
709
     */
710
    public function getAjaxStatus()
711
    {
712
        return $this->ajaxStatus;
713
    }
714
715
    /**
716
     * @return LanguageService
717
     */
718
    protected function getLanguageService()
719
    {
720
        return $GLOBALS['LANG'];
721
    }
722
}
723