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