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