[BUGFIX] Moving files in filelist renames file to "1"
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_foldertree.php
index 664ed4b..94c3ad8 100644 (file)
  *
  * Revised for TYPO3 3.6 November/2003 by Kasper Skårhøj
  *
- * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
- * @coauthor   René Fritz <r.fritz@colorcube.de>
+ * @author Kasper Skårhøj <kasperYYYY@typo3.com>
+ * @coauthor René Fritz <r.fritz@colorcube.de>
  */
 
-
 /**
  * Extension class for the t3lib_treeView class, specially made for browsing folders in the File module
  *
- * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
- * @coauthor   René Fritz <r.fritz@colorcube.de>
+ * @author Kasper Skårhøj <kasperYYYY@typo3.com>
+ * @coauthor René Fritz <r.fritz@colorcube.de>
  * @package TYPO3
  * @subpackage t3lib
  * @see class t3lib_treeView
 class t3lib_folderTree extends t3lib_treeView {
 
        /**
-        * Constructor function of the class
+        * The users' file Storages
+        * @var t3lib_file_storage[]
+        */
+       protected $storages = NULL;
+
+       /**
+        * @var array
+        */
+       protected $storageHashNumbers;
+
+       /**
+        * Indicates, whether the AJAX call was successful,
+        * i.e. the requested page has been found
         *
-        * @return      void
+        * @var boolean
         */
-       function __construct() {
+       protected $ajaxStatus = FALSE;
+
+       /**
+        * Constructor function of the class
+        */
+       public function __construct() {
                parent::init();
 
-               $this->MOUNTS = $GLOBALS['FILEMOUNTS'];
+               $this->storages = $GLOBALS['BE_USER']->getFileStorages();
 
                $this->treeName = 'folder';
-               $this->titleAttrib = ''; //don't apply any title
+                       // Don't apply any title
+               $this->titleAttrib = '';
                $this->domIdPrefix = 'folder';
        }
 
        /**
-        * Compatibility constructor.
+        * Generate the plus/minus icon for the browsable tree.
         *
-        * @deprecated since TYPO3 4.6 and will be removed in TYPO3 4.8. Use __construct() instead.
+        * @param t3lib_file_Folder $folderObject Entry folder object
+        * @param integer $subFolderCounter The current entry number
+        * @param integer $totalSubFolders The total number of entries. If equal to $a, a "bottom" element is returned.
+        * @param integer $nextCount The number of sub-elements to the current element.
+        * @param boolean $isExpanded The element was expanded to render subelements if this flag is set.
+        * @return string Image tag with the plus/minus icon.
+        * @internal
+        * @see t3lib_pageTree::PMicon()
         */
-       public function t3lib_folderTree() {
-               t3lib_div::logDeprecatedFunction();
-                       // Note: we cannot call $this->__construct() here because it would call the derived class constructor and cause recursion
-                       // This code uses official PHP behavior (http://www.php.net/manual/en/language.oop5.basic.php) when $this in the
-                       // statically called non-static method inherits $this from the caller's scope.
-               t3lib_folderTree::__construct();
+       public function PMicon(t3lib_file_Folder $folderObject, $subFolderCounter, $totalSubFolders, $nextCount, $isExpanded) {
+               $PM   = ($nextCount ? ($isExpanded ? 'minus' : 'plus') : 'join');
+               $BTM  = ($subFolderCounter == $totalSubFolders ? 'bottom' : '');
+               $icon = '<img' . t3lib_iconWorks::skinImg(
+                               $this->backPath, 'gfx/ol/' . $PM . $BTM . '.gif',
+                               'width="18" height="16"'
+                       ) . ' alt="" />';
+
+               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
+        * @param boolean $isExpand Whether to be expanded
+        * @return string Link-wrapped input string
+        * @internal
+        */
+       public function PMiconATagWrap($icon, $cmd, $isExpand = TRUE) {
+               if ($this->thisScript) {
+                               // Activates dynamic AJAX based tree
+                       $js = htmlspecialchars('Tree.load(\'' . $cmd . '\', ' . intval($isExpand) . ', this);');
+                       return '<a class="pm" onclick="' . $js . '">' . $icon . '</a>';
+               } else {
+                       return $icon;
+               }
        }
 
        /**
         * Wrapping the folder icon
         *
-        * @param       string          The image tag for the icon
-        * @param       array           The row for the current element
-        * @return      string          The processed icon input value.
-        * @access private
+        * @param string $icon The image tag for the icon
+        * @param t3lib_file_Folder $folderObject The row for the current element
+        * @return string The processed icon input value.
+        * @internal
         */
-       function wrapIcon($icon, $row) {
+       public function wrapIcon($icon, t3lib_file_Folder $folderObject) {
                        // Add title attribute to input icon tag
-               $theFolderIcon = $this->addTagAttributes($icon, ($this->titleAttrib ? $this->titleAttrib . '="' . $this->getTitleAttrib($row) . '"' : ''));
+               $theFolderIcon = $this->addTagAttributes($icon, ($this->titleAttrib ? $this->titleAttrib . '="' . $this->getTitleAttrib($folderObject) . '"' : ''));
 
                        // Wrap icon in click-menu link.
                if (!$this->ext_IconMode) {
-                       $theFolderIcon = $GLOBALS['TBE_TEMPLATE']->wrapClickMenuOnIcon($theFolderIcon, $row['path'], '', 0);
+                       $theFolderIcon = $GLOBALS['TBE_TEMPLATE']->wrapClickMenuOnIcon($theFolderIcon, $folderObject->getCombinedIdentifier(), '', 0);
                } elseif (!strcmp($this->ext_IconMode, 'titlelink')) {
-                       $aOnClick = 'return jumpTo(\'' . $this->getJumpToParam($row) . '\',this,\'' . $this->domIdPrefix . $this->getId($row) . '\',' . $this->bank . ');';
+                       $aOnClick = 'return jumpTo(\'' . $this->getJumpToParam($folderObject) . '\',this,\'' . $this->domIdPrefix . $this->getId($folderObject) . '\',' . $this->bank . ');';
                        $theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $theFolderIcon . '</a>';
                }
+
                return $theFolderIcon;
        }
 
        /**
         * Wrapping $title in a-tags.
         *
-        * @param       string          Title string
-        * @param       string          Item record
-        * @param       integer         Bank pointer (which mount point number)
-        * @return      string
-        * @access private
+        * @param string $title Title string
+        * @param t3lib_file_Folder     $folderObject the folder record
+        * @param integer $bank Bank pointer (which mount point number)
+        * @return string
+        * @internal
         */
-       function wrapTitle($title, $row, $bank = 0) {
-               $aOnClick = 'return jumpTo(\'' . $this->getJumpToParam($row) . '\',this,\'' . $this->domIdPrefix . $this->getId($row) . '\',' . $bank . ');';
-               $CSM = ' oncontextmenu="' . htmlspecialchars($GLOBALS['TBE_TEMPLATE']->wrapClickMenuOnIcon('', $row['path'], '', 0, '', '', TRUE)) . '"';
-               return '<a href="#" title="' . htmlspecialchars($row['title']) . '" onclick="' . htmlspecialchars($aOnClick) . '"' . $CSM . '>' . $title . '</a>';
+       public function wrapTitle($title, t3lib_file_Folder $folderObject, $bank = 0) {
+               $aOnClick = 'return jumpTo(\'' . $this->getJumpToParam($folderObject) . '\', this, \'' . $this->domIdPrefix . $this->getId($folderObject) . '\', ' . $bank . ');';
+               $CSM = ' oncontextmenu="'.htmlspecialchars($GLOBALS['TBE_TEMPLATE']->wrapClickMenuOnIcon('', $folderObject->getCombinedIdentifier(), '', 0, '&bank=' . $this->bank, '', TRUE)) . '"';
+               return '<a href="#" title="' . htmlspecialchars($title) . '" onclick="' . htmlspecialchars($aOnClick) . '"' . $CSM . '>' . $title . '</a>';
        }
 
        /**
         * Returns the id from the record - for folders, this is an md5 hash.
         *
-        * @param       array           Record array
-        * @return      integer         The "uid" field value.
+        * @param t3lib_file_Folder $folderObject The folder object
+        * @return integer The "uid" field value.
         */
-       function getId($v) {
-               return t3lib_div::md5Int($v['path']);
+       public function getId(t3lib_file_Folder $folderObject) {
+               return t3lib_div::md5Int($folderObject->getCombinedIdentifier());
        }
 
        /**
         * Returns jump-url parameter value.
         *
-        * @param       array           The record array.
-        * @return      string          The jump-url parameter.
+        * @param t3lib_file_Folder $folderObject The folder object
+        * @return string The jump-url parameter.
         */
-       function getJumpToParam($v) {
-               return rawurlencode($v['path']);
+       public function getJumpToParam(t3lib_file_Folder $folderObject) {
+               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           The input row array (where the key "_title" is used for the title)
-        * @param       integer         Title length (30)
-        * @return      string          The title.
+        * @param array $row The input row array (where the key "_title" is used for the title)
+        * @param integer $titleLen Title length (30)
+        * @return string The title
         */
-       function getTitleStr($row, $titleLen = 30) {
+       public function getTitleStr($row, $titleLen = 30) {
                return $row['_title'] ? $row['_title'] : parent::getTitleStr($row, $titleLen);
        }
 
        /**
+        * Returns the value for the image "title" attribute
+        *
+        * @param t3lib_file_Folder $folderObject The folder to be used
+        * @return      string The attribute value (is htmlspecialchared() already)
+        */
+       function getTitleAttrib(t3lib_file_Folder $folderObject) {
+               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
+        * @return string HTML code for the browsable tree
         */
-       function getBrowsableTree() {
-
+       public function getBrowsableTree() {
                        // Get stored tree structure AND updating it if needed according to incoming PM GET var.
                $this->initializePositionSaving();
 
                        // Init done:
-               $titleLen = intval($this->BE_USER->uc['titleLen']);
-               $treeArr = array();
+               $treeItems = array();
 
                        // Traverse mounts:
-               foreach ($this->MOUNTS as $key => $val) {
-                       $md5_uid = md5($val['path']);
-                       $specUID = hexdec(substr($md5_uid, 0, 6));
-                       $this->specUIDmap[$specUID] = $val['path'];
+               foreach ($this->storages as $storageObject) {
+                       $this->getBrowseableTreeForStorage($storageObject);
+
+                               // Add tree:
+                       $treeItems = array_merge($treeItems, $this->tree);
+
+                               // if this is an AJAX call, don't run through all mounts, only
+                               // show the expansion of the current one, not the rest of the mounts
+                       if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
+                               // @todo: currently the AJAX script runs through all storages thus, if something is expanded on storage #2, it does not work, the break stops this, the goal should be that only the $this->storages iterates over the selected storage/bank
+                       }
+               }
+
+               return $this->printTree($treeItems);
+       }
+
+       /**
+        * Get a tree for one storage
+        *
+        * @param t3lib_file_Storage $storageObject
+        * @return void
+        */
+       public function getBrowseableTreeForStorage(t3lib_file_Storage $storageObject) {
+                       // If there are filemounts, show each, otherwise just the rootlevel folder
+               $fileMounts = $storageObject->getFileMounts();
+               $rootLevelFolders = array();
+               if (count($fileMounts)) {
+                       foreach ($fileMounts as $fileMountInfo) {
+                               $rootLevelFolders[] = array(
+                                       'folder' => $fileMountInfo['folder'],
+                                       'name' => $fileMountInfo['title']
+                               );
+                       }
+               } else {
+                       $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 t3lib_file_Folder */
+                       $rootLevelFolder = $rootLevelFolderInfo['folder'];
+                       $rootLevelFolderName = $rootLevelFolderInfo['name'];
+                       $folderHashSpecUID = t3lib_div::md5int($rootLevelFolder->getCombinedIdentifier());
+                       $this->specUIDmap[$folderHashSpecUID] = $rootLevelFolder->getCombinedIdentifier();
+
+                               // Hash key
+                       $storageHashNumber = $this->getShortHashNumberForStorage($storageObject, $rootLevelFolder);
 
                                // Set first:
-                       $this->bank = $val['nkey'];
-                       $isOpen = $this->stored[$val['nkey']][$specUID] || $this->expandFirst;
-                       $this->reset();
+                       $this->bank = $storageHashNumber;
+                       $isOpen = $this->stored[$storageHashNumber][$folderHashSpecUID] || $this->expandFirst;
 
                                // Set PM icon:
-                       $cmd = $this->bank . '_' . ($isOpen ? '0_' : '1_') . $specUID . '_' . $this->treeName;
-                       $icon = '<img' . t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/' . ($isOpen ? 'minus' : 'plus') . 'only.gif', 'width="18" height="16"') . ' alt="" />';
+                       $cmd = $this->generateExpandCollapseParameter($this->bank, !$isOpen, $rootLevelFolder);
+                       if (!$storageObject->isBrowsable() || $this->getNumberOfSubfolders($storageObject->getRootLevelFolder()) === 0) {
+                               $rootIcon = 'blank';
+                       } elseif (!$isOpen) {
+                               $rootIcon = 'plusonly';
+                       } else {
+                               $rootIcon = 'minusonly';
+                       }
+                       $icon = '<img' . t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/' . $rootIcon . '.gif') . ' alt="" />';
                        $firstHtml = $this->PM_ATagWrap($icon, $cmd);
 
-                       switch ($val['type']) {
-                               case 'user':
-                                       $icon = 'gfx/i/_icon_ftp_user.gif';
-                               break;
-                               case 'group':
-                                       $icon = 'gfx/i/_icon_ftp_group.gif';
-                               break;
-                               case 'readonly':
-                                       $icon = 'gfx/i/_icon_ftp_readonly.gif';
-                               break;
-                               default:
-                                       $icon = 'gfx/i/_icon_ftp.gif';
-                               break;
+                               // @todo: create sprite icons for user/group mounts etc
+                       if ($storageObject->isBrowsable() === FALSE) {
+                               $icon = 'apps-filetree-folder-locked';
+                       } else {
+                               $icon = 'apps-filetree-root';
                        }
 
-                               // Preparing rootRec for the mount
-                       $firstHtml .= $this->wrapIcon('<img' . t3lib_iconWorks::skinImg($this->backPath, $icon, 'width="18" height="16"') . ' alt="" />', $val);
-                       $row = array();
-                       $row['path'] = $val['path'];
-                       $row['uid'] = $specUID;
-                       $row['title'] = $val['name'];
+                               // Mark a storage which is not online, as offline
+                               // maybe someday there will be a special icon for this
+                       if ($storageObject->isOnline() === FALSE) {
+                               $rootLevelFolderName .= ' (' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_file.xlf:sys_file_storage.isOffline') . ')';
+                       }
 
-                               // Add the root of the mount to ->tree
-                       $this->tree[] = array('HTML' => $firstHtml, 'row' => $row, 'bank' => $this->bank);
+                               // Preparing rootRec for the mount
+                       $firstHtml .= $this->wrapIcon(t3lib_iconWorks::getSpriteIcon($icon), $rootLevelFolder);
+                       $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) {
+                       if ($isOpen && $storageObject->isBrowsable()) {
                                        // Set depth:
-                               $depthD = '<img' . t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/blank.gif', 'width="18" height="16"') . ' alt="" />';
-                               $this->getFolderTree($val['path'], 999, $depthD, $val['type']);
+                               $this->getFolderTree($rootLevelFolder, 999);
                        }
-
-                               // Add tree:
-                       $treeArr = array_merge($treeArr, $this->tree);
                }
-               return $this->printTree($treeArr);
        }
 
        /**
         * Fetches the data for the tree
         *
-        * @param       string          Abs file path
-        * @param       integer         Max depth (recursivity limit)
-        * @param       string          HTML-code prefix for recursive calls.
-        * @return      integer         The count of items on the level
+        * @param t3lib_file_Folder $folderObject the folderobject
+        * @param integer $depth Max depth (recursivity limit)
+        * @param string $type HTML-code prefix for recursive calls.
+        * @return integer The count of items on the level
         * @see getBrowsableTree()
         */
-       function getFolderTree($files_path, $depth = 999, $depthData = '', $type = '') {
+       public function getFolderTree(t3lib_file_Folder $folderObject, $depth = 999, $type = '') {
+               $depth = intval($depth);
 
                        // This generates the directory tree
-               $dirs = t3lib_div::get_dirs($files_path);
+               $subFolders = $folderObject->getSubfolders();
+
+               sort($subFolders);
+               $totalSubFolders = count($subFolders);
+
+               $HTML = '';
+               $subFolderCounter = 0;
+
+               foreach ($subFolders as $subFolder) {
+                       $subFolderCounter++;
+                               // Reserve space.
+                       $this->tree[] = array();
+                               // Get the key for this space
+                       end($this->tree);
+                       $treeKey = key($this->tree);
+
+                       $specUID = t3lib_div::md5int($subFolder->getCombinedIdentifier());
+                       $this->specUIDmap[$specUID] = $subFolder->getCombinedIdentifier();
+
+                       $row = array(
+                               'uid'    => $specUID,
+                               'path'   => $subFolder->getCombinedIdentifier(),
+                               'title'  => $subFolder->getName(),
+                               'folder' => $subFolder
+                       );
+
+                               // Make a recursive call to the next level
+                       if ($depth > 1 && $this->expandNext($specUID)) {
+                               $nextCount = $this->getFolderTree(
+                                       $subFolder,
+                                       $depth - 1,
+                                       $type
+                               );
 
-               $c = 0;
-               if (is_array($dirs)) {
-                       $depth = intval($depth);
-                       $HTML = '';
-                       $a = 0;
-                       $c = count($dirs);
-                       sort($dirs);
-
-                       foreach ($dirs as $key => $val) {
-                               $a++;
-                               $this->tree[] = array(); // Reserve space.
-                               end($this->tree);
-                               $treeKey = key($this->tree); // Get the key for this space
-                               $LN = ($a == $c) ? 'blank' : 'line';
-
-                               $val = preg_replace('/^\.\//', '', $val);
-                               $title = $val;
-                               $path = $files_path . $val . '/';
-                               $webpath = t3lib_BEfunc::getPathType_web_nonweb($path);
-
-                               $md5_uid = md5($path);
-                               $specUID = hexdec(substr($md5_uid, 0, 6));
-                               $this->specUIDmap[$specUID] = $path;
-                               $row = array();
-                               $row['path'] = $path;
-                               $row['uid'] = $specUID;
-                               $row['title'] = $title;
-
-                               if ($depth > 1 && $this->expandNext($specUID)) {
-                                       $nextCount = $this->getFolderTree(
-                                               $path,
-                                                       $depth - 1,
-                                               $this->makeHTML ? $depthData . '<img' . t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/' . $LN . '.gif', 'width="18" height="16"') . ' alt="" />' : '',
-                                               $type
-                                       );
-                                       $exp = 1; // Set "did expand" flag
+                                       // Set "did expand" flag
+                               $isOpen = 1;
+                       } else {
+                               $nextCount = $this->getNumberOfSubfolders($subFolder);
+                                       // Clear "did expand" flag
+                               $isOpen = 0;
+                       }
+
+                               // Set HTML-icons, if any:
+                       if ($this->makeHTML) {
+                               $HTML = $this->PMicon($subFolder, $subFolderCounter, $totalSubFolders, $nextCount, $isOpen);
+                               if ($subFolder->checkActionPermission('write')) {
+                                       $type = '';
+                                       $overlays = array();
                                } else {
-                                       $nextCount = $this->getCount($path);
-                                       $exp = 0; // Clear "did expand" flag
+                                       $type = 'readonly';
+                                       $overlays = array('status-overlay-locked' => array());
                                }
 
-                                       // Set HTML-icons, if any:
-                               if ($this->makeHTML) {
-                                       $HTML = $depthData . $this->PMicon($row, $a, $c, $nextCount, $exp);
+                               if ($isOpen) {
+                                       $icon = 'apps-filetree-folder-opened';
+                               } else {
+                                       $icon = 'apps-filetree-folder-default';
+                               }
 
-                                       $icon = 'gfx/i/_icon_' . $webpath . 'folders' . ($type == 'readonly' ? '_ro' : '') . '.gif';
-                                       if ($val == '_temp_') {
-                                               $icon = 'gfx/i/sysf.gif';
-                                               $row['title'] = $GLOBALS['LANG']->sl('LLL:EXT:lang/locallang_mod_file_list.xml:temp', TRUE);
-                                               $row['_title'] = '<strong>' . $GLOBALS['LANG']->sl('LLL:EXT:lang/locallang_mod_file_list.xml:temp', TRUE) . '</strong>';
-                                       }
-                                       if ($val == '_recycler_') {
-                                               $icon = 'gfx/i/recycler.gif';
-                                               $row['title'] = $GLOBALS['LANG']->sl('LLL:EXT:lang/locallang_mod_file_list.xml:recycler', TRUE);
-                                               $row['_title'] = '<strong>' . $GLOBALS['LANG']->sl('LLL:EXT:lang/locallang_mod_file_list.xml:recycler', TRUE) . '</strong>';
-                                       }
-                                       $HTML .= $this->wrapIcon('<img' . t3lib_iconWorks::skinImg($this->backPath, $icon, 'width="18" height="16"') . ' alt="" />', $row);
+                               if ($subFolder->getIdentifier() == '_temp_') {
+                                       $icon = 'apps-filetree-folder-temp';
+                                       $row['title'] = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_file_list.xml:temp', TRUE);
+                                       $row['_title'] = '<strong>' . $row['title'] . '</strong>';
                                }
 
-                                       // Finally, add the row/HTML content to the ->tree array in the reserved key.
-                               $this->tree[$treeKey] = Array(
-                                       'row' => $row,
-                                       'HTML' => $HTML,
-                                       'bank' => $this->bank
-                               );
+                               if ($subFolder->getIdentifier() == '_recycler_') {
+                                       $icon = 'apps-filetree-folder-recycler';
+                                       $row['title'] = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_file_list.xml:recycler', TRUE);
+                                       $row['_title'] = '<strong>' . $row['title'] . '</strong>';
+                               }
+
+                               $icon = t3lib_iconWorks::getSpriteIcon($icon, array('title' => $subFolder->getIdentifier()), $overlays);
+                               $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
+                       );
                }
-               return $c;
+
+               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 = '';
+
+               $titleLength = intval($this->BE_USER->uc['titleLen']);
+               if (!is_array($treeItems)) {
+                       $treeItems = $this->tree;
+               }
+
+               $out = '
+                       <!-- TYPO3 folder tree structure. -->
+                       <ul class="tree" id="treeRoot">
+               ';
+
+                       // 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 t3lib_file_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)
+                       $isExpandedFolderIdentifier = ($expandedFolderHash == t3lib_div::md5int($folderIdentifier));
+                       $idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($folderObject) . '_' . $treeItem['bank']);
+                       $itemHTML  = '';
+
+                       // If this item is the start of a new level,
+                       // then a new level <ul> is needed, but not in ajax mode
+                       if ($treeItem['isFirst'] && !($doCollapse) && !($doExpand && $isExpandedFolderIdentifier)) {
+                               $itemHTML = "<ul>\n";
+                       }
+
+                       // Add CSS classes to the list item
+                       if ($treeItem['hasSub']) {
+                               $classAttr .= ' expanded';
+                       }
+                       if ($treeItem['isLast']) {
+                               $classAttr .= ' last';
+                       }
+
+                       $itemHTML .='
+                               <li id="' . $idAttr . '" ' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '') . '><div class="treeLinkItem">'.
+                                       $treeItem['HTML'].
+                                       $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLength), $folderObject, $treeItem['bank']) . '</div>';
+
+                       if (!$treeItem['hasSub']) {
+                               $itemHTML .= "</li>\n";
+                       }
+
+                       // 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
+                       if ($treeItem['isLast'] && !$treeItem['hasSub'] && !$doCollapse && !($doExpand && $isExpandedFolderIdentifier)) {
+                               for ($i = $treeItem['invertedDepth']; $closeDepth[$i] == 1; $i++) {
+                                       $closeDepth[$i] = 0;
+                                       $itemHTML .= "</ul></li>\n";
+                               }
+                       }
+
+                               // Ajax request: collapse
+                       if ($doCollapse && $isExpandedFolderIdentifier) {
+                               $this->ajaxStatus = TRUE;
+                               return $itemHTML;
+                       }
+
+                               // Ajax request: expand
+                       if ($doExpand && $isExpandedFolderIdentifier) {
+                               $ajaxOutput .= $itemHTML;
+                               $invertedDepthOfAjaxRequestedItem = $treeItem['invertedDepth'];
+                       } elseif ($invertedDepthOfAjaxRequestedItem) {
+                               if ($treeItem['invertedDepth'] < $invertedDepthOfAjaxRequestedItem) {
+                                       $ajaxOutput .= $itemHTML;
+                               } else {
+                                       $this->ajaxStatus = TRUE;
+                                       return $ajaxOutput;
+                               }
+                       }
+
+                       $out .= $itemHTML;
+               }
+
+                       // If this is a AJAX request, output directly
+               if ($ajaxOutput) {
+                       $this->ajaxStatus = TRUE;
+                       return $ajaxOutput;
+               }
+
+                       // Finally close the first ul
+               $out .= "</ul>\n";
+               return $out;
        }
 
        /**
         * Counts the number of directories in a file path.
         *
-        * @param       string          File path.
-        * @return      integer
+        * @param string $file File path.
+        * @return integer
+        * @deprecated since TYPO3 6.0, as the folder objects do the counting automatically
         */
-       function getCount($files_path) {
+       public function getCount($file) {
+               t3lib_div::logDeprecatedFunction();
                        // This generates the directory tree
-               $dirs = t3lib_div::get_dirs($files_path);
+               $dirs = t3lib_div::get_dirs($file);
                $c = 0;
                if (is_array($dirs)) {
                        $c = count($dirs);
@@ -312,6 +570,17 @@ class t3lib_folderTree extends t3lib_treeView {
        }
 
        /**
+        * Counts the number of directories in a file path.
+        *
+        * @param t3lib_file_Folder $folderObject File path.
+        * @return integer
+        */
+       public function getNumberOfSubfolders(t3lib_file_Folder $folderObject) {
+               $subFolders = $folderObject->getSubfolders();
+               return count($subFolders);
+       }
+
+       /**
         * Get stored tree structure AND updating it if needed according to incoming PM GET var.
         *
         * @return      void
@@ -321,33 +590,126 @@ class t3lib_folderTree extends t3lib_treeView {
                        // Get stored tree structure:
                $this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName]);
 
-                       // Mapping md5-hash to shorter number:
-               $hashMap = array();
-               foreach ($this->MOUNTS as $key => $val) {
-                       $nkey = hexdec(substr($key, 0, 4));
-                       $hashMap[$nkey] = $key;
-                       $this->MOUNTS[$key]['nkey'] = $nkey;
-               }
+               $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):
-               $PM = explode('_', t3lib_div::_GP('PM')); // 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
-               if (count($PM) == 4 && $PM[3] == $this->treeName) {
-                       if (isset($this->MOUNTS[$hashMap[$PM[0]]])) {
-                               if ($PM[1]) { // set
-                                       $this->stored[$PM[0]][$PM[2]] = 1;
-                                       $this->savePosition($this->treeName);
-                               } else { // clear
-                                       unset($this->stored[$PM[0]][$PM[2]]);
-                                       $this->savePosition($this->treeName);
+                       // (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();
                        }
                }
        }
-}
 
-if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_foldertree.php'])) {
-       include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_foldertree.php']);
+       /**
+        * Helper method to map md5-hash to shorter number
+        *
+        * @param t3lib_file_Storage $storageObject
+        * @param t3lib_file_Folder $startingPointFolder
+        * @return integer
+        */
+       protected function getShortHashNumberForStorage(t3lib_file_Storage $storageObject = NULL, t3lib_file_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();
+                               if (count($fileMounts)) {
+                                       foreach ($fileMounts as $fileMount) {
+                                               $nkey = hexdec(substr(t3lib_div::md5int($fileMount['folder']->getCombinedIdentifier()), 0, 4));
+                                               $this->storageHashNumbers[$storageUid . $fileMount['folder']->getCombinedIdentifier()] = $nkey;
+                                       }
+                               } else {
+                                       $folder = $storage->getRootLevelFolder();
+                                       $nkey = hexdec(substr(t3lib_div::md5int($folder->getCombinedIdentifier()), 0, 4));
+                                       $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) {
+                       $PM = t3lib_div::_GP('PM');
+                               // 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
+               list($folderIdentifier, $treeName) = t3lib_div::revExplode('_', $folderIdentifier, 2);
+
+               return array(
+                       $mountKey,
+                       $doExpand,
+                       $folderIdentifier,
+                       $treeName
+               );
+       }
+
+       /**
+        * Generates the "PM" string to sent to expand/collapse items
+        *
+        * @param string $mountKey The mount key / storage UID
+        * @param boolean $doExpand Whether to expand/collapse
+        * @param t3lib_file_Folder $folderObject The folder object
+        * @param string $treeName The name of the tree
+        * @return string
+        */
+       protected function generateExpandCollapseParameter($mountKey = NULL, $doExpand = FALSE, t3lib_file_Folder $folderObject = NULL, $treeName = NULL) {
+               $parts = array(
+                       ($mountKey !== NULL ? $mountKey : $this->bank),
+                       ($doExpand == 1 ? 1 : 0),
+                       ($folderObject !== NULL ? t3lib_div::md5int($folderObject->getCombinedIdentifier()) : ''),
+                       ($treeName !== NULL ? $treeName : $this->treeName)
+               );
+
+               return implode('_', $parts);
+       }
+
+       /**
+        * Gets the AJAX status.
+        *
+        * @return boolean
+        */
+       public function getAjaxStatus() {
+               return $this->ajaxStatus;
+       }
 }
 
 ?>
\ No newline at end of file