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