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