[BUGFIX] Remove HTML from exception messages
[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', '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 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
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 int $a The current entry number
491 * @param int $c The total number of entries. If equal to $a, a "bottom" element is returned.
492 * @param int $nextCount The number of sub-elements to the current element.
493 * @param bool $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 bool $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 int $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 int $id Record id/key
595 * @return bool
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 int $titleLen Title length (30)
683 * @return string The title.
684 */
685 public function getTitleStr($row, $titleLen = 30) {
686 $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
687 $title = trim($row['title']) === '' ? '<em>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', TRUE) . ']</em>' : $title;
688 return $title;
689 }
690
691 /**
692 * Returns the value for the image "title" attribute
693 *
694 * @param array $row The input row array (where the key "title" is used for the title)
695 * @return string The attribute value (is htmlspecialchared() already)
696 * @see wrapIcon()
697 */
698 public function getTitleAttrib($row) {
699 return htmlspecialchars($row['title']);
700 }
701
702 /**
703 * Returns the id from the record (typ. uid)
704 *
705 * @param array $row Record array
706 * @return int The "uid" field value.
707 */
708 public function getId($row) {
709 return $row['uid'];
710 }
711
712 /**
713 * Returns jump-url parameter value.
714 *
715 * @param array $row The record array.
716 * @return string The jump-url parameter.
717 */
718 public function getJumpToParam($row) {
719 return $this->getId($row);
720 }
721
722 /********************************
723 *
724 * tree data buidling
725 *
726 ********************************/
727 /**
728 * Fetches the data for the tree
729 *
730 * @param int $uid item id for which to select subitems (parent id)
731 * @param int $depth Max depth (recursivity limit)
732 * @param string $depthData HTML-code prefix for recursive calls.
733 * @param string $blankLineCode ? (internal)
734 * @param string $subCSSclass CSS class to use for <td> sub-elements
735 * @return int The count of items on the level
736 */
737 public function getTree($uid, $depth = 999, $depthData = '', $blankLineCode = '', $subCSSclass = '') {
738 // Buffer for id hierarchy is reset:
739 $this->buffer_idH = array();
740 // Init vars
741 $depth = (int)$depth;
742 $HTML = '';
743 $a = 0;
744 $res = $this->getDataInit($uid, $subCSSclass);
745 $c = $this->getDataCount($res);
746 $crazyRecursionLimiter = 999;
747 $idH = array();
748 // Traverse the records:
749 while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res, $subCSSclass))) {
750 if (!$GLOBALS['BE_USER']->isInWebMount($row['uid'])) {
751 // Current record is not within web mount => skip it
752 continue;
753 }
754
755 $a++;
756 $crazyRecursionLimiter--;
757 $newID = $row['uid'];
758 if ($newID == 0) {
759 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);
760 }
761 // Reserve space.
762 $this->tree[] = array();
763 end($this->tree);
764 // Get the key for this space
765 $treeKey = key($this->tree);
766 $LN = $a == $c ? 'blank' : 'line';
767 // If records should be accumulated, do so
768 if ($this->setRecs) {
769 $this->recs[$row['uid']] = $row;
770 }
771 // Accumulate the id of the element in the internal arrays
772 $this->ids[] = ($idH[$row['uid']]['uid'] = $row['uid']);
773 $this->ids_hierarchy[$depth][] = $row['uid'];
774 $this->orig_ids_hierarchy[$depth][] = $row['_ORIG_uid'] ?: $row['uid'];
775
776 // Make a recursive call to the next level
777 $HTML_depthData = $depthData . IconUtility::getSpriteIcon('treeline-' . $LN);
778 if ($depth > 1 && $this->expandNext($newID) && !$row['php_tree_stop']) {
779 $nextCount = $this->getTree($newID, $depth - 1, $this->makeHTML ? $HTML_depthData : '', $blankLineCode . ',' . $LN, $row['_SUBCSSCLASS']);
780 if (count($this->buffer_idH)) {
781 $idH[$row['uid']]['subrow'] = $this->buffer_idH;
782 }
783 // Set "did expand" flag
784 $exp = 1;
785 } else {
786 $nextCount = $this->getCount($newID);
787 // Clear "did expand" flag
788 $exp = 0;
789 }
790 // Set HTML-icons, if any:
791 if ($this->makeHTML) {
792 $HTML = $depthData . $this->PMicon($row, $a, $c, $nextCount, $exp);
793 $HTML .= $this->wrapStop($this->getIcon($row), $row);
794 }
795 // Finally, add the row/HTML content to the ->tree array in the reserved key.
796 $this->tree[$treeKey] = array(
797 'row' => $row,
798 'HTML' => $HTML,
799 'HTML_depthData' => $this->makeHTML == 2 ? $HTML_depthData : '',
800 'invertedDepth' => $depth,
801 'blankLineCode' => $blankLineCode,
802 'bank' => $this->bank
803 );
804 }
805 $this->getDataFree($res);
806 $this->buffer_idH = $idH;
807 return $c;
808 }
809
810 /********************************
811 *
812 * Data handling
813 * Works with records and arrays
814 *
815 ********************************/
816 /**
817 * Returns the number of records having the parent id, $uid
818 *
819 * @param int $uid Id to count subitems for
820 * @return int
821 * @access private
822 */
823 public function getCount($uid) {
824 if (is_array($this->data)) {
825 $res = $this->getDataInit($uid);
826 return $this->getDataCount($res);
827 } else {
828 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);
829 }
830 }
831
832 /**
833 * Returns root record for uid (<=0)
834 *
835 * @param int $uid uid, <= 0 (normally, this does not matter)
836 * @return array Array with title/uid keys with values of $this->title/0 (zero)
837 */
838 public function getRootRecord($uid) {
839 return array('title' => $this->title, 'uid' => 0);
840 }
841
842 /**
843 * Returns the record for a uid.
844 * For tables: Looks up the record in the database.
845 * For arrays: Returns the fake record for uid id.
846 *
847 * @param int $uid UID to look up
848 * @return array The record
849 */
850 public function getRecord($uid) {
851 if (is_array($this->data)) {
852 return $this->dataLookup[$uid];
853 } else {
854 return BackendUtility::getRecordWSOL($this->table, $uid);
855 }
856 }
857
858 /**
859 * Getting the tree data: Selecting/Initializing data pointer to items for a certain parent id.
860 * For tables: This will make a database query to select all children to "parent"
861 * For arrays: This will return key to the ->dataLookup array
862 *
863 * @param int $parentId parent item id
864 * @param string $subCSSclass Class for sub-elements.
865 * @return mixed Data handle (Tables: An sql-resource, arrays: A parentId integer. -1 is returned if there were NO subLevel.)
866 * @access private
867 */
868 public function getDataInit($parentId, $subCSSclass = '') {
869 if (is_array($this->data)) {
870 if (!is_array($this->dataLookup[$parentId][$this->subLevelID])) {
871 $parentId = -1;
872 } else {
873 reset($this->dataLookup[$parentId][$this->subLevelID]);
874 }
875 return $parentId;
876 } else {
877 $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);
878 return $res;
879 }
880 }
881
882 /**
883 * Getting the tree data: Counting elements in resource
884 *
885 * @param mixed $res Data handle
886 * @return int number of items
887 * @access private
888 * @see getDataInit()
889 */
890 public function getDataCount(&$res) {
891 if (is_array($this->data)) {
892 return count($this->dataLookup[$res][$this->subLevelID]);
893 } else {
894 $c = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
895 return $c;
896 }
897 }
898
899 /**
900 * Getting the tree data: next entry
901 *
902 * @param mixed $res Data handle
903 * @param string $subCSSclass CSS class for sub elements (workspace related)
904 * @return array item data array OR FALSE if end of elements.
905 * @access private
906 * @see getDataInit()
907 */
908 public function getDataNext(&$res, $subCSSclass = '') {
909 if (is_array($this->data)) {
910 if ($res < 0) {
911 $row = FALSE;
912 } else {
913 list(, $row) = each($this->dataLookup[$res][$this->subLevelID]);
914 // Passing on default <td> class for subelements:
915 if (is_array($row) && $subCSSclass !== '') {
916 $row['_CSSCLASS'] = ($row['_SUBCSSCLASS'] = $subCSSclass);
917 }
918 }
919 return $row;
920 } else {
921 while ($row = @$GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
922 BackendUtility::workspaceOL($this->table, $row, $this->BE_USER->workspace, TRUE);
923 if (is_array($row)) {
924 break;
925 }
926 }
927 // Passing on default <td> class for subelements:
928 if (is_array($row) && $subCSSclass !== '') {
929 if ($this->table === 'pages' && $this->highlightPagesWithVersions && !isset($row['_CSSCLASS']) && count(BackendUtility::countVersionsOfRecordsOnPage($this->BE_USER->workspace, $row['uid']))) {
930 $row['_CSSCLASS'] = 'ver-versions';
931 }
932 if (!isset($row['_CSSCLASS'])) {
933 $row['_CSSCLASS'] = $subCSSclass;
934 }
935 if (!isset($row['_SUBCSSCLASS'])) {
936 $row['_SUBCSSCLASS'] = $subCSSclass;
937 }
938 }
939 return $row;
940 }
941 }
942
943 /**
944 * Getting the tree data: frees data handle
945 *
946 * @param mixed $res Data handle
947 * @return void
948 * @access private
949 */
950 public function getDataFree(&$res) {
951 if (!is_array($this->data)) {
952 $GLOBALS['TYPO3_DB']->sql_free_result($res);
953 }
954 }
955
956 /**
957 * Used to initialize class with an array to browse.
958 * The array inputted will be traversed and an internal index for lookup is created.
959 * The keys of the input array are perceived as "uid"s of records which means that keys GLOBALLY must be unique like uids are.
960 * "uid" and "pid" "fakefields" are also set in each record.
961 * All other fields are optional.
962 *
963 * @param array $dataArr The input array, see examples below in this script.
964 * @param bool $traverse Internal, for recursion.
965 * @param int $pid Internal, for recursion.
966 * @return void
967 */
968 public function setDataFromArray(&$dataArr, $traverse = FALSE, $pid = 0) {
969 if (!$traverse) {
970 $this->data = &$dataArr;
971 $this->dataLookup = array();
972 // Add root
973 $this->dataLookup[0][$this->subLevelID] = &$dataArr;
974 }
975 foreach ($dataArr as $uid => $val) {
976 $dataArr[$uid]['uid'] = $uid;
977 $dataArr[$uid]['pid'] = $pid;
978 // Gives quick access to id's
979 $this->dataLookup[$uid] = &$dataArr[$uid];
980 if (is_array($val[$this->subLevelID])) {
981 $this->setDataFromArray($dataArr[$uid][$this->subLevelID], TRUE, $uid);
982 }
983 }
984 }
985
986 /**
987 * Sets the internal data arrays
988 *
989 * @param array $treeArr Content for $this->data
990 * @param array $treeLookupArr Content for $this->dataLookup
991 * @return void
992 */
993 public function setDataFromTreeArray(&$treeArr, &$treeLookupArr) {
994 $this->data = &$treeArr;
995 $this->dataLookup = &$treeLookupArr;
996 }
997
998 }