TYPO3 CMS  TYPO3_6-2
FolderTreeView.php
Go to the documentation of this file.
1 <?php
3 
22 
31 
37  protected $storages = NULL;
38 
43 
50  protected $ajaxStatus = FALSE;
51 
55  protected $scope;
56 
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 
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 
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 
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 
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 
176  public function getId(\TYPO3\CMS\Core\Resource\Folder $folderObject) {
177  return GeneralUtility::md5Int($folderObject->getCombinedIdentifier());
178  }
179 
186  public function getJumpToParam(\TYPO3\CMS\Core\Resource\Folder $folderObject) {
187  return rawurlencode($folderObject->getCombinedIdentifier());
188  }
189 
198  public function getTitleStr($row, $titleLen = 30) {
199  return $row['_title'] ?: parent::getTitleStr($row, $titleLen);
200  }
201 
209  public function getTitleAttrib(\TYPO3\CMS\Core\Resource\Folder $folderObject) {
210  return htmlspecialchars($folderObject->getName());
211  }
212 
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 
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) {
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 
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 
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'),
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) {
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 
521  public function getNumberOfSubfolders(\TYPO3\CMS\Core\Resource\Folder $folderObject) {
522  $subFolders = $folderObject->getSubfolders();
523  return count($subFolders);
524  }
525 
533  public function initializePositionSaving() {
534  // Get stored tree structure:
535  $this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName]);
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 
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 
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 
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 
648  public function getAjaxStatus() {
649  return $this->ajaxStatus;
650  }
651 
655  protected function getLanguageService() {
656  return $GLOBALS['LANG'];
657  }
658 
659 }
PMicon(\TYPO3\CMS\Core\Resource\Folder $folderObject, $subFolderCounter, $totalSubFolders, $nextCount, $isExpanded)
static skinImg($backPath, $src, $wHattribs='', $outputMode=0)
getShortHashNumberForStorage(\TYPO3\CMS\Core\Resource\ResourceStorage $storageObject=NULL, \TYPO3\CMS\Core\Resource\Folder $startingPointFolder=NULL)
generateExpandCollapseParameter($mountKey=NULL, $doExpand=FALSE, \TYPO3\CMS\Core\Resource\Folder $folderObject=NULL, $treeName=NULL)
static getSpriteIconForResource(\TYPO3\CMS\Core\Resource\ResourceInterface $resource, array $options=array(), array $overlays=array())
getFolderTree(\TYPO3\CMS\Core\Resource\Folder $folderObject, $depth=999, $type='')
static hmac($input, $additionalSecret='')
getId(\TYPO3\CMS\Core\Resource\Folder $folderObject)
getNumberOfSubfolders(\TYPO3\CMS\Core\Resource\Folder $folderObject)
wrapTitle($title, \TYPO3\CMS\Core\Resource\Folder $folderObject, $bank=0)
wrapIcon($icon, \TYPO3\CMS\Core\Resource\Folder $folderObject)
if(isset($ajaxID)) if(in_array( $ajaxID, $noUserAjaxIDs))
Re-apply pairs of single-quotes to the text.
Definition: ajax.php:40
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
static revExplode($delimiter, $string, $count=0)
PMiconATagWrap($icon, $cmd, $isExpand=TRUE)
getJumpToParam(\TYPO3\CMS\Core\Resource\Folder $folderObject)