Revert "[TASK] Avoid slow array functions in loops"
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Tree / View / FolderTreeView.php
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 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
34 */
35 class FolderTreeView extends AbstractTreeView
36 {
37 /**
38 * The users' file Storages
39 *
40 * @var ResourceStorage[]
41 */
42 protected $storages;
43
44 /**
45 * @var array
46 */
47 protected $storageHashNumbers;
48
49 /**
50 * Indicates, whether the AJAX call was successful,
51 * i.e. the requested page has been found
52 *
53 * @var bool
54 */
55 protected $ajaxStatus = false;
56
57 /**
58 * @var array
59 */
60 protected $scope;
61
62 /**
63 * @var IconFactory
64 */
65 protected $iconFactory;
66
67 /**
68 * override to not use a title attribute
69 * @var string
70 */
71 public $titleAttrib = '';
72
73 /**
74 * override to use this treeName
75 * does not need to be set in __construct()
76 * @var string
77 */
78 public $treeName = 'folder';
79
80 /**
81 * override to use this domIdPrefix
82 * @var string
83 */
84 public $domIdPrefix = 'folder';
85
86 /**
87 * Constructor function of the class
88 */
89 public function __construct()
90 {
91 parent::__construct();
92 $this->init();
93 $this->storages = $this->BE_USER->getFileStorages();
94 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
95 }
96
97 /**
98 * Generate the plus/minus icon for the browsable tree.
99 *
100 * @param Folder $folderObject Entry folder object
101 * @param int $subFolderCounter The current entry number
102 * @param int $totalSubFolders The total number of entries. If equal to $a, a "bottom" element is returned.
103 * @param int $nextCount The number of sub-elements to the current element.
104 * @param bool $isExpanded The element was expanded to render subelements if this flag is set.
105 *
106 * @return string Image tag with the plus/minus icon.
107 * @internal
108 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
109 */
110 public function PMicon($folderObject, $subFolderCounter, $totalSubFolders, $nextCount, $isExpanded)
111 {
112 $icon = '';
113 if ($nextCount) {
114 $cmd = $this->generateExpandCollapseParameter($this->bank, !$isExpanded, $folderObject);
115 $icon = $this->PMiconATagWrap($icon, $cmd, !$isExpanded);
116 }
117 return $icon;
118 }
119
120 /**
121 * Wrap the plus/minus icon in a link
122 *
123 * @param string $icon HTML string to wrap, probably an image tag.
124 * @param string $cmd Command for 'PM' get var
125 * @param bool $isExpand Whether to be expanded
126 * @return string Link-wrapped input string
127 * @internal
128 */
129 public function PMiconATagWrap($icon, $cmd, $isExpand = true)
130 {
131 if (empty($this->scope)) {
132 $this->scope = [
133 'class' => static::class,
134 'script' => $this->thisScript,
135 ];
136 }
137
138 if ($this->thisScript) {
139 // Activates dynamic AJAX based tree
140 $scopeData = serialize($this->scope);
141 $scopeHash = GeneralUtility::hmac($scopeData);
142 $js = htmlspecialchars('Tree.load(' . GeneralUtility::quoteJSvalue($cmd) . ', ' . (int)$isExpand . ', this, ' . GeneralUtility::quoteJSvalue($scopeData) . ', ' . GeneralUtility::quoteJSvalue($scopeHash) . ');');
143 return '<a class="list-tree-control' . (!$isExpand ? ' list-tree-control-open' : ' list-tree-control-closed') . '" onclick="' . $js . '"><i class="fa"></i></a>';
144 }
145 return $icon;
146 }
147
148 /**
149 * @param string $cmd
150 * @param bool $isOpen
151 * @return string
152 */
153 protected function renderPMIconAndLink($cmd, $isOpen)
154 {
155 $link = $this->thisScript ? ' href="' . htmlspecialchars($this->getThisScript() . 'PM=' . $cmd) . '"' : '';
156 return '<a class="list-tree-control list-tree-control-' . ($isOpen ? 'open' : 'closed') . '"' . $link . '><i class="fa"></i></a>';
157 }
158
159 /**
160 * Wrapping the folder icon
161 *
162 * @param string $icon The image tag for the icon
163 * @param Folder $folderObject The row for the current element
164 *
165 * @return string The processed icon input value.
166 * @internal
167 */
168 public function wrapIcon($icon, $folderObject)
169 {
170 // Add title attribute to input icon tag
171 $theFolderIcon = '';
172 // Wrap icon in click-menu link.
173 if (!$this->ext_IconMode) {
174 // Check storage access to wrap with click menu
175 if (!$folderObject instanceof InaccessibleFolder) {
176 $tableName = $this->getTableNameForClickMenu($folderObject);
177 $theFolderIcon = BackendUtility::wrapClickMenuOnIcon($icon, $tableName, $folderObject->getCombinedIdentifier(), 'tree');
178 }
179 } elseif ($this->ext_IconMode === 'titlelink') {
180 $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ',' . $this->bank . ');';
181 $theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
182 }
183 return $theFolderIcon;
184 }
185
186 /**
187 * Wrapping $title in a-tags.
188 *
189 * @param string $title Title string
190 * @param Folder $folderObject the folder record
191 * @param int $bank Bank pointer (which mount point number)
192 *
193 * @return string
194 * @internal
195 */
196 public function wrapTitle($title, $folderObject, $bank = 0)
197 {
198 // Check storage access to wrap with click menu
199 if ($folderObject instanceof InaccessibleFolder) {
200 return $title;
201 }
202 $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ', this, ' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ', ' . $bank . ');';
203 $tableName = $this->getTableNameForClickMenu($folderObject);
204 $clickMenuParts = BackendUtility::wrapClickMenuOnIcon('', $tableName, $folderObject->getCombinedIdentifier(), 'tree', '', '', true);
205
206 return '<a href="#" title="' . htmlspecialchars(strip_tags($title)) . '" onclick="' . htmlspecialchars($aOnClick) . '" ' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
207 }
208
209 /**
210 * Returns the id from the record - for folders, this is an md5 hash.
211 *
212 * @param Folder $folderObject The folder object
213 *
214 * @return int The "uid" field value.
215 */
216 public function getId($folderObject)
217 {
218 return GeneralUtility::md5int($folderObject->getCombinedIdentifier());
219 }
220
221 /**
222 * Returns jump-url parameter value.
223 *
224 * @param Folder $folderObject The folder object
225 *
226 * @return string The jump-url parameter.
227 */
228 public function getJumpToParam($folderObject)
229 {
230 return rawurlencode($folderObject->getCombinedIdentifier());
231 }
232
233 /**
234 * Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
235 * '_title' is used for setting an alternative title for folders.
236 *
237 * @param array $row The input row array (where the key "_title" is used for the title)
238 * @param int $titleLen Title length (30)
239 * @return string The title
240 */
241 public function getTitleStr($row, $titleLen = 30)
242 {
243 return $row['_title'] ?? parent::getTitleStr($row, $titleLen);
244 }
245
246 /**
247 * Returns the value for the image "title" attribute
248 *
249 * @param Folder $folderObject The folder to be used
250 *
251 * @return string The attribute value (is htmlspecialchared() already)
252 */
253 public function getTitleAttrib($folderObject)
254 {
255 return htmlspecialchars($folderObject->getName());
256 }
257
258 /**
259 * Will create and return the HTML code for a browsable tree of folders.
260 * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
261 *
262 * @return string HTML code for the browsable tree
263 */
264 public function getBrowsableTree()
265 {
266 // Get stored tree structure AND updating it if needed according to incoming PM GET var.
267 $this->initializePositionSaving();
268 // Init done:
269 $treeItems = [];
270 // Traverse mounts:
271 foreach ($this->storages as $storageObject) {
272 $this->getBrowseableTreeForStorage($storageObject);
273 // Add tree:
274 $treeItems = array_merge($treeItems, $this->tree);
275 }
276 return $this->printTree($treeItems);
277 }
278
279 /**
280 * Get a tree for one storage
281 *
282 * @param ResourceStorage $storageObject
283 */
284 public function getBrowseableTreeForStorage(ResourceStorage $storageObject)
285 {
286 // If there are filemounts, show each, otherwise just the rootlevel folder
287 $fileMounts = $storageObject->getFileMounts();
288 $rootLevelFolders = [];
289 if (!empty($fileMounts)) {
290 foreach ($fileMounts as $fileMountInfo) {
291 $rootLevelFolders[] = [
292 'folder' => $fileMountInfo['folder'],
293 'name' => $fileMountInfo['title']
294 ];
295 }
296 } elseif ($this->BE_USER->isAdmin()) {
297 $rootLevelFolders[] = [
298 'folder' => $storageObject->getRootLevelFolder(),
299 'name' => $storageObject->getName()
300 ];
301 }
302 // Clean the tree
303 $this->reset();
304 // Go through all "root level folders" of this tree (can be the rootlevel folder or any file mount points)
305 foreach ($rootLevelFolders as $rootLevelFolderInfo) {
306 /** @var Folder $rootLevelFolder */
307 $rootLevelFolder = $rootLevelFolderInfo['folder'];
308 $rootLevelFolderName = $rootLevelFolderInfo['name'];
309 $folderHashSpecUID = GeneralUtility::md5int($rootLevelFolder->getCombinedIdentifier());
310 $this->specUIDmap[$folderHashSpecUID] = $rootLevelFolder->getCombinedIdentifier();
311 // Hash key
312 $storageHashNumber = $this->getShortHashNumberForStorage($storageObject, $rootLevelFolder);
313 // Set first:
314 $this->bank = $storageHashNumber;
315 $isOpen = $this->stored[$storageHashNumber][$folderHashSpecUID] || $this->expandFirst;
316 // Set PM icon:
317 $cmd = $this->generateExpandCollapseParameter($this->bank, !$isOpen, $rootLevelFolder);
318 // Only show and link icon if storage is browseable
319 if (!$storageObject->isBrowsable() || $this->getNumberOfSubfolders($rootLevelFolder) === 0) {
320 $firstHtml = '';
321 } else {
322 $firstHtml = $this->renderPMIconAndLink($cmd, $isOpen);
323 }
324 // Mark a storage which is not online, as offline
325 // maybe someday there will be a special icon for this
326 if ($storageObject->isOnline() === false) {
327 $rootLevelFolderName .= ' (' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_file.xlf:sys_file_storage.isOffline') . ')';
328 }
329 // Preparing rootRec for the mount
330 $icon = $this->iconFactory->getIconForResource($rootLevelFolder, Icon::SIZE_SMALL, null, ['mount-root' => true]);
331 $firstHtml .= $this->wrapIcon($icon, $rootLevelFolder);
332 $row = [
333 'uid' => $folderHashSpecUID,
334 'title' => $rootLevelFolderName,
335 'path' => $rootLevelFolder->getCombinedIdentifier(),
336 'folder' => $rootLevelFolder
337 ];
338 // Add the storage root to ->tree
339 $this->tree[] = [
340 'HTML' => $firstHtml,
341 'row' => $row,
342 'bank' => $this->bank,
343 // hasSub is TRUE when the root of the storage is expanded
344 'hasSub' => $isOpen && $storageObject->isBrowsable(),
345 'invertedDepth' => 1000,
346 ];
347 // If the mount is expanded, go down:
348 if ($isOpen && $storageObject->isBrowsable()) {
349 // Set depth:
350 $this->getFolderTree($rootLevelFolder, 999);
351 }
352 }
353 }
354
355 /**
356 * Fetches the data for the tree
357 *
358 * @param Folder $folderObject the folderobject
359 * @param int $depth Max depth (recursivity limit)
360 * @param string $type HTML-code prefix for recursive calls.
361 *
362 * @return int The count of items on the level
363 * @see getBrowsableTree()
364 */
365 public function getFolderTree(Folder $folderObject, $depth = 999, $type = '')
366 {
367 $depth = (int)$depth;
368
369 // This generates the directory tree
370 /* array of \TYPO3\CMS\Core\Resource\Folder */
371 if ($folderObject instanceof InaccessibleFolder) {
372 $subFolders = [];
373 } else {
374 $subFolders = $folderObject->getSubfolders();
375 $subFolders = \TYPO3\CMS\Core\Resource\Utility\ListUtility::resolveSpecialFolderNames($subFolders);
376 uksort($subFolders, 'strnatcasecmp');
377 }
378
379 $totalSubFolders = count($subFolders);
380 $HTML = '';
381 $subFolderCounter = 0;
382 $treeKey = '';
383 /** @var Folder $subFolder */
384 foreach ($subFolders as $subFolderName => $subFolder) {
385 $subFolderCounter++;
386 // Reserve space.
387 $this->tree[] = [];
388 // Get the key for this space
389 end($this->tree);
390 $isLocked = $subFolder instanceof InaccessibleFolder;
391 $treeKey = key($this->tree);
392 $specUID = GeneralUtility::md5int($subFolder->getCombinedIdentifier());
393 $this->specUIDmap[$specUID] = $subFolder->getCombinedIdentifier();
394 $row = [
395 'uid' => $specUID,
396 'path' => $subFolder->getCombinedIdentifier(),
397 'title' => $subFolderName,
398 'folder' => $subFolder
399 ];
400 // Make a recursive call to the next level
401 if (!$isLocked && $depth > 1 && $this->expandNext($specUID)) {
402 $nextCount = $this->getFolderTree($subFolder, $depth - 1, $type);
403 // Set "did expand" flag
404 $isOpen = 1;
405 } else {
406 $nextCount = $isLocked ? 0 : $this->getNumberOfSubfolders($subFolder);
407 // Clear "did expand" flag
408 $isOpen = 0;
409 }
410 // Set HTML-icons, if any:
411 if ($this->makeHTML) {
412 $HTML = $this->PMicon($subFolder, $subFolderCounter, $totalSubFolders, $nextCount, $isOpen);
413 $type = '';
414
415 $role = $subFolder->getRole();
416 if ($role !== FolderInterface::ROLE_DEFAULT) {
417 $row['_title'] = '<strong>' . $subFolderName . '</strong>';
418 }
419 $icon = '<span title="' . htmlspecialchars($subFolderName) . '">'
420 . $this->iconFactory->getIconForResource($subFolder, Icon::SIZE_SMALL, null, ['folder-open' => (bool)$isOpen])
421 . '</span>';
422 $HTML .= $this->wrapIcon($icon, $subFolder);
423 }
424 // Finally, add the row/HTML content to the ->tree array in the reserved key.
425 $this->tree[$treeKey] = [
426 'row' => $row,
427 'HTML' => $HTML,
428 'hasSub' => $nextCount && $this->expandNext($specUID),
429 'isFirst' => $subFolderCounter == 1,
430 'isLast' => false,
431 'invertedDepth' => $depth,
432 'bank' => $this->bank
433 ];
434 }
435 if ($subFolderCounter > 0) {
436 $this->tree[$treeKey]['isLast'] = true;
437 }
438 return $totalSubFolders;
439 }
440
441 /**
442 * Compiles the HTML code for displaying the structure found inside the ->tree array
443 *
444 * @param array|string $treeItems "tree-array" - if blank string, the internal ->tree array is used.
445 * @return string The HTML code for the tree
446 */
447 public function printTree($treeItems = '')
448 {
449 $doExpand = false;
450 $doCollapse = false;
451 $ajaxOutput = '';
452 $titleLength = (int)$this->BE_USER->uc['titleLen'];
453 if (!is_array($treeItems)) {
454 $treeItems = $this->tree;
455 }
456
457 if (empty($treeItems)) {
458 $message = GeneralUtility::makeInstance(
459 FlashMessage::class,
460 $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.message'),
461 $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.title'),
462 FlashMessage::INFO
463 );
464 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
465 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
466 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
467 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
468 $defaultFlashMessageQueue->enqueue($message);
469 return $defaultFlashMessageQueue->renderFlashMessages();
470 }
471
472 $expandedFolderHash = '';
473 $invertedDepthOfAjaxRequestedItem = 0;
474 $out = '<ul class="list-tree list-tree-root">';
475 // Evaluate AJAX request
476 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
477 list(, $expandCollapseCommand, $expandedFolderHash, ) = $this->evaluateExpandCollapseParameter();
478 if ($expandCollapseCommand == 1) {
479 $doExpand = true;
480 } else {
481 $doCollapse = true;
482 }
483 }
484 // We need to count the opened <ul>'s every time we dig into another level,
485 // so we know how many we have to close when all children are done rendering
486 $closeDepth = [];
487 foreach ($treeItems as $treeItem) {
488 /** @var Folder $folderObject */
489 $folderObject = $treeItem['row']['folder'];
490 $classAttr = $treeItem['row']['_CSSCLASS'] ?? '';
491 $folderIdentifier = $folderObject->getCombinedIdentifier();
492 // this is set if the AJAX request has just opened this folder (via the PM command)
493 $isExpandedFolderIdentifier = $expandedFolderHash == GeneralUtility::md5int($folderIdentifier);
494 $idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($folderObject) . '_' . $treeItem['bank']);
495 $itemHTML = '';
496 // If this item is the start of a new level,
497 // then a new level <ul> is needed, but not in ajax mode
498 if (!empty($treeItem['isFirst']) && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
499 $itemHTML = '<ul class="list-tree">';
500 }
501 // Add CSS classes to the list item
502 if (!empty($treeItem['hasSub'])) {
503 $classAttr .= ' list-tree-control-open';
504 }
505 $itemHTML .= '
506 <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>';
507 if (empty($treeItem['hasSub'])) {
508 $itemHTML .= '</li>';
509 }
510 // We have to remember if this is the last one
511 // on level X so the last child on level X+1 closes the <ul>-tag
512 if (!empty($treeItem['isLast']) && !($doExpand && $isExpandedFolderIdentifier)) {
513 $closeDepth[$treeItem['invertedDepth']] = 1;
514 }
515 // If this is the last one and does not have subitems, we need to close
516 // the tree as long as the upper levels have last items too
517 if (!empty($treeItem['isLast']) && empty($treeItem['hasSub']) && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
518 for ($i = $treeItem['invertedDepth']; !empty($closeDepth[$i]); $i++) {
519 $closeDepth[$i] = 0;
520 $itemHTML .= '</ul></li>';
521 }
522 }
523 // Ajax request: collapse
524 if ($doCollapse && $isExpandedFolderIdentifier) {
525 $this->ajaxStatus = true;
526 return $itemHTML;
527 }
528 // Ajax request: expand
529 if ($doExpand && $isExpandedFolderIdentifier) {
530 $ajaxOutput .= $itemHTML;
531 $invertedDepthOfAjaxRequestedItem = $treeItem['invertedDepth'];
532 } elseif ($invertedDepthOfAjaxRequestedItem) {
533 if ($treeItem['invertedDepth'] && ($treeItem['invertedDepth'] < $invertedDepthOfAjaxRequestedItem)) {
534 $ajaxOutput .= $itemHTML;
535 } else {
536 $this->ajaxStatus = true;
537 return $ajaxOutput;
538 }
539 }
540 $out .= $itemHTML;
541 }
542 // If this is an AJAX request, output directly
543 if ($ajaxOutput) {
544 $this->ajaxStatus = true;
545 return $ajaxOutput;
546 }
547 // Finally close the first ul
548 $out .= '</ul>';
549 return $out;
550 }
551
552 /**
553 * Returns table name for click menu
554 *
555 * @param Folder $folderObject
556 * @return string
557 */
558 protected function getTableNameForClickMenu(Folder $folderObject)
559 {
560 if (strpos($folderObject->getRole(), FolderInterface::ROLE_MOUNT) !== false) {
561 $tableName = 'sys_filemounts';
562 } elseif ($folderObject->getIdentifier() === $folderObject->getStorage()->getRootLevelFolder()->getIdentifier()) {
563 $tableName = 'sys_file_storage';
564 } else {
565 $tableName = 'sys_file';
566 }
567 return $tableName;
568 }
569
570 /**
571 * Counts the number of directories in a file path.
572 *
573 * @param Folder $folderObject File path.
574 *
575 * @return int
576 */
577 public function getNumberOfSubfolders(Folder $folderObject)
578 {
579 $subFolders = $folderObject->getSubfolders();
580 return count($subFolders);
581 }
582
583 /**
584 * Get stored tree structure AND updating it if needed according to incoming PM GET var.
585 *
586 * @internal
587 */
588 public function initializePositionSaving()
589 {
590 // Get stored tree structure:
591 $this->stored = json_decode($this->BE_USER->uc['browseTrees'][$this->treeName], true);
592 $this->getShortHashNumberForStorage();
593 // PM action:
594 // (If an plus/minus icon has been clicked,
595 // the PM GET var is sent and we must update the stored positions in the tree):
596 // 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
597 list($storageHashNumber, $doExpand, $numericFolderHash, $treeName) = $this->evaluateExpandCollapseParameter();
598 if ($treeName && $treeName == $this->treeName) {
599 if (in_array($storageHashNumber, $this->storageHashNumbers)) {
600 if ($doExpand == 1) {
601 // Set
602 $this->stored[$storageHashNumber][$numericFolderHash] = 1;
603 } else {
604 // Clear
605 unset($this->stored[$storageHashNumber][$numericFolderHash]);
606 }
607 $this->savePosition();
608 }
609 }
610 }
611
612 /**
613 * Helper method to map md5-hash to shorter number
614 *
615 * @param ResourceStorage $storageObject
616 * @param Folder $startingPointFolder
617 *
618 * @return int
619 */
620 protected function getShortHashNumberForStorage(ResourceStorage $storageObject = null, Folder $startingPointFolder = null)
621 {
622 if (!$this->storageHashNumbers) {
623 $this->storageHashNumbers = [];
624 foreach ($this->storages as $storageUid => $storage) {
625 $fileMounts = $storage->getFileMounts();
626 if (!empty($fileMounts)) {
627 foreach ($fileMounts as $fileMount) {
628 $nkey = hexdec(substr(GeneralUtility::md5int($fileMount['folder']->getCombinedIdentifier()), 0, 4));
629 $this->storageHashNumbers[$storageUid . $fileMount['folder']->getCombinedIdentifier()] = $nkey;
630 }
631 } else {
632 $folder = $storage->getRootLevelFolder();
633 $nkey = hexdec(substr(GeneralUtility::md5int($folder->getCombinedIdentifier()), 0, 4));
634 $this->storageHashNumbers[$storageUid . $folder->getCombinedIdentifier()] = $nkey;
635 }
636 }
637 }
638 if ($storageObject) {
639 if ($startingPointFolder) {
640 return $this->storageHashNumbers[$storageObject->getUid() . $startingPointFolder->getCombinedIdentifier()];
641 }
642 return $this->storageHashNumbers[$storageObject->getUid()];
643 }
644 return null;
645 }
646
647 /**
648 * Gets the values from the Expand/Collapse Parameter (&PM)
649 * previously known as "PM" (plus/minus)
650 * PM action:
651 * (If an plus/minus icon has been clicked,
652 * the PM GET var is sent and we must update the stored positions in the tree):
653 * 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
654 *
655 * @param string $PM The "plus/minus" command
656 * @return array
657 */
658 protected function evaluateExpandCollapseParameter($PM = null)
659 {
660 if ($PM === null) {
661 $PM = GeneralUtility::_GP('PM');
662 // IE takes anchor as parameter
663 if (($PMpos = strpos($PM, '#')) !== false) {
664 $PM = substr($PM, 0, $PMpos);
665 }
666 }
667 // Take the first three parameters
668 list($mountKey, $doExpand, $folderIdentifier) = array_pad(explode('_', $PM, 3), 3, null);
669 // In case the folder identifier contains "_", we just need to get the fourth/last parameter
670 list($folderIdentifier, $treeName) = array_pad(GeneralUtility::revExplode('_', $folderIdentifier, 2), 2, null);
671 return [
672 $mountKey,
673 $doExpand,
674 $folderIdentifier,
675 $treeName
676 ];
677 }
678
679 /**
680 * Generates the "PM" string to sent to expand/collapse items
681 *
682 * @param string $mountKey The mount key / storage UID
683 * @param bool $doExpand Whether to expand/collapse
684 * @param Folder $folderObject The folder object
685 * @param string $treeName The name of the tree
686 *
687 * @return string
688 */
689 protected function generateExpandCollapseParameter($mountKey = null, $doExpand = false, Folder $folderObject = null, $treeName = null)
690 {
691 $parts = [
692 $mountKey ?? $this->bank,
693 $doExpand == 1 ? 1 : 0,
694 $folderObject !== null ? GeneralUtility::md5int($folderObject->getCombinedIdentifier()) : '',
695 $treeName ?? $this->treeName
696 ];
697 return implode('_', $parts);
698 }
699
700 /**
701 * Gets the AJAX status.
702 *
703 * @return bool
704 */
705 public function getAjaxStatus()
706 {
707 return $this->ajaxStatus;
708 }
709
710 /**
711 * @return LanguageService
712 */
713 protected function getLanguageService()
714 {
715 return $GLOBALS['LANG'];
716 }
717 }