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