[TASK] Remove the "@todo define visibility" in ext:backend
[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['WEBMOUNTS']
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', '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 * Icon file path.
188 *
189 * @var string
190 */
191 public $iconPath = '';
192
193 /**
194 * Icon file name for item icons.
195 *
196 * @var string
197 */
198 public $iconName = 'default.gif';
199
200 /**
201 * If TRUE, HTML code is also accumulated in ->tree array during rendering of the tree.
202 * If 2, then also the icon prefix code (depthData) is stored
203 *
204 * @var int
205 */
206 public $makeHTML = 1;
207
208 /**
209 * If TRUE, records as selected will be stored internally in the ->recs array
210 *
211 * @var int
212 */
213 public $setRecs = 0;
214
215 /**
216 * Sets the associative array key which identifies a new sublevel if arrays are used for trees.
217 * This value has formerly been "subLevel" and "--sublevel--"
218 *
219 * @var string
220 */
221 public $subLevelID = '_SUB_LEVEL';
222
223 // *********
224 // Internal
225 // *********
226 // For record trees:
227 // one-dim array of the uid's selected.
228 /**
229 * @var array
230 */
231 public $ids = array();
232
233 // The hierarchy of element uids
234 /**
235 * @var array
236 */
237 public $ids_hierarchy = array();
238
239 // The hierarchy of versioned element uids
240 /**
241 * @var array
242 */
243 public $orig_ids_hierarchy = array();
244
245 // Temporary, internal array
246 /**
247 * @var array
248 */
249 public $buffer_idH = array();
250
251 // For FOLDER trees:
252 // Special UIDs for folders (integer-hashes of paths)
253 /**
254 * @var array
255 */
256 public $specUIDmap = array();
257
258 // For arrays:
259 // Holds the input data array
260 /**
261 * @var bool
262 */
263 public $data = FALSE;
264
265 // Holds an index with references to the data array.
266 /**
267 * @var bool
268 */
269 public $dataLookup = FALSE;
270
271 // For both types
272 // Tree is accumulated in this variable
273 /**
274 * @var array
275 */
276 public $tree = array();
277
278 // Holds (session stored) information about which items in the tree are unfolded and which are not.
279 /**
280 * @var array
281 */
282 public $stored = array();
283
284 // Points to the current mountpoint key
285 /**
286 * @var int
287 */
288 public $bank = 0;
289
290 // Accumulates the displayed records.
291 /**
292 * @var array
293 */
294 public $recs = array();
295
296 /**
297 * Sets the script url depending on being a module or script request
298 */
299 protected function determineScriptUrl() {
300 if ($moduleName = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('M')) {
301 $this->thisScript = \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleUrl($moduleName);
302 } else {
303 $this->thisScript = \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('SCRIPT_NAME');
304 }
305 }
306
307 /**
308 * @return string
309 */
310 protected function getThisScript() {
311 return strpos($this->thisScript, '?') === FALSE ? $this->thisScript . '?' : $this->thisScript . '&';
312 }
313
314 /**
315 * Initialize the tree class. Needs to be overwritten
316 * Will set ->fieldsArray, ->backPath and ->clause
317 *
318 * @param string Record WHERE clause
319 * @param string Record ORDER BY field
320 * @return void
321 */
322 public function init($clause = '', $orderByFields = '') {
323 // Setting BE_USER by default
324 $this->BE_USER = $GLOBALS['BE_USER'];
325 // Setting title attribute to use.
326 $this->titleAttrib = 'title';
327 // Setting backpath.
328 $this->backPath = $GLOBALS['BACK_PATH'];
329 // Setting clause
330 if ($clause) {
331 $this->clause = $clause;
332 }
333 if ($orderByFields) {
334 $this->orderByFields = $orderByFields;
335 }
336 if (!is_array($this->MOUNTS)) {
337 // Dummy
338 $this->MOUNTS = array(0 => 0);
339 }
340 $this->setTreeName();
341 // Setting this to FALSE disables the use of array-trees by default
342 $this->data = FALSE;
343 $this->dataLookup = FALSE;
344 }
345
346 /**
347 * Sets the tree name which is used to identify the tree
348 * Used for JavaScript and other things
349 *
350 * @param string $treeName Default is the table name. Underscores are stripped.
351 * @return void
352 */
353 public function setTreeName($treeName = '') {
354 $this->treeName = $treeName ?: $this->treeName;
355 $this->treeName = $this->treeName ?: $this->table;
356 $this->treeName = str_replace('_', '', $this->treeName);
357 }
358
359 /**
360 * Adds a fieldname to the internal array ->fieldArray
361 *
362 * @param string $field Field name to
363 * @param boolean $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
364 * @return void
365 */
366 public function addField($field, $noCheck = 0) {
367 if ($noCheck || is_array($GLOBALS['TCA'][$this->table]['columns'][$field]) || GeneralUtility::inList($this->defaultList, $field)) {
368 $this->fieldArray[] = $field;
369 }
370 }
371
372 /**
373 * Resets the tree, recs, ids, ids_hierarchy and orig_ids_hierarchy internal variables. Use it if you need it.
374 *
375 * @return void
376 */
377 public function reset() {
378 $this->tree = array();
379 $this->recs = array();
380 $this->ids = array();
381 $this->ids_hierarchy = array();
382 $this->orig_ids_hierarchy = array();
383 }
384
385 /*******************************************
386 *
387 * output
388 *
389 *******************************************/
390 /**
391 * Will create and return the HTML code for a browsable tree
392 * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
393 *
394 * @return string HTML code for the browsable tree
395 */
396 public function getBrowsableTree() {
397 // Get stored tree structure AND updating it if needed according to incoming PM GET var.
398 $this->initializePositionSaving();
399 // Init done:
400 $treeArr = array();
401 // Traverse mounts:
402 foreach ($this->MOUNTS as $idx => $uid) {
403 // Set first:
404 $this->bank = $idx;
405 $isOpen = $this->stored[$idx][$uid] || $this->expandFirst;
406 // Save ids while resetting everything else.
407 $curIds = $this->ids;
408 $this->reset();
409 $this->ids = $curIds;
410 // Set PM icon for root of mount:
411 $cmd = $this->bank . '_' . ($isOpen ? '0_' : '1_') . $uid . '_' . $this->treeName;
412 $icon = IconUtility::getSpriteIcon('treeline-' . ($isOpen ? 'minus' : 'plus') . 'only');
413
414 $firstHtml = $this->PM_ATagWrap($icon, $cmd);
415 // Preparing rootRec for the mount
416 if ($uid) {
417 $rootRec = $this->getRecord($uid);
418 $firstHtml .= $this->getIcon($rootRec);
419 } else {
420 // Artificial record for the tree root, id=0
421 $rootRec = $this->getRootRecord($uid);
422 $firstHtml .= $this->getRootIcon($rootRec);
423 }
424 if (is_array($rootRec)) {
425 // In case it was swapped inside getRecord due to workspaces.
426 $uid = $rootRec['uid'];
427 // Add the root of the mount to ->tree
428 $this->tree[] = array('HTML' => $firstHtml, 'row' => $rootRec, 'bank' => $this->bank);
429 // If the mount is expanded, go down:
430 if ($isOpen) {
431 // Set depth:
432 $depthD = IconUtility::getSpriteIcon('treeline-blank');
433 if ($this->addSelfId) {
434 $this->ids[] = $uid;
435 }
436 $this->getTree($uid, 999, $depthD, '', $rootRec['_SUBCSSCLASS']);
437 }
438 // Add tree:
439 $treeArr = array_merge($treeArr, $this->tree);
440 }
441 }
442 return $this->printTree($treeArr);
443 }
444
445 /**
446 * Compiles the HTML code for displaying the structure found inside the ->tree array
447 *
448 * @param array $treeArr "tree-array" - if blank string, the internal ->tree array is used.
449 * @return string The HTML code for the tree
450 */
451 public function printTree($treeArr = '') {
452 $titleLen = (int)$this->BE_USER->uc['titleLen'];
453 if (!is_array($treeArr)) {
454 $treeArr = $this->tree;
455 }
456 $out = '';
457 // put a table around it with IDs to access the rows from JS
458 // not a problem if you don't need it
459 // In XHTML there is no "name" attribute of <td> elements -
460 // but Mozilla will not be able to highlight rows if the name
461 // attribute is NOT there.
462 $out .= '
463
464 <!--
465 TYPO3 tree structure.
466 -->
467 <table cellpadding="0" cellspacing="0" border="0" id="typo3-tree">';
468 foreach ($treeArr as $k => $v) {
469 $idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($v['row']) . '_' . $v['bank']);
470 $out .= '
471 <tr>
472 <td id="' . $idAttr . '"' . ($v['row']['_CSSCLASS'] ? ' class="' . $v['row']['_CSSCLASS'] . '"' : '') . '>' . $v['HTML'] . $this->wrapTitle($this->getTitleStr($v['row'], $titleLen), $v['row'], $v['bank']) . '</td>
473 </tr>
474 ';
475 }
476 $out .= '
477 </table>';
478 return $out;
479 }
480
481 /*******************************************
482 *
483 * rendering parts
484 *
485 *******************************************/
486 /**
487 * Generate the plus/minus icon for the browsable tree.
488 *
489 * @param array $row Record for the entry
490 * @param integer $a The current entry number
491 * @param integer $c The total number of entries. If equal to $a, a "bottom" element is returned.
492 * @param integer $nextCount The number of sub-elements to the current element.
493 * @param boolean $exp The element was expanded to render subelements if this flag is set.
494 * @return string Image tag with the plus/minus icon.
495 * @access private
496 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
497 */
498 public function PMicon($row, $a, $c, $nextCount, $exp) {
499 $PM = $nextCount ? ($exp ? 'minus' : 'plus') : 'join';
500 $BTM = $a == $c ? 'bottom' : '';
501 $icon = IconUtility::getSpriteIcon('treeline-' . $PM . $BTM);
502 if ($nextCount) {
503 $cmd = $this->bank . '_' . ($exp ? '0_' : '1_') . $row['uid'] . '_' . $this->treeName;
504 $bMark = $this->bank . '_' . $row['uid'];
505 $icon = $this->PM_ATagWrap($icon, $cmd, $bMark);
506 }
507 return $icon;
508 }
509
510 /**
511 * Wrap the plus/minus icon in a link
512 *
513 * @param string $icon HTML string to wrap, probably an image tag.
514 * @param string $cmd Command for 'PM' get var
515 * @param boolean $bMark If set, the link will have a anchor point (=$bMark) and a name attribute (=$bMark)
516 * @return string Link-wrapped input string
517 * @access private
518 */
519 public function PM_ATagWrap($icon, $cmd, $bMark = '') {
520 if ($this->thisScript) {
521 if ($bMark) {
522 $anchor = '#' . $bMark;
523 $name = ' name="' . $bMark . '"';
524 }
525 $aUrl = $this->getThisScript() . 'PM=' . $cmd . $anchor;
526 return '<a href="' . htmlspecialchars($aUrl) . '"' . $name . '>' . $icon . '</a>';
527 } else {
528 return $icon;
529 }
530 }
531
532 /**
533 * Wrapping $title in a-tags.
534 *
535 * @param string $title Title string
536 * @param string $row Item record
537 * @param integer $bank Bank pointer (which mount point number)
538 * @return string
539 * @access private
540 */
541 public function wrapTitle($title, $row, $bank = 0) {
542 $aOnClick = 'return jumpTo(\'' . $this->getJumpToParam($row) . '\',this,\'' . $this->domIdPrefix . $this->getId($row) . '\',' . $bank . ');';
543 return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $title . '</a>';
544 }
545
546 /**
547 * Wrapping the image tag, $icon, for the row, $row (except for mount points)
548 *
549 * @param string $icon The image tag for the icon
550 * @param array $row The row for the current element
551 * @return string The processed icon input value.
552 * @access private
553 */
554 public function wrapIcon($icon, $row) {
555 return $icon;
556 }
557
558 /**
559 * Adds attributes to image tag.
560 *
561 * @param string $icon Icon image tag
562 * @param string $attr Attributes to add, eg. ' border="0"'
563 * @return string Image tag, modified with $attr attributes added.
564 */
565 public function addTagAttributes($icon, $attr) {
566 return preg_replace('/ ?\\/?>$/', '', $icon) . ' ' . $attr . ' />';
567 }
568
569 /**
570 * Adds a red "+" to the input string, $str, if the field "php_tree_stop" in the $row (pages) is set
571 *
572 * @param string $str Input string, like a page title for the tree
573 * @param array $row record row with "php_tree_stop" field
574 * @return string Modified string
575 * @access private
576 */
577 public function wrapStop($str, $row) {
578 if ($row['php_tree_stop']) {
579 $str .= '<span class="typo3-red"><a href="' . htmlspecialchars(GeneralUtility::linkThisScript(array('setTempDBmount' => $row['uid']))) . '" class="typo3-red">+</a> </span>';
580 }
581 return $str;
582 }
583
584 /*******************************************
585 *
586 * tree handling
587 *
588 *******************************************/
589 /**
590 * Returns TRUE/FALSE if the next level for $id should be expanded - based on
591 * data in $this->stored[][] and ->expandAll flag.
592 * Extending parent function
593 *
594 * @param integer $id Record id/key
595 * @return boolean
596 * @access private
597 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::expandNext()
598 */
599 public function expandNext($id) {
600 return $this->stored[$this->bank][$id] || $this->expandAll ? 1 : 0;
601 }
602
603 /**
604 * Get stored tree structure AND updating it if needed according to incoming PM GET var.
605 *
606 * @return void
607 * @access private
608 */
609 public function initializePositionSaving() {
610 // Get stored tree structure:
611 $this->stored = unserialize($this->BE_USER->uc['browseTrees'][$this->treeName]);
612 // PM action
613 // (If an plus/minus icon has been clicked, the PM GET var is sent and we
614 // must update the stored positions in the tree):
615 // 0: mount key, 1: set/clear boolean, 2: item ID (cannot contain "_"), 3: treeName
616 $PM = explode('_', GeneralUtility::_GP('PM'));
617 if (count($PM) == 4 && $PM[3] == $this->treeName) {
618 if (isset($this->MOUNTS[$PM[0]])) {
619 // set
620 if ($PM[1]) {
621 $this->stored[$PM[0]][$PM[2]] = 1;
622 $this->savePosition();
623 } else {
624 unset($this->stored[$PM[0]][$PM[2]]);
625 $this->savePosition();
626 }
627 }
628 }
629 }
630
631 /**
632 * Saves the content of ->stored (keeps track of expanded positions in the tree)
633 * $this->treeName will be used as key for BE_USER->uc[] to store it in
634 *
635 * @return void
636 * @access private
637 */
638 public function savePosition() {
639 $this->BE_USER->uc['browseTrees'][$this->treeName] = serialize($this->stored);
640 $this->BE_USER->writeUC();
641 }
642
643 /******************************
644 *
645 * Functions that might be overwritten by extended classes
646 *
647 ********************************/
648 /**
649 * Returns the root icon for a tree/mountpoint (defaults to the globe)
650 *
651 * @param array $rec Record for root.
652 * @return string Icon image tag.
653 */
654 public function getRootIcon($rec) {
655 return $this->wrapIcon(IconUtility::getSpriteIcon('apps-pagetree-root'), $rec);
656 }
657
658 /**
659 * Get icon for the row.
660 * If $this->iconPath and $this->iconName is set, try to get icon based on those values.
661 *
662 * @param array $row Item row.
663 * @return string Image tag.
664 */
665 public function getIcon($row) {
666 if ($this->iconPath && $this->iconName) {
667 $icon = '<img' . IconUtility::skinImg('', ($this->iconPath . $this->iconName), 'width="18" height="16"') . ' alt=""' . ($this->showDefaultTitleAttribute ? ' title="UID: ' . $row['uid'] . '"' : '') . ' />';
668 } else {
669 $icon = IconUtility::getSpriteIconForRecord($this->table, $row, array(
670 'title' => $this->showDefaultTitleAttribute ? 'UID: ' . $row['uid'] : $this->getTitleAttrib($row),
671 'class' => 'c-recIcon'
672 ));
673 }
674 return $this->wrapIcon($icon, $row);
675 }
676
677 /**
678 * Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
679 * Do NOT htmlspecialchar the string from this function - has already been done.
680 *
681 * @param array $row The input row array (where the key "title" is used for the title)
682 * @param integer $titleLen Title length (30)
683 * @return string The title.
684 */
685 public function getTitleStr($row, $titleLen = 30) {
686 if ($this->ext_showNavTitle && strlen(trim($row['nav_title'])) > 0) {
687 $title = '<span title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tca.xlf:title', TRUE) . ' ' . htmlspecialchars(trim($row['title'])) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['nav_title'], $titleLen)) . '</span>';
688 } else {
689 $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
690 if (strlen(trim($row['nav_title'])) > 0) {
691 $title = '<span title="' . $GLOBALS['LANG']->sL('LLL:EXT:cms/locallang_tca.xlf:pages.nav_title', TRUE) . ' ' . htmlspecialchars(trim($row['nav_title'])) . '">' . $title . '</span>';
692 }
693 $title = strlen(trim($row['title'])) == 0 ? '<em>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', TRUE) . ']</em>' : $title;
694 }
695 return $title;
696 }
697
698 /**
699 * Returns the value for the image "title" attribute
700 *
701 * @param array $row The input row array (where the key "title" is used for the title)
702 * @return string The attribute value (is htmlspecialchared() already)
703 * @see wrapIcon()
704 */
705 public function getTitleAttrib($row) {
706 return htmlspecialchars($row['title']);
707 }
708
709 /**
710 * Returns the id from the record (typ. uid)
711 *
712 * @param array $row Record array
713 * @return integer The "uid" field value.
714 */
715 public function getId($row) {
716 return $row['uid'];
717 }
718
719 /**
720 * Returns jump-url parameter value.
721 *
722 * @param array $row The record array.
723 * @return string The jump-url parameter.
724 */
725 public function getJumpToParam($row) {
726 return $this->getId($row);
727 }
728
729 /********************************
730 *
731 * tree data buidling
732 *
733 ********************************/
734 /**
735 * Fetches the data for the tree
736 *
737 * @param integer $uid item id for which to select subitems (parent id)
738 * @param integer $depth Max depth (recursivity limit)
739 * @param string $depthData HTML-code prefix for recursive calls.
740 * @param string $blankLineCode ? (internal)
741 * @param string $subCSSclass CSS class to use for <td> sub-elements
742 * @return integer The count of items on the level
743 */
744 public function getTree($uid, $depth = 999, $depthData = '', $blankLineCode = '', $subCSSclass = '') {
745 // Buffer for id hierarchy is reset:
746 $this->buffer_idH = array();
747 // Init vars
748 $depth = (int)$depth;
749 $HTML = '';
750 $a = 0;
751 $res = $this->getDataInit($uid, $subCSSclass);
752 $c = $this->getDataCount($res);
753 $crazyRecursionLimiter = 999;
754 $idH = array();
755 // Traverse the records:
756 while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res, $subCSSclass))) {
757 if (!$GLOBALS['BE_USER']->isInWebMount($row['uid'])) {
758 // Current record is not within web mount => skip it
759 continue;
760 }
761
762 $a++;
763 $crazyRecursionLimiter--;
764 $newID = $row['uid'];
765 if ($newID == 0) {
766 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.<br /><br />See <a href="http://forge.typo3.org/issues/16150" target="_blank">forge.typo3.org/issues/16150</a> to get more information about a possible cause.', 1294586383);
767 }
768 // Reserve space.
769 $this->tree[] = array();
770 end($this->tree);
771 // Get the key for this space
772 $treeKey = key($this->tree);
773 $LN = $a == $c ? 'blank' : 'line';
774 // If records should be accumulated, do so
775 if ($this->setRecs) {
776 $this->recs[$row['uid']] = $row;
777 }
778 // Accumulate the id of the element in the internal arrays
779 $this->ids[] = ($idH[$row['uid']]['uid'] = $row['uid']);
780 $this->ids_hierarchy[$depth][] = $row['uid'];
781 $this->orig_ids_hierarchy[$depth][] = $row['_ORIG_uid'] ?: $row['uid'];
782
783 // Make a recursive call to the next level
784 $HTML_depthData = $depthData . IconUtility::getSpriteIcon('treeline-' . $LN);
785 if ($depth > 1 && $this->expandNext($newID) && !$row['php_tree_stop']) {
786 $nextCount = $this->getTree($newID, $depth - 1, $this->makeHTML ? $HTML_depthData : '', $blankLineCode . ',' . $LN, $row['_SUBCSSCLASS']);
787 if (count($this->buffer_idH)) {
788 $idH[$row['uid']]['subrow'] = $this->buffer_idH;
789 }
790 // Set "did expand" flag
791 $exp = 1;
792 } else {
793 $nextCount = $this->getCount($newID);
794 // Clear "did expand" flag
795 $exp = 0;
796 }
797 // Set HTML-icons, if any:
798 if ($this->makeHTML) {
799 $HTML = $depthData . $this->PMicon($row, $a, $c, $nextCount, $exp);
800 $HTML .= $this->wrapStop($this->getIcon($row), $row);
801 }
802 // Finally, add the row/HTML content to the ->tree array in the reserved key.
803 $this->tree[$treeKey] = array(
804 'row' => $row,
805 'HTML' => $HTML,
806 'HTML_depthData' => $this->makeHTML == 2 ? $HTML_depthData : '',
807 'invertedDepth' => $depth,
808 'blankLineCode' => $blankLineCode,
809 'bank' => $this->bank
810 );
811 }
812 $this->getDataFree($res);
813 $this->buffer_idH = $idH;
814 return $c;
815 }
816
817 /********************************
818 *
819 * Data handling
820 * Works with records and arrays
821 *
822 ********************************/
823 /**
824 * Returns the number of records having the parent id, $uid
825 *
826 * @param integer $uid Id to count subitems for
827 * @return integer
828 * @access private
829 */
830 public function getCount($uid) {
831 if (is_array($this->data)) {
832 $res = $this->getDataInit($uid);
833 return $this->getDataCount($res);
834 } else {
835 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);
836 }
837 }
838
839 /**
840 * Returns root record for uid (<=0)
841 *
842 * @param integer $uid uid, <= 0 (normally, this does not matter)
843 * @return array Array with title/uid keys with values of $this->title/0 (zero)
844 */
845 public function getRootRecord($uid) {
846 return array('title' => $this->title, 'uid' => 0);
847 }
848
849 /**
850 * Returns the record for a uid.
851 * For tables: Looks up the record in the database.
852 * For arrays: Returns the fake record for uid id.
853 *
854 * @param integer $uid UID to look up
855 * @return array The record
856 */
857 public function getRecord($uid) {
858 if (is_array($this->data)) {
859 return $this->dataLookup[$uid];
860 } else {
861 return BackendUtility::getRecordWSOL($this->table, $uid);
862 }
863 }
864
865 /**
866 * Getting the tree data: Selecting/Initializing data pointer to items for a certain parent id.
867 * For tables: This will make a database query to select all children to "parent"
868 * For arrays: This will return key to the ->dataLookup array
869 *
870 * @param integer $parentId parent item id
871 * @param string $subCSSclass Class for sub-elements.
872 * @return mixed Data handle (Tables: An sql-resource, arrays: A parentId integer. -1 is returned if there were NO subLevel.)
873 * @access private
874 */
875 public function getDataInit($parentId, $subCSSclass = '') {
876 if (is_array($this->data)) {
877 if (!is_array($this->dataLookup[$parentId][$this->subLevelID])) {
878 $parentId = -1;
879 } else {
880 reset($this->dataLookup[$parentId][$this->subLevelID]);
881 }
882 return $parentId;
883 } else {
884 $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);
885 return $res;
886 }
887 }
888
889 /**
890 * Getting the tree data: Counting elements in resource
891 *
892 * @param mixed $res Data handle
893 * @return integer number of items
894 * @access private
895 * @see getDataInit()
896 */
897 public function getDataCount(&$res) {
898 if (is_array($this->data)) {
899 return count($this->dataLookup[$res][$this->subLevelID]);
900 } else {
901 $c = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
902 return $c;
903 }
904 }
905
906 /**
907 * Getting the tree data: next entry
908 *
909 * @param mixed $res Data handle
910 * @param string $subCSSclass CSS class for sub elements (workspace related)
911 * @return array item data array OR FALSE if end of elements.
912 * @access private
913 * @see getDataInit()
914 */
915 public function getDataNext(&$res, $subCSSclass = '') {
916 if (is_array($this->data)) {
917 if ($res < 0) {
918 $row = FALSE;
919 } else {
920 list(, $row) = each($this->dataLookup[$res][$this->subLevelID]);
921 // Passing on default <td> class for subelements:
922 if (is_array($row) && $subCSSclass !== '') {
923 $row['_CSSCLASS'] = ($row['_SUBCSSCLASS'] = $subCSSclass);
924 }
925 }
926 return $row;
927 } else {
928 while ($row = @$GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
929 BackendUtility::workspaceOL($this->table, $row, $this->BE_USER->workspace, TRUE);
930 if (is_array($row)) {
931 break;
932 }
933 }
934 // Passing on default <td> class for subelements:
935 if (is_array($row) && $subCSSclass !== '') {
936 if ($this->table === 'pages' && $this->highlightPagesWithVersions && !isset($row['_CSSCLASS']) && count(BackendUtility::countVersionsOfRecordsOnPage($this->BE_USER->workspace, $row['uid']))) {
937 $row['_CSSCLASS'] = 'ver-versions';
938 }
939 if (!isset($row['_CSSCLASS'])) {
940 $row['_CSSCLASS'] = $subCSSclass;
941 }
942 if (!isset($row['_SUBCSSCLASS'])) {
943 $row['_SUBCSSCLASS'] = $subCSSclass;
944 }
945 }
946 return $row;
947 }
948 }
949
950 /**
951 * Getting the tree data: frees data handle
952 *
953 * @param mixed $res Data handle
954 * @return void
955 * @access private
956 */
957 public function getDataFree(&$res) {
958 if (!is_array($this->data)) {
959 $GLOBALS['TYPO3_DB']->sql_free_result($res);
960 }
961 }
962
963 /**
964 * Used to initialize class with an array to browse.
965 * The array inputted will be traversed and an internal index for lookup is created.
966 * The keys of the input array are perceived as "uid"s of records which means that keys GLOBALLY must be unique like uids are.
967 * "uid" and "pid" "fakefields" are also set in each record.
968 * All other fields are optional.
969 *
970 * @param array $dataArr The input array, see examples below in this script.
971 * @param boolean $traverse Internal, for recursion.
972 * @param integer $pid Internal, for recursion.
973 * @return void
974 */
975 public function setDataFromArray(&$dataArr, $traverse = FALSE, $pid = 0) {
976 if (!$traverse) {
977 $this->data = &$dataArr;
978 $this->dataLookup = array();
979 // Add root
980 $this->dataLookup[0][$this->subLevelID] = &$dataArr;
981 }
982 foreach ($dataArr as $uid => $val) {
983 $dataArr[$uid]['uid'] = $uid;
984 $dataArr[$uid]['pid'] = $pid;
985 // Gives quick access to id's
986 $this->dataLookup[$uid] = &$dataArr[$uid];
987 if (is_array($val[$this->subLevelID])) {
988 $this->setDataFromArray($dataArr[$uid][$this->subLevelID], TRUE, $uid);
989 }
990 }
991 }
992
993 /**
994 * Sets the internal data arrays
995 *
996 * @param array $treeArr Content for $this->data
997 * @param array $treeLookupArr Content for $this->dataLookup
998 * @return void
999 */
1000 public function setDataFromTreeArray(&$treeArr, &$treeLookupArr) {
1001 $this->data = &$treeArr;
1002 $this->dataLookup = &$treeLookupArr;
1003 }
1004
1005 }