[TASK] Rework folder trees based on CSS only
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Tree / View / AbstractTreeView.php
1 <?php
2 namespace TYPO3\CMS\Backend\Tree\View;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Backend\Utility\IconUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * Base class for creating a browsable array/page/folder tree in HTML
23 *
24 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
25 * @author René Fritz <r.fritz@colorcube.de>
26 */
27 abstract class AbstractTreeView {
28
29 // EXTERNAL, static:
30 // If set, the first element in the tree is always expanded.
31 /**
32 * @var int
33 */
34 public $expandFirst = 0;
35
36 // If set, then ALL items will be expanded, regardless of stored settings.
37 /**
38 * @var int
39 */
40 public $expandAll = 0;
41
42 // Holds the current script to reload to.
43 /**
44 * @var string
45 */
46 public $thisScript = '';
47
48 // Which HTML attribute to use: alt/title. See init().
49 /**
50 * @var string
51 */
52 public $titleAttrib = 'title';
53
54 // If TRUE, no context menu is rendered on icons. If set to "titlelink" the
55 // icon is linked as the title is.
56 /**
57 * @var bool
58 */
59 public $ext_IconMode = FALSE;
60
61 // If set, the id of the mounts will be added to the internal ids array
62 /**
63 * @var int
64 */
65 public $addSelfId = 0;
66
67 // Used if the tree is made of records (not folders for ex.)
68 /**
69 * @var string
70 */
71 public $title = 'no title';
72
73 // If TRUE, a default title attribute showing the UID of the record is shown.
74 // This cannot be enabled by default because it will destroy many applications
75 // where another title attribute is in fact applied later.
76 /**
77 * @var bool
78 */
79 public $showDefaultTitleAttribute = FALSE;
80
81 // If TRUE, pages containing child records which has versions will be
82 // highlighted in yellow. This might be too expensive in terms
83 // of processing power.
84 /**
85 * @var bool
86 */
87 public $highlightPagesWithVersions = TRUE;
88
89 /**
90 * Needs to be initialized with $GLOBALS['BE_USER']
91 * Done by default in init()
92 *
93 * @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
94 */
95 public $BE_USER = '';
96
97 /**
98 * Needs to be initialized with e.g. $GLOBALS['BE_USER']->returnWebmounts()
99 * Default setting in init() is 0 => 0
100 * The keys are mount-ids (can be anything basically) and the
101 * values are the ID of the root element (COULD be zero or anything else.
102 * For pages that would be the uid of the page, zero for the pagetree root.)
103 *
104 * @var string
105 */
106 public $MOUNTS = '';
107
108 /**
109 * Database table to get the tree data from.
110 * Leave blank if data comes from an array.
111 *
112 * @var string
113 */
114 public $table = '';
115
116 /**
117 * Defines the field of $table which is the parent id field (like pid for table pages).
118 *
119 * @var string
120 */
121 public $parentField = 'pid';
122
123 /**
124 * WHERE clause used for selecting records for the tree. Is set by function init.
125 * Only makes sense when $this->table is set.
126 *
127 * @see init()
128 * @var string
129 */
130 public $clause = '';
131
132 /**
133 * Field for ORDER BY. Is set by function init.
134 * Only makes sense when $this->table is set.
135 *
136 * @see init()
137 * @var string
138 */
139 public $orderByFields = '';
140
141 /**
142 * Default set of fields selected from the tree table.
143 * Make SURE that these fields names listed herein are actually possible to select from $this->table (if that variable is set to a TCA table name)
144 *
145 * @see addField()
146 * @var array
147 */
148 public $fieldArray = array('uid', 'pid', 'title');
149
150 /**
151 * List of other fields which are ALLOWED to set (here, based on the "pages" table!)
152 *
153 * @see addField()
154 * @var array
155 */
156 public $defaultList = 'uid,pid,tstamp,sorting,deleted,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,crdate,cruser_id';
157
158 /**
159 * Unique name for the tree.
160 * Used as key for storing the tree into the BE users settings.
161 * Used as key to pass parameters in links.
162 * MUST NOT contain underscore chars.
163 * etc.
164 *
165 * @var string
166 */
167 public $treeName = '';
168
169 /**
170 * A prefix for table cell id's which will be wrapped around an item.
171 * Can be used for highlighting by JavaScript.
172 * Needs to be unique if multiple trees are on one HTML page.
173 *
174 * @see printTree()
175 * @var string
176 */
177 public $domIdPrefix = 'row';
178
179 /**
180 * Back path for icons
181 *
182 * @var string
183 */
184 public $backPath;
185
186 /**
187 * If TRUE, HTML code is also accumulated in ->tree array during rendering of the tree.
188 * If 2, then also the icon prefix code (depthData) is stored
189 *
190 * @var int
191 */
192 public $makeHTML = 1;
193
194 /**
195 * If TRUE, records as selected will be stored internally in the ->recs array
196 *
197 * @var int
198 */
199 public $setRecs = 0;
200
201 /**
202 * Sets the associative array key which identifies a new sublevel if arrays are used for trees.
203 * This value has formerly been "subLevel" and "--sublevel--"
204 *
205 * @var string
206 */
207 public $subLevelID = '_SUB_LEVEL';
208
209 // *********
210 // Internal
211 // *********
212 // For record trees:
213 // one-dim array of the uid's selected.
214 /**
215 * @var array
216 */
217 public $ids = array();
218
219 // The hierarchy of element uids
220 /**
221 * @var array
222 */
223 public $ids_hierarchy = array();
224
225 // The hierarchy of versioned element uids
226 /**
227 * @var array
228 */
229 public $orig_ids_hierarchy = array();
230
231 // Temporary, internal array
232 /**
233 * @var array
234 */
235 public $buffer_idH = array();
236
237 // For FOLDER trees:
238 // Special UIDs for folders (integer-hashes of paths)
239 /**
240 * @var array
241 */
242 public $specUIDmap = array();
243
244 // For arrays:
245 // Holds the input data array
246 /**
247 * @var bool
248 */
249 public $data = FALSE;
250
251 // Holds an index with references to the data array.
252 /**
253 * @var bool
254 */
255 public $dataLookup = FALSE;
256
257 // For both types
258 // Tree is accumulated in this variable
259 /**
260 * @var array
261 */
262 public $tree = array();
263
264 // Holds (session stored) information about which items in the tree are unfolded and which are not.
265 /**
266 * @var array
267 */
268 public $stored = array();
269
270 // Points to the current mountpoint key
271 /**
272 * @var int
273 */
274 public $bank = 0;
275
276 // Accumulates the displayed records.
277 /**
278 * @var array
279 */
280 public $recs = array();
281
282 /**
283 * Sets the script url depending on being a module or script request
284 */
285 protected function determineScriptUrl() {
286 if ($moduleName = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('M')) {
287 $this->thisScript = \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleUrl($moduleName);
288 } else {
289 $this->thisScript = \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('SCRIPT_NAME');
290 }
291 }
292
293 /**
294 * @return string
295 */
296 protected function getThisScript() {
297 return strpos($this->thisScript, '?') === FALSE ? $this->thisScript . '?' : $this->thisScript . '&';
298 }
299
300 /**
301 * Initialize the tree class. Needs to be overwritten
302 * Will set ->fieldsArray, ->backPath and ->clause
303 *
304 * @param string Record WHERE clause
305 * @param string Record ORDER BY field
306 * @return void
307 */
308 public function init($clause = '', $orderByFields = '') {
309 // Setting BE_USER by default
310 $this->BE_USER = $GLOBALS['BE_USER'];
311 // Setting backpath.
312 $this->backPath = $GLOBALS['BACK_PATH'];
313 // Setting clause
314 if ($clause) {
315 $this->clause = $clause;
316 }
317 if ($orderByFields) {
318 $this->orderByFields = $orderByFields;
319 }
320 if (!is_array($this->MOUNTS)) {
321 // Dummy
322 $this->MOUNTS = array(0 => 0);
323 }
324 // Sets the tree name which is used to identify the tree, used for JavaScript and other things
325 $this->treeName = str_replace('_', '', $this->treeName ?: $this->table);
326 // Setting this to FALSE disables the use of array-trees by default
327 $this->data = FALSE;
328 $this->dataLookup = FALSE;
329 }
330
331 /**
332 * Adds a fieldname to the internal array ->fieldArray
333 *
334 * @param string $field Field name to
335 * @param bool $noCheck If set, the fieldname will be set no matter what. Otherwise the field name must either be found as key in $GLOBALS['TCA'][$table]['columns'] or in the list ->defaultList
336 * @return void
337 */
338 public function addField($field, $noCheck = 0) {
339 if ($noCheck || is_array($GLOBALS['TCA'][$this->table]['columns'][$field]) || GeneralUtility::inList($this->defaultList, $field)) {
340 $this->fieldArray[] = $field;
341 }
342 }
343
344 /**
345 * Resets the tree, recs, ids, ids_hierarchy and orig_ids_hierarchy internal variables. Use it if you need it.
346 *
347 * @return void
348 */
349 public function reset() {
350 $this->tree = array();
351 $this->recs = array();
352 $this->ids = array();
353 $this->ids_hierarchy = array();
354 $this->orig_ids_hierarchy = array();
355 }
356
357 /*******************************************
358 *
359 * output
360 *
361 *******************************************/
362 /**
363 * Will create and return the HTML code for a browsable tree
364 * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
365 *
366 * @return string HTML code for the browsable tree
367 */
368 public function getBrowsableTree() {
369 // Get stored tree structure AND updating it if needed according to incoming PM GET var.
370 $this->initializePositionSaving();
371 // Init done:
372 $treeArr = array();
373 // Traverse mounts:
374 foreach ($this->MOUNTS as $idx => $uid) {
375 // Set first:
376 $this->bank = $idx;
377 $isOpen = $this->stored[$idx][$uid] || $this->expandFirst;
378 // Save ids while resetting everything else.
379 $curIds = $this->ids;
380 $this->reset();
381 $this->ids = $curIds;
382 // Set PM icon for root of mount:
383 $cmd = $this->bank . '_' . ($isOpen ? '0_' : '1_') . $uid . '_' . $this->treeName;
384
385 $firstHtml = $this->PM_ATagWrap('', $cmd, '', $isOpen);
386 // Preparing rootRec for the mount
387 if ($uid) {
388 $rootRec = $this->getRecord($uid);
389 $firstHtml .= $this->getIcon($rootRec);
390 } else {
391 // Artificial record for the tree root, id=0
392 $rootRec = $this->getRootRecord($uid);
393 $firstHtml .= $this->getRootIcon($rootRec);
394 }
395 if (is_array($rootRec)) {
396 // In case it was swapped inside getRecord due to workspaces.
397 $uid = $rootRec['uid'];
398 // Add the root of the mount to ->tree
399 $this->tree[] = array('HTML' => $firstHtml, 'row' => $rootRec, 'hasSub' => $isOpen, 'bank' => $this->bank);
400 // If the mount is expanded, go down:
401 if ($isOpen) {
402 if ($this->addSelfId) {
403 $this->ids[] = $uid;
404 }
405 $this->getTree($uid, 999, '', '', $rootRec['_SUBCSSCLASS']);
406 }
407 // Add tree:
408 $treeArr = array_merge($treeArr, $this->tree);
409 }
410 }
411 return $this->printTree($treeArr);
412 }
413
414 /**
415 * Compiles the HTML code for displaying the structure found inside the ->tree array
416 *
417 * @param array $treeArr "tree-array" - if blank string, the internal ->tree array is used.
418 * @return string The HTML code for the tree
419 */
420 public function printTree($treeArr = '') {
421 $titleLen = (int)$this->BE_USER->uc['titleLen'];
422 if (!is_array($treeArr)) {
423 $treeArr = $this->tree;
424 }
425 $out = '';
426 $closeDepth = array();
427 foreach ($treeArr as $treeItem) {
428 $classAttr = $treeItem['row']['_CSSCLASS'];
429 if ($treeItem['isFirst']) {
430 $out .= '<ul class="list-tree">';
431 }
432
433 // Add CSS classes to the list item
434 if ($treeItem['hasSub']) {
435 $classAttr .= ' list-tree-control-open';
436 }
437
438 $idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($treeItem['row']) . '_' . $treeItem['bank']);
439 $out .= '<li id="' . $idAttr . '"' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '') . '><span class="list-tree-group">' . $treeItem['HTML'] . $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLen), $treeItem['row'], $treeItem['bank']) . '</span>';
440
441 if (!$treeItem['hasSub']) {
442 $out .= '</li>';
443 }
444
445 // We have to remember if this is the last one
446 // on level X so the last child on level X+1 closes the <ul>-tag
447 if ($treeItem['isLast']) {
448 $closeDepth[$treeItem['invertedDepth']] = 1;
449 }
450 // If this is the last one and does not have subitems, we need to close
451 // the tree as long as the upper levels have last items too
452 if ($treeItem['isLast'] && !$treeItem['hasSub']) {
453 for ($i = $treeItem['invertedDepth']; $closeDepth[$i] == 1; $i++) {
454 $closeDepth[$i] = 0;
455 $out .= '</ul></li>';
456 }
457 }
458 }
459 $out = '<ul class="list-tree" id="treeRoot">' . $out . '</ul>';
460 return $out;
461 }
462
463 /*******************************************
464 *
465 * rendering parts
466 *
467 *******************************************/
468 /**
469 * Generate the plus/minus icon for the browsable tree.
470 *
471 * @param array $row Record for the entry
472 * @param int $a The current entry number
473 * @param int $c The total number of entries. If equal to $a, a "bottom" element is returned.
474 * @param int $nextCount The number of sub-elements to the current element.
475 * @param bool $isOpen The element was expanded to render subelements if this flag is set.
476 * @return string Image tag with the plus/minus icon.
477 * @access private
478 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
479 */
480 public function PMicon($row, $a, $c, $nextCount, $isOpen) {
481 if ($nextCount) {
482 $cmd = $this->bank . '_' . ($isOpen ? '0_' : '1_') . $row['uid'] . '_' . $this->treeName;
483 $bMark = $this->bank . '_' . $row['uid'];
484 return $this->PM_ATagWrap('', $cmd, $bMark, $isOpen);
485 } else {
486 return '';
487 }
488 }
489
490 /**
491 * Wrap the plus/minus icon in a link
492 *
493 * @param string $icon HTML string to wrap, probably an image tag.
494 * @param string $cmd Command for 'PM' get var
495 * @param bool $bMark If set, the link will have a anchor point (=$bMark) and a name attribute (=$bMark)
496 * @param bool $isOpen
497 * @return string Link-wrapped input string
498 * @access private
499 */
500 public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = FALSE) {
501 if ($this->thisScript) {
502 if ($bMark) {
503 $anchor = '#' . $bMark;
504 $name = ' name="' . $bMark . '"';
505 }
506 $aUrl = $this->getThisScript() . 'PM=' . $cmd . $anchor;
507 return '<a class="list-tree-control ' . ($isOpen ? 'list-tree-control-open' : 'list-tree-control-closed') . ' href="' . htmlspecialchars($aUrl) . '"' . $name . '><i class="fa"></i></a>';
508 } else {
509 return $icon;
510 }
511 }
512
513 /**
514 * Wrapping $title in a-tags.
515 *
516 * @param string $title Title string
517 * @param string $row Item record
518 * @param int $bank Bank pointer (which mount point number)
519 * @return string
520 * @access private
521 */
522 public function wrapTitle($title, $row, $bank = 0) {
523 $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($row)) . ',' . $bank . ');';
524 return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $title . '</a>';
525 }
526
527 /**
528 * Wrapping the image tag, $icon, for the row, $row (except for mount points)
529 *
530 * @param string $icon The image tag for the icon
531 * @param array $row The row for the current element
532 * @return string The processed icon input value.
533 * @access private
534 */
535 public function wrapIcon($icon, $row) {
536 return $icon;
537 }
538
539 /**
540 * Adds attributes to image tag.
541 *
542 * @param string $icon Icon image tag
543 * @param string $attr Attributes to add, eg. ' border="0"'
544 * @return string Image tag, modified with $attr attributes added.
545 */
546 public function addTagAttributes($icon, $attr) {
547 return preg_replace('/ ?\\/?>$/', '', $icon) . ' ' . $attr . ' />';
548 }
549
550 /**
551 * Adds a red "+" to the input string, $str, if the field "php_tree_stop" in the $row (pages) is set
552 *
553 * @param string $str Input string, like a page title for the tree
554 * @param array $row record row with "php_tree_stop" field
555 * @return string Modified string
556 * @access private
557 */
558 public function wrapStop($str, $row) {
559 if ($row['php_tree_stop']) {
560 $str .= '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(array('setTempDBmount' => $row['uid']))) . '" class="text-danger">+</a> ';
561 }
562 return $str;
563 }
564
565 /*******************************************
566 *
567 * tree handling
568 *
569 *******************************************/
570 /**
571 * Returns TRUE/FALSE if the next level for $id should be expanded - based on
572 * data in $this->stored[][] and ->expandAll flag.
573 * Extending parent function
574 *
575 * @param int $id Record id/key
576 * @return bool
577 * @access private
578 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::expandNext()
579 */
580 public function expandNext($id) {
581 return $this->stored[$this->bank][$id] || $this->expandAll ? 1 : 0;
582 }
583
584 /**
585 * Get stored tree structure AND updating it if needed according to incoming PM GET var.
586 *
587 * @return void
588 * @access private
589 */
590 public function initializePositionSaving() {
591 // Get stored tree structure:
592 $this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName]);
593 // PM action
594 // (If an plus/minus icon has been clicked, the PM GET var is sent and we
595 // must update the stored positions in the tree):
596 // 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
597 $PM = explode('_', GeneralUtility::_GP('PM'));
598 if (count($PM) === 4 && $PM[3] == $this->treeName) {
599 if (isset($this->MOUNTS[$PM[0]])) {
600 // set
601 if ($PM[1]) {
602 $this->stored[$PM[0]][$PM[2]] = 1;
603 $this->savePosition();
604 } else {
605 unset($this->stored[$PM[0]][$PM[2]]);
606 $this->savePosition();
607 }
608 }
609 }
610 }
611
612 /**
613 * Saves the content of ->stored (keeps track of expanded positions in the tree)
614 * $this->treeName will be used as key for BE_USER->uc[] to store it in
615 *
616 * @return void
617 * @access private
618 */
619 public function savePosition() {
620 $this->BE_USER->uc['browseTrees'][$this->treeName] = serialize($this->stored);
621 $this->BE_USER->writeUC();
622 }
623
624 /******************************
625 *
626 * Functions that might be overwritten by extended classes
627 *
628 ********************************/
629 /**
630 * Returns the root icon for a tree/mountpoint (defaults to the globe)
631 *
632 * @param array $rec Record for root.
633 * @return string Icon image tag.
634 */
635 public function getRootIcon($rec) {
636 return $this->wrapIcon(IconUtility::getSpriteIcon('apps-pagetree-root'), $rec);
637 }
638
639 /**
640 * Get icon for the row.
641 *
642 * @param array $row Item row.
643 * @return string Image tag.
644 */
645 public function getIcon($row) {
646 $icon = IconUtility::getSpriteIconForRecord($this->table, $row, array(
647 'title' => $this->showDefaultTitleAttribute ? 'UID: ' . $row['uid'] : $this->getTitleAttrib($row),
648 'class' => 'c-recIcon'
649 ));
650 return $this->wrapIcon($icon, $row);
651 }
652
653 /**
654 * Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
655 * Do NOT htmlspecialchar the string from this function - has already been done.
656 *
657 * @param array $row The input row array (where the key "title" is used for the title)
658 * @param int $titleLen Title length (30)
659 * @return string The title.
660 */
661 public function getTitleStr($row, $titleLen = 30) {
662 $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
663 $title = trim($row['title']) === '' ? '<em>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', TRUE) . ']</em>' : $title;
664 return $title;
665 }
666
667 /**
668 * Returns the value for the image "title" attribute
669 *
670 * @param array $row The input row array (where the key "title" is used for the title)
671 * @return string The attribute value (is htmlspecialchared() already)
672 * @see wrapIcon()
673 */
674 public function getTitleAttrib($row) {
675 return htmlspecialchars($row['title']);
676 }
677
678 /**
679 * Returns the id from the record (typ. uid)
680 *
681 * @param array $row Record array
682 * @return int The "uid" field value.
683 */
684 public function getId($row) {
685 return $row['uid'];
686 }
687
688 /**
689 * Returns jump-url parameter value.
690 *
691 * @param array $row The record array.
692 * @return string The jump-url parameter.
693 */
694 public function getJumpToParam($row) {
695 return $this->getId($row);
696 }
697
698 /********************************
699 *
700 * tree data buidling
701 *
702 ********************************/
703 /**
704 * Fetches the data for the tree
705 *
706 * @param int $uid item id for which to select subitems (parent id)
707 * @param int $depth Max depth (recursivity limit)
708 * @param string $depthData HTML-code prefix for recursive calls.
709 * @param string $blankLineCode ? (internal)
710 * @param string $subCSSclass CSS class to use for <td> sub-elements
711 * @return int The count of items on the level
712 */
713 public function getTree($uid, $depth = 999, $depthData = '', $blankLineCode = '', $subCSSclass = '') {
714 // Buffer for id hierarchy is reset:
715 $this->buffer_idH = array();
716 // Init vars
717 $depth = (int)$depth;
718 $HTML = '';
719 $a = 0;
720 $res = $this->getDataInit($uid, $subCSSclass);
721 $c = $this->getDataCount($res);
722 $crazyRecursionLimiter = 999;
723 $idH = array();
724 // Traverse the records:
725 while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res, $subCSSclass))) {
726 $pageUid = ($this->table === 'pages') ? $row['uid'] : $row['pid'];
727 if (!$GLOBALS['BE_USER']->isInWebMount($pageUid)) {
728 // Current record is not within web mount => skip it
729 continue;
730 }
731
732 $a++;
733 $crazyRecursionLimiter--;
734 $newID = $row['uid'];
735 if ($newID == 0) {
736 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. See http://forge.typo3.org/issues/16150 to get more information about a possible cause.', 1294586383);
737 }
738 // Reserve space.
739 $this->tree[] = array();
740 end($this->tree);
741 // Get the key for this space
742 $treeKey = key($this->tree);
743 // If records should be accumulated, do so
744 if ($this->setRecs) {
745 $this->recs[$row['uid']] = $row;
746 }
747 // Accumulate the id of the element in the internal arrays
748 $this->ids[] = ($idH[$row['uid']]['uid'] = $row['uid']);
749 $this->ids_hierarchy[$depth][] = $row['uid'];
750 $this->orig_ids_hierarchy[$depth][] = $row['_ORIG_uid'] ?: $row['uid'];
751
752 // Make a recursive call to the next level
753 $hasSub = $this->expandNext($newID) && !$row['php_tree_stop'];
754 if ($depth > 1 && $hasSub) {
755 $nextCount = $this->getTree($newID, $depth - 1, '', '', $row['_SUBCSSCLASS']);
756 if (!empty($this->buffer_idH)) {
757 $idH[$row['uid']]['subrow'] = $this->buffer_idH;
758 }
759 // Set "did expand" flag
760 $isOpen = 1;
761 } else {
762 $nextCount = $this->getCount($newID);
763 // Clear "did expand" flag
764 $isOpen = 0;
765 }
766 // Set HTML-icons, if any:
767 if ($this->makeHTML) {
768 $HTML = $this->PMicon($row, $a, $c, $nextCount, $isOpen) . $this->wrapStop($this->getIcon($row), $row);
769 }
770 // Finally, add the row/HTML content to the ->tree array in the reserved key.
771 $this->tree[$treeKey] = array(
772 'row' => $row,
773 'HTML' => $HTML,
774 'invertedDepth' => $depth,
775 'bank' => $this->bank,
776 'hasSub' => $nextCount && $hasSub,
777 'isFirst' => $a === 1,
778 'isLast' => $a === $c,
779 );
780 }
781
782 $this->getDataFree($res);
783 $this->buffer_idH = $idH;
784 return $c;
785 }
786
787 /********************************
788 *
789 * Data handling
790 * Works with records and arrays
791 *
792 ********************************/
793 /**
794 * Returns the number of records having the parent id, $uid
795 *
796 * @param int $uid Id to count subitems for
797 * @return int
798 * @access private
799 */
800 public function getCount($uid) {
801 if (is_array($this->data)) {
802 $res = $this->getDataInit($uid);
803 return $this->getDataCount($res);
804 } else {
805 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);
806 }
807 }
808
809 /**
810 * Returns root record for uid (<=0)
811 *
812 * @param int $uid uid, <= 0 (normally, this does not matter)
813 * @return array Array with title/uid keys with values of $this->title/0 (zero)
814 */
815 public function getRootRecord($uid) {
816 return array('title' => $this->title, 'uid' => 0);
817 }
818
819 /**
820 * Returns the record for a uid.
821 * For tables: Looks up the record in the database.
822 * For arrays: Returns the fake record for uid id.
823 *
824 * @param int $uid UID to look up
825 * @return array The record
826 */
827 public function getRecord($uid) {
828 if (is_array($this->data)) {
829 return $this->dataLookup[$uid];
830 } else {
831 return BackendUtility::getRecordWSOL($this->table, $uid);
832 }
833 }
834
835 /**
836 * Getting the tree data: Selecting/Initializing data pointer to items for a certain parent id.
837 * For tables: This will make a database query to select all children to "parent"
838 * For arrays: This will return key to the ->dataLookup array
839 *
840 * @param int $parentId parent item id
841 * @param string $subCSSclass Class for sub-elements.
842 * @return mixed Data handle (Tables: An sql-resource, arrays: A parentId integer. -1 is returned if there were NO subLevel.)
843 * @access private
844 */
845 public function getDataInit($parentId, $subCSSclass = '') {
846 if (is_array($this->data)) {
847 if (!is_array($this->dataLookup[$parentId][$this->subLevelID])) {
848 $parentId = -1;
849 } else {
850 reset($this->dataLookup[$parentId][$this->subLevelID]);
851 }
852 return $parentId;
853 } else {
854 return $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);
855 }
856 }
857
858 /**
859 * Getting the tree data: Counting elements in resource
860 *
861 * @param mixed $res Data handle
862 * @return int number of items
863 * @access private
864 * @see getDataInit()
865 */
866 public function getDataCount(&$res) {
867 if (is_array($this->data)) {
868 return count($this->dataLookup[$res][$this->subLevelID]);
869 } else {
870 return $GLOBALS['TYPO3_DB']->sql_num_rows($res);
871 }
872 }
873
874 /**
875 * Getting the tree data: next entry
876 *
877 * @param mixed $res Data handle
878 * @param string $subCSSclass CSS class for sub elements (workspace related)
879 * @return array item data array OR FALSE if end of elements.
880 * @access private
881 * @see getDataInit()
882 */
883 public function getDataNext(&$res, $subCSSclass = '') {
884 if (is_array($this->data)) {
885 if ($res < 0) {
886 $row = FALSE;
887 } else {
888 list(, $row) = each($this->dataLookup[$res][$this->subLevelID]);
889 // Passing on default <td> class for subelements:
890 if (is_array($row) && $subCSSclass !== '') {
891 $row['_CSSCLASS'] = ($row['_SUBCSSCLASS'] = $subCSSclass);
892 }
893 }
894 return $row;
895 } else {
896 while ($row = @$GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
897 BackendUtility::workspaceOL($this->table, $row, $this->BE_USER->workspace, TRUE);
898 if (is_array($row)) {
899 break;
900 }
901 }
902 // Passing on default <td> class for subelements:
903 if (is_array($row) && $subCSSclass !== '') {
904 if ($this->table === 'pages' && $this->highlightPagesWithVersions && !isset($row['_CSSCLASS']) && !empty(BackendUtility::countVersionsOfRecordsOnPage($this->BE_USER->workspace, $row['uid']))) {
905 $row['_CSSCLASS'] = 'ver-versions';
906 }
907 if (!isset($row['_CSSCLASS'])) {
908 $row['_CSSCLASS'] = $subCSSclass;
909 }
910 if (!isset($row['_SUBCSSCLASS'])) {
911 $row['_SUBCSSCLASS'] = $subCSSclass;
912 }
913 }
914 return $row;
915 }
916 }
917
918 /**
919 * Getting the tree data: frees data handle
920 *
921 * @param mixed $res Data handle
922 * @return void
923 * @access private
924 */
925 public function getDataFree(&$res) {
926 if (!is_array($this->data)) {
927 $GLOBALS['TYPO3_DB']->sql_free_result($res);
928 }
929 }
930
931 /**
932 * Used to initialize class with an array to browse.
933 * The array inputted will be traversed and an internal index for lookup is created.
934 * The keys of the input array are perceived as "uid"s of records which means that keys GLOBALLY must be unique like uids are.
935 * "uid" and "pid" "fakefields" are also set in each record.
936 * All other fields are optional.
937 *
938 * @param array $dataArr The input array, see examples below in this script.
939 * @param bool $traverse Internal, for recursion.
940 * @param int $pid Internal, for recursion.
941 * @return void
942 */
943 public function setDataFromArray(&$dataArr, $traverse = FALSE, $pid = 0) {
944 if (!$traverse) {
945 $this->data = &$dataArr;
946 $this->dataLookup = array();
947 // Add root
948 $this->dataLookup[0][$this->subLevelID] = &$dataArr;
949 }
950 foreach ($dataArr as $uid => $val) {
951 $dataArr[$uid]['uid'] = $uid;
952 $dataArr[$uid]['pid'] = $pid;
953 // Gives quick access to id's
954 $this->dataLookup[$uid] = &$dataArr[$uid];
955 if (is_array($val[$this->subLevelID])) {
956 $this->setDataFromArray($dataArr[$uid][$this->subLevelID], TRUE, $uid);
957 }
958 }
959 }
960
961 /**
962 * Sets the internal data arrays
963 *
964 * @param array $treeArr Content for $this->data
965 * @param array $treeLookupArr Content for $this->dataLookup
966 * @return void
967 */
968 public function setDataFromTreeArray(&$treeArr, &$treeLookupArr) {
969 $this->data = &$treeArr;
970 $this->dataLookup = &$treeLookupArr;
971 }
972
973 }