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