TYPO3 CMS  TYPO3_6-2
AbstractTreeView.php
Go to the documentation of this file.
1 <?php
3 
21 
28 abstract class AbstractTreeView {
29 
30  // EXTERNAL, static:
31  // If set, the first element in the tree is always expanded.
35  public $expandFirst = 0;
36 
37  // If set, then ALL items will be expanded, regardless of stored settings.
41  public $expandAll = 0;
42 
43  // Holds the current script to reload to.
47  public $thisScript = '';
48 
49  // Which HTML attribute to use: alt/title. See init().
53  public $titleAttrib = 'title';
54 
55  // If TRUE, no context menu is rendered on icons. If set to "titlelink" the
56  // icon is linked as the title is.
60  public $ext_IconMode = FALSE;
61 
62  // If set, the id of the mounts will be added to the internal ids array
66  public $addSelfId = 0;
67 
68  // Used if the tree is made of records (not folders for ex.)
72  public $title = 'no title';
73 
74  // If TRUE, a default title attribute showing the UID of the record is shown.
75  // This cannot be enabled by default because it will destroy many applications
76  // where another title attribute is in fact applied later.
81 
82  // If TRUE, pages containing child records which has versions will be
83  // highlighted in yellow. This might be too expensive in terms
84  // of processing power.
89 
97  public $BE_USER = '';
98 
108  public $MOUNTS = '';
109 
116  public $table = '';
117 
123  public $parentField = 'pid';
124 
132  public $clause = '';
133 
141  public $orderByFields = '';
142 
150  public $fieldArray = array('uid', 'pid', 'title');
151 
158  public $defaultList = 'uid,pid,tstamp,sorting,deleted,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,crdate,cruser_id';
159 
169  public $treeName = '';
170 
179  public $domIdPrefix = 'row';
180 
186  public $backPath;
187 
193  public $iconPath = '';
194 
200  public $iconName = 'default.gif';
201 
208  public $makeHTML = 1;
209 
215  public $setRecs = 0;
216 
223  public $subLevelID = '_SUB_LEVEL';
224 
225  // *********
226  // Internal
227  // *********
228  // For record trees:
229  // one-dim array of the uid's selected.
233  public $ids = array();
234 
235  // The hierarchy of element uids
239  public $ids_hierarchy = array();
240 
241  // The hierarchy of versioned element uids
245  public $orig_ids_hierarchy = array();
246 
247  // Temporary, internal array
251  public $buffer_idH = array();
252 
253  // For FOLDER trees:
254  // Special UIDs for folders (integer-hashes of paths)
258  public $specUIDmap = array();
259 
260  // For arrays:
261  // Holds the input data array
265  public $data = FALSE;
266 
267  // Holds an index with references to the data array.
271  public $dataLookup = FALSE;
272 
273  // For both types
274  // Tree is accumulated in this variable
278  public $tree = array();
279 
280  // Holds (session stored) information about which items in the tree are unfolded and which are not.
284  public $stored = array();
285 
286  // Points to the current mountpoint key
290  public $bank = 0;
291 
292  // Accumulates the displayed records.
296  public $recs = array();
297 
301  protected $workspaceService = NULL;
302 
306  protected function determineScriptUrl() {
307  if ($moduleName = GeneralUtility::_GP('M')) {
309  } else {
310  $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
311  }
312  }
313 
317  protected function getThisScript() {
318  return strpos($this->thisScript, '?') === FALSE ? $this->thisScript . '?' : $this->thisScript . '&';
319  }
320 
330  public function init($clause = '', $orderByFields = '') {
331  // Setting BE_USER by default
332  $this->BE_USER = $GLOBALS['BE_USER'];
333  // Setting title attribute to use.
334  $this->titleAttrib = 'title';
335  // Setting backpath.
336  $this->backPath = $GLOBALS['BACK_PATH'];
337  // Setting clause
338  if ($clause) {
339  $this->clause = $clause;
340  }
341  if ($orderByFields) {
342  $this->orderByFields = $orderByFields;
343  }
344  if (!is_array($this->MOUNTS)) {
345  // Dummy
346  $this->MOUNTS = array(0 => 0);
347  }
348  $this->setTreeName();
349  // Setting this to FALSE disables the use of array-trees by default
350  $this->data = FALSE;
351  $this->dataLookup = FALSE;
352  }
353 
362  public function setTreeName($treeName = '') {
363  $this->treeName = $treeName ?: $this->treeName;
364  $this->treeName = $this->treeName ?: $this->table;
365  $this->treeName = str_replace('_', '', $this->treeName);
366  }
367 
376  public function addField($field, $noCheck = 0) {
377  if ($noCheck || is_array($GLOBALS['TCA'][$this->table]['columns'][$field]) || GeneralUtility::inList($this->defaultList, $field)) {
378  $this->fieldArray[] = $field;
379  }
380  }
381 
388  public function reset() {
389  $this->tree = array();
390  $this->recs = array();
391  $this->ids = array();
392  $this->ids_hierarchy = array();
393  $this->orig_ids_hierarchy = array();
394  }
395 
396  /*******************************************
397  *
398  * output
399  *
400  *******************************************/
408  public function getBrowsableTree() {
409  // Get stored tree structure AND updating it if needed according to incoming PM GET var.
410  $this->initializePositionSaving();
411  // Init done:
412  $treeArr = array();
413  // Traverse mounts:
414  foreach ($this->MOUNTS as $idx => $uid) {
415  // Set first:
416  $this->bank = $idx;
417  $isOpen = $this->stored[$idx][$uid] || $this->expandFirst;
418  // Save ids while resetting everything else.
419  $curIds = $this->ids;
420  $this->reset();
421  $this->ids = $curIds;
422  // Set PM icon for root of mount:
423  $cmd = $this->bank . '_' . ($isOpen ? '0_' : '1_') . $uid . '_' . $this->treeName;
424  $icon = IconUtility::getSpriteIcon('treeline-'. ($isOpen ? 'minus' : 'plus') . 'only');
425 
426  $firstHtml = $this->PM_ATagWrap($icon, $cmd);
427  // Preparing rootRec for the mount
428  if ($uid) {
429  $rootRec = $this->getRecord($uid);
430  $firstHtml .= $this->getIcon($rootRec);
431  } else {
432  // Artificial record for the tree root, id=0
433  $rootRec = $this->getRootRecord($uid);
434  $firstHtml .= $this->getRootIcon($rootRec);
435  }
436  if (is_array($rootRec)) {
437  // In case it was swapped inside getRecord due to workspaces.
438  $uid = $rootRec['uid'];
439  // Add the root of the mount to ->tree
440  $this->tree[] = array('HTML' => $firstHtml, 'row' => $rootRec, 'bank' => $this->bank);
441  // If the mount is expanded, go down:
442  if ($isOpen) {
443  // Set depth:
444  $depthD = IconUtility::getSpriteIcon('treeline-blank');
445  if ($this->addSelfId) {
446  $this->ids[] = $uid;
447  }
448  $this->getTree($uid, 999, $depthD, '', $rootRec['_SUBCSSCLASS']);
449  }
450  // Add tree:
451  $treeArr = array_merge($treeArr, $this->tree);
452  }
453  }
454  return $this->printTree($treeArr);
455  }
456 
464  public function printTree($treeArr = '') {
465  $titleLen = (int)$this->BE_USER->uc['titleLen'];
466  if (!is_array($treeArr)) {
467  $treeArr = $this->tree;
468  }
469  $out = '';
470  // put a table around it with IDs to access the rows from JS
471  // not a problem if you don't need it
472  // In XHTML there is no "name" attribute of <td> elements -
473  // but Mozilla will not be able to highlight rows if the name
474  // attribute is NOT there.
475  $out .= '
476 
477  <!--
478  TYPO3 tree structure.
479  -->
480  <table cellpadding="0" cellspacing="0" border="0" id="typo3-tree">';
481  foreach ($treeArr as $k => $v) {
482  $idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($v['row']) . '_' . $v['bank']);
483  $out .= '
484  <tr>
485  <td id="' . $idAttr . '"' . ($v['row']['_CSSCLASS'] ? ' class="' . $v['row']['_CSSCLASS'] . '"' : '') . '>' . $v['HTML'] . $this->wrapTitle($this->getTitleStr($v['row'], $titleLen), $v['row'], $v['bank']) . '</td>
486  </tr>
487  ';
488  }
489  $out .= '
490  </table>';
491  return $out;
492  }
493 
494  /*******************************************
495  *
496  * rendering parts
497  *
498  *******************************************/
512  public function PMicon($row, $a, $c, $nextCount, $exp) {
513  $PM = $nextCount ? ($exp ? 'minus' : 'plus') : 'join';
514  $BTM = $a == $c ? 'bottom' : '';
515  $icon = IconUtility::getSpriteIcon('treeline-' . $PM . $BTM);
516  if ($nextCount) {
517  $cmd = $this->bank . '_' . ($exp ? '0_' : '1_') . $row['uid'] . '_' . $this->treeName;
518  $bMark = $this->bank . '_' . $row['uid'];
519  $icon = $this->PM_ATagWrap($icon, $cmd, $bMark);
520  }
521  return $icon;
522  }
523 
534  public function PM_ATagWrap($icon, $cmd, $bMark = '') {
535  if ($this->thisScript) {
536  if ($bMark) {
537  $anchor = '#' . $bMark;
538  $name = ' name="' . $bMark . '"';
539  }
540  $aUrl = $this->getThisScript() . 'PM=' . $cmd . $anchor;
541  return '<a href="' . htmlspecialchars($aUrl) . '"' . $name . '>' . $icon . '</a>';
542  } else {
543  return $icon;
544  }
545  }
546 
557  public function wrapTitle($title, $row, $bank = 0) {
558  $aOnClick = 'return jumpTo(\'' . $this->getJumpToParam($row) . '\',this,\'' . $this->domIdPrefix . $this->getId($row) . '\',' . $bank . ');';
559  return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $title . '</a>';
560  }
561 
571  public function wrapIcon($icon, $row) {
572  return $icon;
573  }
574 
583  public function addTagAttributes($icon, $attr) {
584  return preg_replace('/ ?\\/?>$/', '', $icon) . ' ' . $attr . ' />';
585  }
586 
596  public function wrapStop($str, $row) {
597  if ($row['php_tree_stop']) {
598  $str .= '<span class="typo3-red"><a href="' . htmlspecialchars(GeneralUtility::linkThisScript(array('setTempDBmount' => $row['uid']))) . '" class="typo3-red">+</a> </span>';
599  }
600  return $str;
601  }
602 
603  /*******************************************
604  *
605  * tree handling
606  *
607  *******************************************/
619  public function expandNext($id) {
620  return $this->stored[$this->bank][$id] || $this->expandAll ? 1 : 0;
621  }
622 
630  public function initializePositionSaving() {
631  // Get stored tree structure:
632  $this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName]);
633  // PM action
634  // (If an plus/minus icon has been clicked, the PM GET var is sent and we
635  // must update the stored positions in the tree):
636  // 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
637  $PM = explode('_', GeneralUtility::_GP('PM'));
638  if (count($PM) == 4 && $PM[3] == $this->treeName) {
639  if (isset($this->MOUNTS[$PM[0]])) {
640  // set
641  if ($PM[1]) {
642  $this->stored[$PM[0]][$PM[2]] = 1;
643  $this->savePosition();
644  } else {
645  unset($this->stored[$PM[0]][$PM[2]]);
646  $this->savePosition();
647  }
648  }
649  }
650  }
651 
660  public function savePosition() {
661  $this->BE_USER->uc['browseTrees'][$this->treeName] = serialize($this->stored);
662  $this->BE_USER->writeUC();
663  }
664 
665  /******************************
666  *
667  * Functions that might be overwritten by extended classes
668  *
669  ********************************/
677  public function getRootIcon($rec) {
678  return $this->wrapIcon(IconUtility::getSpriteIcon('apps-pagetree-root'), $rec);
679  }
680 
689  public function getIcon($row) {
690  if ($this->iconPath && $this->iconName) {
691  $icon = '<img' . IconUtility::skinImg('', ($this->iconPath . $this->iconName), 'width="18" height="16"') . ' alt=""' . ($this->showDefaultTitleAttribute ? ' title="UID: ' . $row['uid'] . '"' : '') . ' />';
692  } else {
693  $icon = IconUtility::getSpriteIconForRecord($this->table, $row, array(
694  'title' => $this->showDefaultTitleAttribute ? 'UID: ' . $row['uid'] : $this->getTitleAttrib($row),
695  'class' => 'c-recIcon'
696  ));
697  }
698  return $this->wrapIcon($icon, $row);
699  }
700 
710  public function getTitleStr($row, $titleLen = 30) {
711  if ($this->ext_showNavTitle && strlen(trim($row['nav_title'])) > 0) {
712  $title = '<span title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tca.xlf:title', TRUE) . ' ' . htmlspecialchars(trim($row['title'])) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['nav_title'], $titleLen)) . '</span>';
713  } else {
714  $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
715  if (strlen(trim($row['nav_title'])) > 0) {
716  $title = '<span title="' . $GLOBALS['LANG']->sL('LLL:EXT:cms/locallang_tca.xlf:pages.nav_title', TRUE) . ' ' . htmlspecialchars(trim($row['nav_title'])) . '">' . $title . '</span>';
717  }
718  $title = strlen(trim($row['title'])) == 0 ? '<em>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', TRUE) . ']</em>' : $title;
719  }
720  return $title;
721  }
722 
731  public function getTitleAttrib($row) {
732  return htmlspecialchars($row['title']);
733  }
734 
742  public function getId($row) {
743  return $row['uid'];
744  }
745 
753  public function getJumpToParam($row) {
754  return $this->getId($row);
755  }
756 
757  /********************************
758  *
759  * tree data buidling
760  *
761  ********************************/
773  public function getTree($uid, $depth = 999, $depthData = '', $blankLineCode = '', $subCSSclass = '') {
774  // Buffer for id hierarchy is reset:
775  $this->buffer_idH = array();
776  // Init vars
777  $depth = (int)$depth;
778  $HTML = '';
779  $a = 0;
780  $res = $this->getDataInit($uid, $subCSSclass);
781  $c = $this->getDataCount($res);
782  $crazyRecursionLimiter = 999;
783  $idH = array();
784  // Traverse the records:
785  while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res, $subCSSclass))) {
786  $pageUid = ($this->table === 'pages') ? $row['uid'] : $row['pid'];
787  if (!$GLOBALS['BE_USER']->isInWebMount($pageUid)) {
788  // Current record is not within web mount => skip it
789  continue;
790  }
791 
792  $a++;
793  $crazyRecursionLimiter--;
794  $newID = $row['uid'];
795  if ($newID == 0) {
796  throw new \RuntimeException('Endless recursion detected: TYPO3 has detected an error in the database. Please fix it manually (e.g. using phpMyAdmin) and change the UID of ' . $this->table . ':0 to a new value.<br /><br />See <a href="http://forge.typo3.org/issues/16150" target="_blank">forge.typo3.org/issues/16150</a> to get more information about a possible cause.', 1294586383);
797  }
798  // Reserve space.
799  $this->tree[] = array();
800  end($this->tree);
801  // Get the key for this space
802  $treeKey = key($this->tree);
803  $LN = $a == $c ? 'blank' : 'line';
804  // If records should be accumulated, do so
805  if ($this->setRecs) {
806  $this->recs[$row['uid']] = $row;
807  }
808  // Accumulate the id of the element in the internal arrays
809  $this->ids[] = ($idH[$row['uid']]['uid'] = $row['uid']);
810  $this->ids_hierarchy[$depth][] = $row['uid'];
811  $this->orig_ids_hierarchy[$depth][] = $row['_ORIG_uid'] ?: $row['uid'];
812 
813  // Make a recursive call to the next level
814  $HTML_depthData = $depthData . IconUtility::getSpriteIcon('treeline-' . $LN);
815  if ($depth > 1 && $this->expandNext($newID) && !$row['php_tree_stop']) {
816  $nextCount = $this->getTree($newID, $depth - 1, $this->makeHTML ? $HTML_depthData : '', $blankLineCode . ',' . $LN, $row['_SUBCSSCLASS']);
817  if (count($this->buffer_idH)) {
818  $idH[$row['uid']]['subrow'] = $this->buffer_idH;
819  }
820  // Set "did expand" flag
821  $exp = 1;
822  } else {
823  $nextCount = $this->getCount($newID);
824  // Clear "did expand" flag
825  $exp = 0;
826  }
827  // Set HTML-icons, if any:
828  if ($this->makeHTML) {
829  $HTML = $depthData . $this->PMicon($row, $a, $c, $nextCount, $exp);
830  $HTML .= $this->wrapStop($this->getIcon($row), $row);
831  }
832  // Finally, add the row/HTML content to the ->tree array in the reserved key.
833  $this->tree[$treeKey] = array(
834  'row' => $row,
835  'HTML' => $HTML,
836  'HTML_depthData' => $this->makeHTML == 2 ? $HTML_depthData : '',
837  'invertedDepth' => $depth,
838  'blankLineCode' => $blankLineCode,
839  'bank' => $this->bank
840  );
841  }
842  $this->getDataFree($res);
843  $this->buffer_idH = $idH;
844  return $c;
845  }
846 
847  /********************************
848  *
849  * Data handling
850  * Works with records and arrays
851  *
852  ********************************/
861  public function getCount($uid) {
862  if (is_array($this->data)) {
863  $res = $this->getDataInit($uid);
864  return $this->getDataCount($res);
865  } else {
866  return $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $this->table, $this->parentField . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($uid, $this->table) . BackendUtility::deleteClause($this->table) . BackendUtility::versioningPlaceholderClause($this->table) . $this->clause);
867  }
868  }
869 
877  public function getRootRecord($uid) {
878  return array('title' => $this->title, 'uid' => 0);
879  }
880 
890  public function getRecord($uid) {
891  if (is_array($this->data)) {
892  return $this->dataLookup[$uid];
893  } else {
894  return BackendUtility::getRecordWSOL($this->table, $uid);
895  }
896  }
897 
909  public function getDataInit($parentId, $subCSSclass = '') {
910  if (is_array($this->data)) {
911  if (!is_array($this->dataLookup[$parentId][$this->subLevelID])) {
912  $parentId = -1;
913  } else {
914  reset($this->dataLookup[$parentId][$this->subLevelID]);
915  }
916  return $parentId;
917  } else {
918  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(implode(',', $this->fieldArray), $this->table, $this->parentField . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($parentId, $this->table) . BackendUtility::deleteClause($this->table) . BackendUtility::versioningPlaceholderClause($this->table) . $this->clause, '', $this->orderByFields);
919  return $res;
920  }
921  }
922 
932  public function getDataCount(&$res) {
933  if (is_array($this->data)) {
934  return count($this->dataLookup[$res][$this->subLevelID]);
935  } else {
936  $c = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
937  return $c;
938  }
939  }
940 
951  public function getDataNext(&$res, $subCSSclass = '') {
952  if (is_array($this->data)) {
953  if ($res < 0) {
954  $row = FALSE;
955  } else {
956  list(, $row) = each($this->dataLookup[$res][$this->subLevelID]);
957  // Passing on default <td> class for subelements:
958  if (is_array($row) && $subCSSclass !== '') {
959  $row['_CSSCLASS'] = ($row['_SUBCSSCLASS'] = $subCSSclass);
960  }
961  }
962  return $row;
963  } else {
964  while ($row = @$GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
965  BackendUtility::workspaceOL($this->table, $row, $this->BE_USER->workspace, TRUE);
966  if (is_array($row)) {
967  break;
968  }
969  }
970  // Passing on default <td> class for subelements:
971  if (is_array($row) && $subCSSclass !== '') {
972  if ($this->table === 'pages' && $this->highlightPagesWithVersions && !isset($row['_CSSCLASS']) && $this->hasPageRecordVersions($this->BE_USER->workspace, $row['uid'])) {
973  $row['_CSSCLASS'] = 'ver-versions';
974  }
975  if (!isset($row['_CSSCLASS'])) {
976  $row['_CSSCLASS'] = $subCSSclass;
977  }
978  if (!isset($row['_SUBCSSCLASS'])) {
979  $row['_SUBCSSCLASS'] = $subCSSclass;
980  }
981  }
982  return $row;
983  }
984  }
985 
994  public function getDataFree(&$res) {
995  if (!is_array($this->data)) {
996  $GLOBALS['TYPO3_DB']->sql_free_result($res);
997  }
998  }
999 
1013  public function setDataFromArray(&$dataArr, $traverse = FALSE, $pid = 0) {
1014  if (!$traverse) {
1015  $this->data = &$dataArr;
1016  $this->dataLookup = array();
1017  // Add root
1018  $this->dataLookup[0][$this->subLevelID] = &$dataArr;
1019  }
1020  foreach ($dataArr as $uid => $val) {
1021  $dataArr[$uid]['uid'] = $uid;
1022  $dataArr[$uid]['pid'] = $pid;
1023  // Gives quick access to id's
1024  $this->dataLookup[$uid] = &$dataArr[$uid];
1025  if (is_array($val[$this->subLevelID])) {
1026  $this->setDataFromArray($dataArr[$uid][$this->subLevelID], TRUE, $uid);
1027  }
1028  }
1029  }
1030 
1039  public function setDataFromTreeArray(&$treeArr, &$treeLookupArr) {
1040  $this->data = &$treeArr;
1041  $this->dataLookup = &$treeLookupArr;
1042  }
1043 
1049  protected function hasPageRecordVersions($workspaceId, $pageId) {
1050  if (!ExtensionManagementUtility::isLoaded('workspaces')) {
1051  return FALSE;
1052  }
1053 
1054  return $this->getWorkspaceService()->hasPageRecordVersions($workspaceId, $pageId);
1055 
1056  }
1057 
1061  protected function getWorkspaceService() {
1062  if ($this->workspaceService === NULL) {
1063  $this->workspaceService = GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService');
1064  }
1065 
1066  return $this->workspaceService;
1067  }
1068 
1069 }
PMicon($row, $a, $c, $nextCount, $exp)
$moduleName
Definition: mod.php:22
$uid
Definition: server.php:36
setDataFromArray(&$dataArr, $traverse=FALSE, $pid=0)
getTree($uid, $depth=999, $depthData='', $blankLineCode='', $subCSSclass='')
static getModuleUrl($moduleName, $urlParameters=array(), $backPathOverride=FALSE, $returnAbsoluteUrl=FALSE)
static getSpriteIcon($iconName, array $options=array(), array $overlays=array())
setDataFromTreeArray(&$treeArr, &$treeLookupArr)
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'][]