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