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