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