[BUGFIX] Fix several typos in php comments
[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 // Internal
197 // *********
198 // For record trees:
199 // one-dim array of the uid's selected.
200 /**
201 * @var array
202 */
203 public $ids = [];
204
205 // The hierarchy of element uids
206 /**
207 * @var array
208 */
209 public $ids_hierarchy = [];
210
211 // The hierarchy of versioned element uids
212 /**
213 * @var array
214 */
215 public $orig_ids_hierarchy = [];
216
217 // Temporary, internal array
218 /**
219 * @var array
220 */
221 public $buffer_idH = [];
222
223 // For FOLDER trees:
224 // Special UIDs for folders (integer-hashes of paths)
225 /**
226 * @var array
227 */
228 public $specUIDmap = [];
229
230 // For both types
231 // Tree is accumulated in this variable
232 /**
233 * @var array
234 */
235 public $tree = [];
236
237 // Holds (session stored) information about which items in the tree are unfolded and which are not.
238 /**
239 * @var array
240 */
241 public $stored = [];
242
243 // Points to the current mountpoint key
244 /**
245 * @var int
246 */
247 public $bank = 0;
248
249 // Accumulates the displayed records.
250 /**
251 * @var array
252 */
253 public $recs = [];
254
255 /**
256 * Constructor
257 */
258 public function __construct()
259 {
260 $this->determineScriptUrl();
261 }
262
263 /**
264 * Sets the script url depending on being a module or script request
265 */
266 protected function determineScriptUrl()
267 {
268 if ($routePath = GeneralUtility::_GP('route')) {
269 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
270 $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
271 } else {
272 $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
273 }
274 }
275
276 /**
277 * @return string
278 */
279 protected function getThisScript()
280 {
281 return strpos($this->thisScript, '?') === false ? $this->thisScript . '?' : $this->thisScript . '&';
282 }
283
284 /**
285 * Initialize the tree class. Needs to be overwritten
286 *
287 * @param string $clause Record WHERE clause
288 * @param string $orderByFields Record ORDER BY field
289 */
290 public function init($clause = '', $orderByFields = '')
291 {
292 // Setting BE_USER by default
293 $this->BE_USER = $GLOBALS['BE_USER'];
294 // Setting clause
295 if ($clause) {
296 $this->clause = $clause;
297 }
298 if ($orderByFields) {
299 $this->orderByFields = $orderByFields;
300 }
301 if (!is_array($this->MOUNTS)) {
302 // Dummy
303 $this->MOUNTS = [0 => 0];
304 }
305 // Sets the tree name which is used to identify the tree, used for JavaScript and other things
306 $this->treeName = str_replace('_', '', $this->treeName ?: $this->table);
307 }
308
309 /**
310 * Adds a fieldname to the internal array ->fieldArray
311 *
312 * @param string $field Field name to
313 * @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
314 */
315 public function addField($field, $noCheck = false)
316 {
317 if ($noCheck || is_array($GLOBALS['TCA'][$this->table]['columns'][$field]) || GeneralUtility::inList($this->defaultList, $field)) {
318 $this->fieldArray[] = $field;
319 }
320 }
321
322 /**
323 * Resets the tree, recs, ids, ids_hierarchy and orig_ids_hierarchy internal variables. Use it if you need it.
324 */
325 public function reset()
326 {
327 $this->tree = [];
328 $this->recs = [];
329 $this->ids = [];
330 $this->ids_hierarchy = [];
331 $this->orig_ids_hierarchy = [];
332 }
333
334 /*******************************************
335 *
336 * output
337 *
338 *******************************************/
339 /**
340 * Will create and return the HTML code for a browsable tree
341 * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
342 *
343 * @return string HTML code for the browsable tree
344 */
345 public function getBrowsableTree()
346 {
347 // Get stored tree structure AND updating it if needed according to incoming PM GET var.
348 $this->initializePositionSaving();
349 // Init done:
350 $lastMountPointPid = 0;
351 $treeArr = [];
352 // Traverse mounts:
353 foreach ($this->MOUNTS as $idx => $uid) {
354 // Set first:
355 $this->bank = $idx;
356 $isOpen = $this->stored[$idx][$uid] || $this->expandFirst;
357 // Save ids while resetting everything else.
358 $curIds = $this->ids;
359 $this->reset();
360 $this->ids = $curIds;
361 // Set PM icon for root of mount:
362 $cmd = $this->bank . '_' . ($isOpen ? '0_' : '1_') . $uid . '_' . $this->treeName;
363
364 $firstHtml = $this->PM_ATagWrap('', $cmd, '', $isOpen);
365 // Preparing rootRec for the mount
366 if ($uid) {
367 $rootRec = $this->getRecord($uid);
368 if (is_array($rootRec)) {
369 $firstHtml .= $this->getIcon($rootRec);
370 }
371
372 if ($this->ext_showPathAboveMounts) {
373 $mountPointPid = $rootRec['pid'];
374 if ($lastMountPointPid !== $mountPointPid) {
375 $title = $this->getMountPointPath((int)$mountPointPid);
376 $this->tree[] = ['isMountPointPath' => true, 'title' => $title];
377 }
378 $lastMountPointPid = $mountPointPid;
379 }
380 } else {
381 // Artificial record for the tree root, id=0
382 $rootRec = $this->getRootRecord();
383 $firstHtml .= $this->getRootIcon($rootRec);
384 }
385 if (is_array($rootRec)) {
386 // In case it was swapped inside getRecord due to workspaces.
387 $uid = $rootRec['uid'];
388 // Add the root of the mount to ->tree
389 $this->tree[] = ['HTML' => $firstHtml, 'row' => $rootRec, 'hasSub' => $isOpen, 'bank' => $this->bank];
390 // If the mount is expanded, go down:
391 if ($isOpen) {
392 $depthData = '<span class="treeline-icon treeline-icon-clear"></span>';
393 if ($this->addSelfId) {
394 $this->ids[] = $uid;
395 }
396 $this->getTree($uid, 999, $depthData);
397 }
398 // Add tree:
399 $treeArr = array_merge($treeArr, $this->tree);
400 }
401 }
402 return $this->printTree($treeArr);
403 }
404
405 /**
406 * Compiles the HTML code for displaying the structure found inside the ->tree array
407 *
408 * @param array|string $treeArr "tree-array" - if blank string, the internal ->tree array is used.
409 * @return string The HTML code for the tree
410 */
411 public function printTree($treeArr = '')
412 {
413 $titleLen = (int)$this->BE_USER->uc['titleLen'];
414 if (!is_array($treeArr)) {
415 $treeArr = $this->tree;
416 }
417 $out = '';
418 $closeDepth = [];
419 foreach ($treeArr as $treeItem) {
420 $classAttr = '';
421 if ($treeItem['isFirst']) {
422 $out .= '<ul class="list-tree">';
423 }
424
425 // Add CSS classes to the list item
426 if ($treeItem['hasSub']) {
427 $classAttr .= ' list-tree-control-open';
428 }
429
430 $idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($treeItem['row']) . '_' . $treeItem['bank']);
431 $out .= '
432 <li id="' . $idAttr . '"' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '') . '>
433 <span class="list-tree-group">
434 <span class="list-tree-icon">' . $treeItem['HTML'] . '</span>
435 <span class="list-tree-title">' . $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLen), $treeItem['row'], $treeItem['bank']) . '</span>
436 </span>';
437
438 if (!$treeItem['hasSub']) {
439 $out .= '</li>';
440 }
441
442 // We have to remember if this is the last one
443 // on level X so the last child on level X+1 closes the <ul>-tag
444 if ($treeItem['isLast']) {
445 $closeDepth[$treeItem['invertedDepth']] = 1;
446 }
447 // If this is the last one and does not have subitems, we need to close
448 // the tree as long as the upper levels have last items too
449 if ($treeItem['isLast'] && !$treeItem['hasSub']) {
450 for ($i = $treeItem['invertedDepth']; $closeDepth[$i] == 1; $i++) {
451 $closeDepth[$i] = 0;
452 $out .= '</ul></li>';
453 }
454 }
455 }
456 $out = '<ul class="list-tree list-tree-root list-tree-root-clean">' . $out . '</ul>';
457 return $out;
458 }
459
460 /*******************************************
461 *
462 * rendering parts
463 *
464 *******************************************/
465 /**
466 * Generate the plus/minus icon for the browsable tree.
467 *
468 * @param array $row Record for the entry
469 * @param int $a The current entry number
470 * @param int $c The total number of entries. If equal to $a, a "bottom" element is returned.
471 * @param int $nextCount The number of sub-elements to the current element.
472 * @param bool $isOpen The element was expanded to render subelements if this flag is set.
473 * @return string Image tag with the plus/minus icon.
474 * @internal
475 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
476 */
477 public function PMicon($row, $a, $c, $nextCount, $isOpen)
478 {
479 if ($nextCount) {
480 $cmd = $this->bank . '_' . ($isOpen ? '0_' : '1_') . $row['uid'] . '_' . $this->treeName;
481 $bMark = $this->bank . '_' . $row['uid'];
482 return $this->PM_ATagWrap('', $cmd, $bMark, $isOpen);
483 }
484 return '';
485 }
486
487 /**
488 * Wrap the plus/minus icon in a link
489 *
490 * @param string $icon HTML string to wrap, probably an image tag.
491 * @param string $cmd Command for 'PM' get var
492 * @param string $bMark If set, the link will have an anchor point (=$bMark) and a name attribute (=$bMark)
493 * @param bool $isOpen
494 * @return string Link-wrapped input string
495 * @internal
496 */
497 public function PM_ATagWrap($icon, $cmd, $bMark = '', $isOpen = false)
498 {
499 if ($this->thisScript) {
500 $anchor = $bMark ? '#' . $bMark : '';
501 $name = $bMark ? ' name="' . $bMark . '"' : '';
502 $aUrl = $this->getThisScript() . 'PM=' . $cmd . $anchor;
503 return '<a class="list-tree-control ' . ($isOpen ? 'list-tree-control-open' : 'list-tree-control-closed') . '" href="' . htmlspecialchars($aUrl) . '"' . $name . '><i class="fa"></i></a>';
504 }
505 return $icon;
506 }
507
508 /**
509 * Wrapping $title in a-tags.
510 *
511 * @param string $title Title string
512 * @param array $row Item record
513 * @param int $bank Bank pointer (which mount point number)
514 * @return string
515 * @internal
516 */
517 public function wrapTitle($title, $row, $bank = 0)
518 {
519 $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($row)) . ',' . $bank . ');';
520 return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $title . '</a>';
521 }
522
523 /**
524 * Wrapping the image tag, $icon, for the row, $row (except for mount points)
525 *
526 * @param string $icon The image tag for the icon
527 * @param array $row The row for the current element
528 * @return string The processed icon input value.
529 * @internal
530 */
531 public function wrapIcon($icon, $row)
532 {
533 return $icon;
534 }
535
536 /**
537 * Adds attributes to image tag.
538 *
539 * @param string $icon Icon image tag
540 * @param string $attr Attributes to add, eg. ' border="0"'
541 * @return string Image tag, modified with $attr attributes added.
542 */
543 public function addTagAttributes($icon, $attr)
544 {
545 return preg_replace('/ ?\\/?>$/', '', $icon) . ' ' . $attr . ' />';
546 }
547
548 /**
549 * Adds a red "+" to the input string, $str, if the field "php_tree_stop" in the $row (pages) is set
550 *
551 * @param string $str Input string, like a page title for the tree
552 * @param array $row record row with "php_tree_stop" field
553 * @return string Modified string
554 * @internal
555 */
556 public function wrapStop($str, $row)
557 {
558 if ($row['php_tree_stop']) {
559 $str .= '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(['setTempDBmount' => $row['uid']])) . '" class="text-danger">+</a> ';
560 }
561 return $str;
562 }
563
564 /*******************************************
565 *
566 * tree handling
567 *
568 *******************************************/
569 /**
570 * Returns TRUE/FALSE if the next level for $id should be expanded - based on
571 * data in $this->stored[][] and ->expandAll flag.
572 * Extending parent function
573 *
574 * @param int $id Record id/key
575 * @return bool
576 * @internal
577 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::expandNext()
578 */
579 public function expandNext($id)
580 {
581 return !empty($this->stored[$this->bank][$id]) || $this->expandAll;
582 }
583
584 /**
585 * Get stored tree structure AND updating it if needed according to incoming PM GET var.
586 *
587 * @internal
588 */
589 public function initializePositionSaving()
590 {
591 // Get stored tree structure:
592 $this->stored = json_decode($this->BE_USER->uc['browseTrees'][$this->treeName], true);
593 // PM action
594 // (If a 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 * @internal
617 */
618 public function savePosition()
619 {
620 $this->BE_USER->uc['browseTrees'][$this->treeName] = json_encode($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 {
637 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
638 return $this->wrapIcon($iconFactory->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render(), $rec);
639 }
640
641 /**
642 * Get icon for the row.
643 *
644 * @param array|int $row Item row or uid
645 * @return string Image tag.
646 */
647 public function getIcon($row)
648 {
649 if (is_int($row)) {
650 $row = BackendUtility::getRecord($this->table, $row);
651 }
652 $title = $this->showDefaultTitleAttribute ? htmlspecialchars('UID: ' . $row['uid']) : $this->getTitleAttrib($row);
653 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
654 $icon = $row['is_siteroot'] ? $iconFactory->getIcon('apps-pagetree-folder-root', Icon::SIZE_SMALL) : $iconFactory->getIconForRecord($this->table, $row, Icon::SIZE_SMALL);
655 $icon = '<span title="' . $title . '">' . $icon->render() . '</span>';
656 return $this->wrapIcon($icon, $row);
657 }
658
659 /**
660 * Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
661 * Do NOT htmlspecialchar the string from this function - has already been done.
662 *
663 * @param array $row The input row array (where the key "title" is used for the title)
664 * @param int $titleLen Title length (30)
665 * @return string The title.
666 */
667 public function getTitleStr($row, $titleLen = 30)
668 {
669 $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
670 $title = trim($row['title']) === '' ? '<em>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>' : $title;
671 return $title;
672 }
673
674 /**
675 * Returns the value for the image "title" attribute
676 *
677 * @param array $row The input row array (where the key "title" is used for the title)
678 * @return string The attribute value (is htmlspecialchared() already)
679 * @see wrapIcon()
680 */
681 public function getTitleAttrib($row)
682 {
683 return htmlspecialchars($row['title']);
684 }
685
686 /**
687 * Returns the id from the record (typ. uid)
688 *
689 * @param array $row Record array
690 * @return int The "uid" field value.
691 */
692 public function getId($row)
693 {
694 return $row['uid'];
695 }
696
697 /**
698 * Returns jump-url parameter value.
699 *
700 * @param array $row The record array.
701 * @return string The jump-url parameter.
702 */
703 public function getJumpToParam($row)
704 {
705 return $this->getId($row);
706 }
707
708 /********************************
709 *
710 * tree data building
711 *
712 ********************************/
713 /**
714 * Fetches the data for the tree
715 *
716 * @param int $uid item id for which to select subitems (parent id)
717 * @param int $depth Max depth (recursivity limit)
718 * @param string $depthData HTML-code prefix for recursive calls.
719 * @return int The count of items on the level
720 */
721 public function getTree($uid, $depth = 999, $depthData = '')
722 {
723 // Buffer for id hierarchy is reset:
724 $this->buffer_idH = [];
725 // Init vars
726 $depth = (int)$depth;
727 $HTML = '';
728 $a = 0;
729 $res = $this->getDataInit($uid);
730 $c = $this->getDataCount($res);
731 $crazyRecursionLimiter = 999;
732 $idH = [];
733 // Traverse the records:
734 while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res))) {
735 $pageUid = ($this->table === 'pages') ? $row['uid'] : $row['pid'];
736 if (!$this->getBackendUser()->isInWebMount($pageUid)) {
737 // Current record is not within web mount => skip it
738 continue;
739 }
740
741 $a++;
742 $crazyRecursionLimiter--;
743 $newID = $row['uid'];
744 if ($newID == 0) {
745 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);
746 }
747 // Reserve space.
748 $this->tree[] = [];
749 end($this->tree);
750 // Get the key for this space
751 $treeKey = key($this->tree);
752 // If records should be accumulated, do so
753 if ($this->setRecs) {
754 $this->recs[$row['uid']] = $row;
755 }
756 // Accumulate the id of the element in the internal arrays
757 $this->ids[] = ($idH[$row['uid']]['uid'] = $row['uid']);
758 $this->ids_hierarchy[$depth][] = $row['uid'];
759 $this->orig_ids_hierarchy[$depth][] = $row['_ORIG_uid'] ?: $row['uid'];
760
761 // Make a recursive call to the next level
762 $nextLevelDepthData = $depthData . '<span class="treeline-icon treeline-icon-' . ($a === $c ? 'clear' : 'line') . '"></span>';
763 $hasSub = $this->expandNext($newID) && !$row['php_tree_stop'];
764 if ($depth > 1 && $hasSub) {
765 $nextCount = $this->getTree($newID, $depth - 1, $nextLevelDepthData);
766 if (!empty($this->buffer_idH)) {
767 $idH[$row['uid']]['subrow'] = $this->buffer_idH;
768 }
769 // Set "did expand" flag
770 $isOpen = 1;
771 } else {
772 $nextCount = $this->getCount($newID);
773 // Clear "did expand" flag
774 $isOpen = 0;
775 }
776 // Set HTML-icons, if any:
777 if ($this->makeHTML) {
778 $HTML = $this->PMicon($row, $a, $c, $nextCount, $isOpen) . $this->wrapStop($this->getIcon($row), $row);
779 }
780 // Finally, add the row/HTML content to the ->tree array in the reserved key.
781 $this->tree[$treeKey] = [
782 'row' => $row,
783 'HTML' => $HTML,
784 'invertedDepth' => $depth,
785 'depthData' => $depthData,
786 'bank' => $this->bank,
787 'hasSub' => $nextCount && $hasSub,
788 'isFirst' => $a === 1,
789 'isLast' => $a === $c,
790 ];
791 }
792
793 $this->getDataFree($res);
794 $this->buffer_idH = $idH;
795 return $c;
796 }
797
798 /********************************
799 *
800 * Data handling
801 * Works with records and arrays
802 *
803 ********************************/
804 /**
805 * Returns the number of records having the parent id, $uid
806 *
807 * @param int $uid Id to count subitems for
808 * @return int
809 * @internal
810 */
811 public function getCount($uid)
812 {
813 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
814 $queryBuilder->getRestrictions()
815 ->removeAll()
816 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
817 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
818 $count = $queryBuilder
819 ->count('uid')
820 ->from($this->table)
821 ->where(
822 $queryBuilder->expr()->eq(
823 $this->parentField,
824 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
825 ),
826 QueryHelper::stripLogicalOperatorPrefix($this->clause)
827 )
828 ->execute()
829 ->fetchColumn();
830
831 return (int)$count;
832 }
833
834 /**
835 * Returns root record for uid (<=0)
836 *
837 * @return array Array with title/uid keys with values of $this->title/0 (zero)
838 */
839 public function getRootRecord()
840 {
841 return ['title' => $this->title, 'uid' => 0];
842 }
843
844 /**
845 * Returns the record for a uid.
846 * For tables: Looks up the record in the database.
847 * For arrays: Returns the fake record for uid id.
848 *
849 * @param int $uid UID to look up
850 * @return array The record
851 */
852 public function getRecord($uid)
853 {
854 return BackendUtility::getRecordWSOL($this->table, $uid);
855 }
856
857 /**
858 * Getting the tree data: Selecting/Initializing data pointer to items for a certain parent id.
859 * For tables: This will make a database query to select all children to "parent"
860 * For arrays: This will return key to the ->dataLookup array
861 *
862 * @param int $parentId parent item id
863 *
864 * @return mixed Data handle (Tables: An sql-resource, arrays: A parentId integer. -1 is returned if there were NO subLevel.)
865 * @internal
866 */
867 public function getDataInit($parentId)
868 {
869 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
870 $queryBuilder->getRestrictions()
871 ->removeAll()
872 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
873 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
874 $queryBuilder
875 ->select(...$this->fieldArray)
876 ->from($this->table)
877 ->where(
878 $queryBuilder->expr()->eq(
879 $this->parentField,
880 $queryBuilder->createNamedParameter($parentId, \PDO::PARAM_INT)
881 ),
882 QueryHelper::stripLogicalOperatorPrefix($this->clause)
883 );
884
885 foreach (QueryHelper::parseOrderBy($this->orderByFields) as $orderPair) {
886 list($fieldName, $order) = $orderPair;
887 $queryBuilder->addOrderBy($fieldName, $order);
888 }
889
890 return $queryBuilder->execute();
891 }
892
893 /**
894 * Getting the tree data: Counting elements in resource
895 *
896 * @param mixed $res Data handle
897 * @return int number of items
898 * @internal
899 * @see getDataInit()
900 */
901 public function getDataCount(&$res)
902 {
903 return $res->rowCount();
904 }
905
906 /**
907 * Getting the tree data: next entry
908 *
909 * @param mixed $res Data handle
910 *
911 * @return array|bool item data array OR FALSE if end of elements.
912 * @internal
913 * @see getDataInit()
914 */
915 public function getDataNext(&$res)
916 {
917 while ($row = $res->fetch()) {
918 BackendUtility::workspaceOL($this->table, $row, $this->BE_USER->workspace, true);
919 if (is_array($row)) {
920 break;
921 }
922 }
923 return $row;
924 }
925
926 /**
927 * Getting the tree data: frees data handle
928 *
929 * @param mixed $res Data handle
930 * @internal
931 */
932 public function getDataFree(&$res)
933 {
934 $res->closeCursor();
935 }
936
937 /**
938 * Returns the mount point path for a temporary mount or the given id
939 *
940 * @param int $uid
941 * @return string
942 */
943 protected function getMountPointPath(int $uid): string
944 {
945 if ($uid <= 0) {
946 return '';
947 }
948 $rootline = array_reverse(BackendUtility::BEgetRootLine($uid));
949 array_shift($rootline);
950 $path = [];
951 foreach ($rootline as $rootlineElement) {
952 $record = BackendUtility::getRecordWSOL('pages', $rootlineElement['uid'], 'title, nav_title', '', true, true);
953 $text = $record['title'];
954 if ((bool)($this->getBackendUser()->getTSConfig()['options.']['pageTree.']['showNavTitle'] ?? false)
955 && trim($record['nav_title'] ?? '') !== ''
956 ) {
957 $text = $record['nav_title'];
958 }
959 $path[] = htmlspecialchars($text);
960 }
961 return '/' . implode('/', $path);
962 }
963
964 /**
965 * @return LanguageService
966 */
967 protected function getLanguageService()
968 {
969 return $GLOBALS['LANG'];
970 }
971
972 /**
973 * @return BackendUserAuthentication
974 */
975 protected function getBackendUser()
976 {
977 return $GLOBALS['BE_USER'];
978 }
979 }