FolderTreeView.php 22.9 KB
Newer Older
1
2
3
<?php
namespace TYPO3\CMS\Backend\Tree\View;

4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
8
9
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
10
 *
11
12
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
15
 * The TYPO3 project - inspiring people to share!
 */
Christian Kuhn's avatar
Christian Kuhn committed
16

Nicole Cordes's avatar
Nicole Cordes committed
17
use TYPO3\CMS\Backend\Utility\IconUtility;
18
use TYPO3\CMS\Core\Messaging\FlashMessage;
Nicole Cordes's avatar
Nicole Cordes committed
19
20
use TYPO3\CMS\Core\Resource\FolderInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Lang\LanguageService;
Nicole Cordes's avatar
Nicole Cordes committed
22

23
/**
Christian Kuhn's avatar
Christian Kuhn committed
24
25
 * Generate a folder tree,
 * specially made for browsing folders in the File module
26
 */
Thomas Schlumberger's avatar
Thomas Schlumberger committed
27
class FolderTreeView extends AbstractTreeView {
28
29
30
31

	/**
	 * The users' file Storages
	 *
32
	 * @var \TYPO3\CMS\Core\Resource\ResourceStorage[]
33
34
35
36
37
38
39
40
41
42
43
44
	 */
	protected $storages = NULL;

	/**
	 * @var array
	 */
	protected $storageHashNumbers;

	/**
	 * Indicates, whether the AJAX call was successful,
	 * i.e. the requested page has been found
	 *
45
	 * @var bool
46
47
48
	 */
	protected $ajaxStatus = FALSE;

49
50
51
52
53
	/**
	 * @var array
	 */
	protected $scope;

54
	/**
Benni Mack's avatar
Benni Mack committed
55
	 * If file-drag mode is set, temp and recycler folders are filtered out.
56
57
58
59
	 * @var bool
	 */
	public $ext_noTempRecyclerDirs;

Thomas Schlumberger's avatar
Thomas Schlumberger committed
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
	/**
	 * override to not use a title attribute
	 * @var string
	 */
	public $titleAttrib = '';

	/**
	 * override to use this treeName
	 * does not need to be set in __construct()
	 * @var string
	 */
	public $treeName = 'folder';

	/**
	 * override to use this domIdPrefix
	 * @var string
	 */
	public $domIdPrefix = 'folder';

79
80
81
82
83
	/**
	 * Constructor function of the class
	 */
	public function __construct() {
		parent::init();
84
		$this->storages = $this->BE_USER->getFileStorages();
85
86
87
88
89
90
	}

	/**
	 * Generate the plus/minus icon for the browsable tree.
	 *
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject Entry folder object
91
92
93
	 * @param int $subFolderCounter The current entry number
	 * @param int $totalSubFolders The total number of entries. If equal to $a, a "bottom" element is returned.
	 * @param int $nextCount The number of sub-elements to the current element.
94
	 * @param bool $isExpanded The element was expanded to render subelements if this flag is set.
95
96
	 * @return string Image tag with the plus/minus icon.
	 * @internal
Christian Kuhn's avatar
Christian Kuhn committed
97
	 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
98
	 */
Susanne Moog's avatar
Susanne Moog committed
99
	public function PMicon($folderObject, $subFolderCounter, $totalSubFolders, $nextCount, $isExpanded) {
100
		$icon = '';
101
102
103
104
105
106
107
108
109
110
111
112
		if ($nextCount) {
			$cmd = $this->generateExpandCollapseParameter($this->bank, !$isExpanded, $folderObject);
			$icon = $this->PMiconATagWrap($icon, $cmd, !$isExpanded);
		}
		return $icon;
	}

	/**
	 * Wrap the plus/minus icon in a link
	 *
	 * @param string $icon HTML string to wrap, probably an image tag.
	 * @param string $cmd Command for 'PM' get var
113
	 * @param bool $isExpand Whether to be expanded
114
115
116
117
	 * @return string Link-wrapped input string
	 * @internal
	 */
	public function PMiconATagWrap($icon, $cmd, $isExpand = TRUE) {
118
119
120
121
122
123
124
125
126
127
128
129
130

		if (empty($this->scope)) {
			$this->scope = array(
				'class' => get_class($this),
				'script' => $this->thisScript,
				'ext_noTempRecyclerDirs' => $this->ext_noTempRecyclerDirs,
				'browser' => array(
					'mode' => $GLOBALS['SOBE']->browser->mode,
					'act' => $GLOBALS['SOBE']->browser->act,
				),
			);
		}

131
132
		if ($this->thisScript) {
			// Activates dynamic AJAX based tree
133
134
			$scopeData = serialize($this->scope);
			$scopeHash = GeneralUtility::hmac($scopeData);
135
			$js = htmlspecialchars('Tree.load(' . GeneralUtility::quoteJSvalue($cmd) . ', ' . (int)$isExpand . ', this, ' . GeneralUtility::quoteJSvalue($scopeData) . ', ' . GeneralUtility::quoteJSvalue($scopeHash) . ');');
136
			return '<a class="list-tree-control' . (!$isExpand ? ' list-tree-control-open' : ' list-tree-control-closed') . '" onclick="' . $js . '"><i class="fa"></i></a>';
137
138
139
140
141
142
143
144
145
146
147
148
149
		} else {
			return $icon;
		}
	}

	/**
	 * Wrapping the folder icon
	 *
	 * @param string $icon The image tag for the icon
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject The row for the current element
	 * @return string The processed icon input value.
	 * @internal
	 */
Susanne Moog's avatar
Susanne Moog committed
150
	public function wrapIcon($icon, $folderObject) {
151
		// Add title attribute to input icon tag
152
		$theFolderIcon = '';
153
154
		// Wrap icon in click-menu link.
		if (!$this->ext_IconMode) {
155
			// Check storage access to wrap with click menu
156
			if (!$folderObject instanceof \TYPO3\CMS\Core\Resource\InaccessibleFolder) {
157
				$theFolderIcon = $GLOBALS['TBE_TEMPLATE']->wrapClickMenuOnIcon($icon, $folderObject->getCombinedIdentifier(), '', 0);
158
			}
159
		} elseif ($this->ext_IconMode === 'titlelink') {
160
			$aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ',' . $this->bank . ');';
161
			$theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>';
162
163
164
165
166
167
168
169
		}
		return $theFolderIcon;
	}

	/**
	 * Wrapping $title in a-tags.
	 *
	 * @param string $title Title string
Susanne Moog's avatar
Susanne Moog committed
170
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject the folder record
171
	 * @param int $bank Bank pointer (which mount point number)
172
173
174
	 * @return string
	 * @internal
	 */
Susanne Moog's avatar
Susanne Moog committed
175
	public function wrapTitle($title, $folderObject, $bank = 0) {
176
		// Check storage access to wrap with click menu
177
		if ($folderObject instanceof \TYPO3\CMS\Core\Resource\InaccessibleFolder) {
178
179
			return $title;
		}
180
		$aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($folderObject)) . ', this, ' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($folderObject)) . ', ' . $bank . ');';
181
		$clickMenuParts = $GLOBALS['TBE_TEMPLATE']->wrapClickMenuOnIcon('', $folderObject->getCombinedIdentifier(), '', 0, ('&bank=' . $this->bank), '', TRUE);
182

183
		return '<a href="#" title="' . htmlspecialchars(strip_tags($title)) . '" onclick="' . htmlspecialchars($aOnClick) . '" ' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
184
185
186
187
188
189
	}

	/**
	 * Returns the id from the record - for folders, this is an md5 hash.
	 *
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject The folder object
190
	 * @return int The "uid" field value.
191
	 */
Susanne Moog's avatar
Susanne Moog committed
192
	public function getId($folderObject) {
Nicole Cordes's avatar
Nicole Cordes committed
193
		return GeneralUtility::md5Int($folderObject->getCombinedIdentifier());
194
195
196
197
198
199
200
201
	}

	/**
	 * Returns jump-url parameter value.
	 *
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject The folder object
	 * @return string The jump-url parameter.
	 */
Susanne Moog's avatar
Susanne Moog committed
202
	public function getJumpToParam($folderObject) {
203
204
205
206
207
208
209
210
		return rawurlencode($folderObject->getCombinedIdentifier());
	}

	/**
	 * Returns the title for the input record. If blank, a "no title" labele (localized) will be returned.
	 * '_title' is used for setting an alternative title for folders.
	 *
	 * @param array $row The input row array (where the key "_title" is used for the title)
211
	 * @param int $titleLen Title length (30)
212
213
214
	 * @return string The title
	 */
	public function getTitleStr($row, $titleLen = 30) {
215
		return $row['_title'] ?: parent::getTitleStr($row, $titleLen);
216
217
218
219
220
221
222
223
	}

	/**
	 * Returns the value for the image "title" attribute
	 *
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject The folder to be used
	 * @return 	string The attribute value (is htmlspecialchared() already)
	 */
Susanne Moog's avatar
Susanne Moog committed
224
	public function getTitleAttrib($folderObject) {
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
		return htmlspecialchars($folderObject->getName());
	}

	/**
	 * Will create and return the HTML code for a browsable tree of folders.
	 * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
	 *
	 * @return string HTML code for the browsable tree
	 */
	public function getBrowsableTree() {
		// Get stored tree structure AND updating it if needed according to incoming PM GET var.
		$this->initializePositionSaving();
		// Init done:
		$treeItems = array();
		// Traverse mounts:
		foreach ($this->storages as $storageObject) {
			$this->getBrowseableTreeForStorage($storageObject);
			// Add tree:
			$treeItems = array_merge($treeItems, $this->tree);
		}
		return $this->printTree($treeItems);
	}

	/**
	 * Get a tree for one storage
	 *
	 * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storageObject
	 * @return void
	 */
	public function getBrowseableTreeForStorage(\TYPO3\CMS\Core\Resource\ResourceStorage $storageObject) {
		// If there are filemounts, show each, otherwise just the rootlevel folder
		$fileMounts = $storageObject->getFileMounts();
		$rootLevelFolders = array();
258
		if (!empty($fileMounts)) {
259
260
261
262
263
264
			foreach ($fileMounts as $fileMountInfo) {
				$rootLevelFolders[] = array(
					'folder' => $fileMountInfo['folder'],
					'name' => $fileMountInfo['title']
				);
			}
265
		} elseif ($this->BE_USER->isAdmin()) {
266
267
268
269
270
271
272
273
274
275
276
277
			$rootLevelFolders[] = array(
				'folder' => $storageObject->getRootLevelFolder(),
				'name' => $storageObject->getName()
			);
		}
		// Clean the tree
		$this->reset();
		// Go through all "root level folders" of this tree (can be the rootlevel folder or any file mount points)
		foreach ($rootLevelFolders as $rootLevelFolderInfo) {
			/** @var $rootLevelFolder \TYPO3\CMS\Core\Resource\Folder */
			$rootLevelFolder = $rootLevelFolderInfo['folder'];
			$rootLevelFolderName = $rootLevelFolderInfo['name'];
Nicole Cordes's avatar
Nicole Cordes committed
278
			$folderHashSpecUID = GeneralUtility::md5int($rootLevelFolder->getCombinedIdentifier());
279
280
281
282
283
284
285
286
			$this->specUIDmap[$folderHashSpecUID] = $rootLevelFolder->getCombinedIdentifier();
			// Hash key
			$storageHashNumber = $this->getShortHashNumberForStorage($storageObject, $rootLevelFolder);
			// Set first:
			$this->bank = $storageHashNumber;
			$isOpen = $this->stored[$storageHashNumber][$folderHashSpecUID] || $this->expandFirst;
			// Set PM icon:
			$cmd = $this->generateExpandCollapseParameter($this->bank, !$isOpen, $rootLevelFolder);
287
			if (!$storageObject->isBrowsable() || $this->getNumberOfSubfolders($rootLevelFolder) === 0) {
288
				$firstHtml = '';
289
			} else {
290
291
292
293
294
295
				// Only show and link icon if storage is browseable
				$link = '';
				if ($this->thisScript) {
					$link = ' href="' . htmlspecialchars($this->getThisScript() . 'PM=' . $cmd) . '"';
				}
				$firstHtml = '<a class="list-tree-control list-tree-control-' . ($isOpen ? 'open' : 'closed') . '"' . $link . '><i class="fa"></i></a>';
296
			}
297
298
299
			// Mark a storage which is not online, as offline
			// maybe someday there will be a special icon for this
			if ($storageObject->isOnline() === FALSE) {
300
				$rootLevelFolderName .= ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_file.xlf:sys_file_storage.isOffline') . ')';
301
302
			}
			// Preparing rootRec for the mount
303
			$firstHtml .= $this->wrapIcon(IconUtility::getSpriteIconForResource($rootLevelFolder, array('mount-root' => TRUE)), $rootLevelFolder);
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
			$row = array(
				'uid' => $folderHashSpecUID,
				'title' => $rootLevelFolderName,
				'path' => $rootLevelFolder->getCombinedIdentifier(),
				'folder' => $rootLevelFolder
			);
			// Add the storage root to ->tree
			$this->tree[] = array(
				'HTML' => $firstHtml,
				'row' => $row,
				'bank' => $this->bank,
				// hasSub is TRUE when the root of the storage is expanded
				'hasSub' => $isOpen && $storageObject->isBrowsable()
			);
			// If the mount is expanded, go down:
			if ($isOpen && $storageObject->isBrowsable()) {
				// Set depth:
				$this->getFolderTree($rootLevelFolder, 999);
			}
		}
	}

	/**
	 * Fetches the data for the tree
	 *
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject the folderobject
330
	 * @param int $depth Max depth (recursivity limit)
331
	 * @param string $type HTML-code prefix for recursive calls.
332
	 * @return int The count of items on the level
333
334
335
	 * @see getBrowsableTree()
	 */
	public function getFolderTree(\TYPO3\CMS\Core\Resource\Folder $folderObject, $depth = 999, $type = '') {
336
		$depth = (int)$depth;
337

338
		// This generates the directory tree
339
		/* array of \TYPO3\CMS\Core\Resource\Folder */
340
341
342
343
344
345
346
		if ($folderObject instanceof \TYPO3\CMS\Core\Resource\InaccessibleFolder) {
			$subFolders = array();
		} else {
			$subFolders = $folderObject->getSubfolders();
			$subFolders = \TYPO3\CMS\Core\Resource\Utility\ListUtility::resolveSpecialFolderNames($subFolders);
			uksort($subFolders, 'strnatcasecmp');
		}
347

348
349
350
		$totalSubFolders = count($subFolders);
		$HTML = '';
		$subFolderCounter = 0;
351
		foreach ($subFolders as $subFolderName => $subFolder) {
352
353
354
355
356
			$subFolderCounter++;
			// Reserve space.
			$this->tree[] = array();
			// Get the key for this space
			end($this->tree);
357
			$isLocked = $subFolder instanceof \TYPO3\CMS\Core\Resource\InaccessibleFolder;
358
			$treeKey = key($this->tree);
Nicole Cordes's avatar
Nicole Cordes committed
359
			$specUID = GeneralUtility::md5int($subFolder->getCombinedIdentifier());
360
361
362
363
			$this->specUIDmap[$specUID] = $subFolder->getCombinedIdentifier();
			$row = array(
				'uid' => $specUID,
				'path' => $subFolder->getCombinedIdentifier(),
364
				'title' => $subFolderName,
365
366
367
				'folder' => $subFolder
			);
			// Make a recursive call to the next level
368
			if (!$isLocked && $depth > 1 && $this->expandNext($specUID)) {
369
370
371
372
				$nextCount = $this->getFolderTree($subFolder, $depth - 1, $type);
				// Set "did expand" flag
				$isOpen = 1;
			} else {
373
				$nextCount = $isLocked ? 0 : $this->getNumberOfSubfolders($subFolder);
374
375
376
377
378
379
				// Clear "did expand" flag
				$isOpen = 0;
			}
			// Set HTML-icons, if any:
			if ($this->makeHTML) {
				$HTML = $this->PMicon($subFolder, $subFolderCounter, $totalSubFolders, $nextCount, $isOpen);
380
381
				$type = '';

382
				$role = $subFolder->getRole();
Nicole Cordes's avatar
Nicole Cordes committed
383
				if ($role !== FolderInterface::ROLE_DEFAULT) {
384
					$row['_title'] = '<strong>' . $subFolderName . '</strong>';
385
				}
386
				$icon = IconUtility::getSpriteIconForResource($subFolder, array('title' => $subFolderName, 'folder-open' => (bool)$isOpen));
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
				$HTML .= $this->wrapIcon($icon, $subFolder);
			}
			// Finally, add the row/HTML content to the ->tree array in the reserved key.
			$this->tree[$treeKey] = array(
				'row' => $row,
				'HTML' => $HTML,
				'hasSub' => $nextCount && $this->expandNext($specUID),
				'isFirst' => $subFolderCounter == 1,
				'isLast' => FALSE,
				'invertedDepth' => $depth,
				'bank' => $this->bank
			);
		}
		if ($subFolderCounter > 0) {
			$this->tree[$treeKey]['isLast'] = TRUE;
		}
		return $totalSubFolders;
	}

	/**
	 * Compiles the HTML code for displaying the structure found inside the ->tree array
	 *
	 * @param array|string $treeItems "tree-array" - if blank string, the internal ->tree array is used.
	 * @return string The HTML code for the tree
	 */
	public function printTree($treeItems = '') {
		$doExpand = FALSE;
		$doCollapse = FALSE;
		$ajaxOutput = '';
416
		$titleLength = (int)$this->BE_USER->uc['titleLen'];
417
418
419
		if (!is_array($treeItems)) {
			$treeItems = $this->tree;
		}
420
421
422
423
424
425
426
427
428
429
430

		if (empty($treeItems)) {
			$message = GeneralUtility::makeInstance(
				FlashMessage::class,
				$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.message'),
				$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:foldertreeview.noFolders.title'),
				FlashMessage::INFO
			);
			return $message->render();
		}

Benni Mack's avatar
Benni Mack committed
431
		$out = '<ul class="list-tree list-tree-root">';
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
		// Evaluate AJAX request
		if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
			list(, $expandCollapseCommand, $expandedFolderHash, ) = $this->evaluateExpandCollapseParameter();
			if ($expandCollapseCommand == 1) {
				// We don't know yet. Will be set later.
				$invertedDepthOfAjaxRequestedItem = 0;
				$doExpand = TRUE;
			} else {
				$doCollapse = TRUE;
			}
		}
		// We need to count the opened <ul>'s every time we dig into another level,
		// so we know how many we have to close when all children are done rendering
		$closeDepth = array();
		foreach ($treeItems as $treeItem) {
			/** @var $folderObject \TYPO3\CMS\Core\Resource\Folder */
			$folderObject = $treeItem['row']['folder'];
			$classAttr = $treeItem['row']['_CSSCLASS'];
			$folderIdentifier = $folderObject->getCombinedIdentifier();
			// this is set if the AJAX request has just opened this folder (via the PM command)
Nicole Cordes's avatar
Nicole Cordes committed
452
			$isExpandedFolderIdentifier = $expandedFolderHash == GeneralUtility::md5int($folderIdentifier);
453
			$idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($folderObject) . '_' . $treeItem['bank']);
454
455
456
			$itemHTML = '';
			// If this item is the start of a new level,
			// then a new level <ul> is needed, but not in ajax mode
457
			if ($treeItem['isFirst'] && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
458
				$itemHTML = '<ul class="list-tree">';
459
460
461
			}
			// Add CSS classes to the list item
			if ($treeItem['hasSub']) {
462
				$classAttr .= ' list-tree-control-open';
463
			}
464
			$itemHTML .= '
465
				<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>';
466
			if (!$treeItem['hasSub']) {
467
				$itemHTML .= '</li>';
468
469
470
471
472
473
474
475
			}
			// We have to remember if this is the last one
			// on level X so the last child on level X+1 closes the <ul>-tag
			if ($treeItem['isLast'] && !($doExpand && $isExpandedFolderIdentifier)) {
				$closeDepth[$treeItem['invertedDepth']] = 1;
			}
			// If this is the last one and does not have subitems, we need to close
			// the tree as long as the upper levels have last items too
476
			if ($treeItem['isLast'] && !$treeItem['hasSub'] && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
477
478
				for ($i = $treeItem['invertedDepth']; $closeDepth[$i] == 1; $i++) {
					$closeDepth[$i] = 0;
479
					$itemHTML .= '</ul></li>';
480
481
482
483
484
485
486
487
488
489
490
491
				}
			}
			// Ajax request: collapse
			if ($doCollapse && $isExpandedFolderIdentifier) {
				$this->ajaxStatus = TRUE;
				return $itemHTML;
			}
			// Ajax request: expand
			if ($doExpand && $isExpandedFolderIdentifier) {
				$ajaxOutput .= $itemHTML;
				$invertedDepthOfAjaxRequestedItem = $treeItem['invertedDepth'];
			} elseif ($invertedDepthOfAjaxRequestedItem) {
492
				if ($treeItem['invertedDepth'] && ($treeItem['invertedDepth'] < $invertedDepthOfAjaxRequestedItem)) {
493
494
495
496
497
498
499
500
					$ajaxOutput .= $itemHTML;
				} else {
					$this->ajaxStatus = TRUE;
					return $ajaxOutput;
				}
			}
			$out .= $itemHTML;
		}
501
		// If this is an AJAX request, output directly
502
503
504
505
506
		if ($ajaxOutput) {
			$this->ajaxStatus = TRUE;
			return $ajaxOutput;
		}
		// Finally close the first ul
507
		$out .= '</ul>';
508
509
510
511
512
513
514
		return $out;
	}

	/**
	 * Counts the number of directories in a file path.
	 *
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject File path.
515
	 * @return int
516
517
518
519
520
521
522
523
524
	 */
	public function getNumberOfSubfolders(\TYPO3\CMS\Core\Resource\Folder $folderObject) {
		$subFolders = $folderObject->getSubfolders();
		return count($subFolders);
	}

	/**
	 * Get stored tree structure AND updating it if needed according to incoming PM GET var.
	 *
525
	 * @return void
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
	 * @access private
	 */
	public function initializePositionSaving() {
		// Get stored tree structure:
		$this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName]);
		$this->getShortHashNumberForStorage();
		// PM action:
		// (If an plus/minus icon has been clicked,
		// the PM GET var is sent and we must update the stored positions in the tree):
		// 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
		list($storageHashNumber, $doExpand, $numericFolderHash, $treeName) = $this->evaluateExpandCollapseParameter();
		if ($treeName && $treeName == $this->treeName) {
			if (in_array($storageHashNumber, $this->storageHashNumbers)) {
				if ($doExpand == 1) {
					// Set
					$this->stored[$storageHashNumber][$numericFolderHash] = 1;
				} else {
					// Clear
					unset($this->stored[$storageHashNumber][$numericFolderHash]);
				}
				$this->savePosition();
			}
		}
	}

	/**
	 * Helper method to map md5-hash to shorter number
	 *
	 * @param \TYPO3\CMS\Core\Resource\ResourceStorage $storageObject
	 * @param \TYPO3\CMS\Core\Resource\Folder $startingPointFolder
556
	 * @return int
557
558
559
560
561
562
563
564
	 */
	protected function getShortHashNumberForStorage(\TYPO3\CMS\Core\Resource\ResourceStorage $storageObject = NULL, \TYPO3\CMS\Core\Resource\Folder $startingPointFolder = NULL) {
		if (!$this->storageHashNumbers) {
			$this->storageHashNumbers = array();
			// Mapping md5-hash to shorter number:
			$hashMap = array();
			foreach ($this->storages as $storageUid => $storage) {
				$fileMounts = $storage->getFileMounts();
565
				if (!empty($fileMounts)) {
566
					foreach ($fileMounts as $fileMount) {
Nicole Cordes's avatar
Nicole Cordes committed
567
						$nkey = hexdec(substr(GeneralUtility::md5int($fileMount['folder']->getCombinedIdentifier()), 0, 4));
568
569
570
571
						$this->storageHashNumbers[$storageUid . $fileMount['folder']->getCombinedIdentifier()] = $nkey;
					}
				} else {
					$folder = $storage->getRootLevelFolder();
Nicole Cordes's avatar
Nicole Cordes committed
572
					$nkey = hexdec(substr(GeneralUtility::md5int($folder->getCombinedIdentifier()), 0, 4));
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
					$this->storageHashNumbers[$storageUid . $folder->getCombinedIdentifier()] = $nkey;
				}
			}
		}
		if ($storageObject) {
			if ($startingPointFolder) {
				return $this->storageHashNumbers[$storageObject->getUid() . $startingPointFolder->getCombinedIdentifier()];
			} else {
				return $this->storageHashNumbers[$storageObject->getUid()];
			}
		} else {
			return NULL;
		}
	}

	/**
	 * Gets the values from the Expand/Collapse Parameter (&PM)
	 * previously known as "PM" (plus/minus)
	 * PM action:
	 * (If an plus/minus icon has been clicked,
	 * the PM GET var is sent and we must update the stored positions in the tree):
	 * 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
	 *
	 * @param string $PM The "plus/minus" command
	 * @return array
	 */
	protected function evaluateExpandCollapseParameter($PM = NULL) {
		if ($PM === NULL) {
Nicole Cordes's avatar
Nicole Cordes committed
601
			$PM = GeneralUtility::_GP('PM');
602
603
604
605
606
607
608
609
			// IE takes anchor as parameter
			if (($PMpos = strpos($PM, '#')) !== FALSE) {
				$PM = substr($PM, 0, $PMpos);
			}
		}
		// Take the first three parameters
		list($mountKey, $doExpand, $folderIdentifier) = explode('_', $PM, 3);
		// In case the folder identifier contains "_", we just need to get the fourth/last parameter
Nicole Cordes's avatar
Nicole Cordes committed
610
		list($folderIdentifier, $treeName) = GeneralUtility::revExplode('_', $folderIdentifier, 2);
611
612
613
614
615
616
617
618
619
620
621
622
		return array(
			$mountKey,
			$doExpand,
			$folderIdentifier,
			$treeName
		);
	}

	/**
	 * Generates the "PM" string to sent to expand/collapse items
	 *
	 * @param string $mountKey The mount key / storage UID
623
	 * @param bool $doExpand Whether to expand/collapse
624
625
626
627
628
629
630
631
	 * @param \TYPO3\CMS\Core\Resource\Folder $folderObject The folder object
	 * @param string $treeName The name of the tree
	 * @return string
	 */
	protected function generateExpandCollapseParameter($mountKey = NULL, $doExpand = FALSE, \TYPO3\CMS\Core\Resource\Folder $folderObject = NULL, $treeName = NULL) {
		$parts = array(
			$mountKey !== NULL ? $mountKey : $this->bank,
			$doExpand == 1 ? 1 : 0,
Nicole Cordes's avatar
Nicole Cordes committed
632
			$folderObject !== NULL ? GeneralUtility::md5int($folderObject->getCombinedIdentifier()) : '',
633
634
635
636
637
638
639
640
			$treeName !== NULL ? $treeName : $this->treeName
		);
		return implode('_', $parts);
	}

	/**
	 * Gets the AJAX status.
	 *
641
	 * @return bool
642
643
644
645
646
	 */
	public function getAjaxStatus() {
		return $this->ajaxStatus;
	}

647
648
649
650
651
652
653
	/**
	 * @return LanguageService
	 */
	protected function getLanguageService() {
		return $GLOBALS['LANG'];
	}

654
}