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