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