[TASK] Remove the function index
[Packages/TYPO3.CMS.git] / typo3 / class.webpagetree.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * Extension class for the t3lib_browsetree class, specially made
30 * for browsing pages in the Web module
31 *
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
33 * @author Benjamin Mack <bmack@xnos.org>
34 * @package TYPO3
35 * @subpackage core
36 * @see class t3lib_browseTree
37 */
38 class webPageTree extends t3lib_browseTree {
39
40 var $ext_showPageId;
41 var $ext_IconMode;
42 var $ext_separateNotinmenuPages;
43 var $ext_alphasortNotinmenuPages;
44 // Indicates, whether the ajax call was successful, i.e. the requested page has been found
45 var $ajaxStatus = FALSE;
46
47 /**
48 * Calls init functions
49 */
50 function __construct() {
51 $this->init();
52 }
53
54 /**
55 * Wrapping icon in browse tree
56 *
57 * @param string $thePageIcon Icon IMG code
58 * @param array $row Data row for element.
59 * @return string Page icon
60 */
61 function wrapIcon($thePageIcon, &$row) {
62 // If the record is locked, present a warning sign.
63 if ($lockInfo=t3lib_BEfunc::isRecordLocked('pages', $row['uid'])) {
64 $aOnClick = 'alert('.$GLOBALS['LANG']->JScharCode($lockInfo['msg']).');return false;';
65 $lockIcon='<a href="#" onclick="'.htmlspecialchars($aOnClick).'">'.
66 t3lib_iconWorks::getSpriteIcon('status-warning-in-use', array('title'=>htmlspecialchars($lockInfo['msg']))).
67 '</a>';
68 } else {
69 $lockIcon = '';
70 }
71
72 // Wrap icon in click-menu link.
73 if (!$this->ext_IconMode) {
74 $thePageIcon = $GLOBALS['TBE_TEMPLATE']->wrapClickMenuOnIcon($thePageIcon, 'pages', $row['uid'], 0, '&bank='.$this->bank);
75 } elseif (!strcmp($this->ext_IconMode, 'titlelink')) {
76 $aOnClick = 'return jumpTo(\''.$this->getJumpToParam($row).'\',this,\''.$this->treeName.'\');';
77 $thePageIcon='<a href="#" onclick="'.htmlspecialchars($aOnClick).'">'.$thePageIcon.'</a>';
78 }
79
80 // Wrap icon in a drag/drop span.
81 $dragDropIcon = '<span class="dragIcon" id="dragIconID_'.$row['uid'].'">'.$thePageIcon.'</span>';
82
83 // Add Page ID:
84 $pageIdStr = '';
85 if ($this->ext_showPageId) {
86 $pageIdStr = '<span class="dragId">[' . $row['uid'] . ']</span> ';
87 }
88
89 // Call stats information hook
90 $stat = '';
91 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'])) {
92 $_params = array('pages', $row['uid']);
93 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] as $_funcRef) {
94 $stat.=t3lib_div::callUserFunction($_funcRef, $_params, $this);
95 }
96 }
97
98 return $dragDropIcon.$lockIcon.$pageIdStr.$stat;
99 }
100
101 /**
102 * Adds a red "+" to the input string, $str, if the field "php_tree_stop" in the $row (pages) is set
103 *
104 * @param string $str Input string, like a page title for the tree
105 * @param array $row Record row with "php_tree_stop" field
106 * @return string Modified string
107 * @access private
108 */
109 function wrapStop($str, $row) {
110 if ($row['php_tree_stop']) {
111 $str .= '<a href="'.htmlspecialchars(t3lib_div::linkThisScript(array('setTempDBmount' => $row['uid']))).'" class="typo3-red">+</a> ';
112 }
113 return $str;
114 }
115
116 /**
117 * Wrapping $title in a-tags.
118 *
119 * @param string $title Title string
120 * @param string $row Item record
121 * @param integer $bank Bank pointer (which mount point number)
122 * @return string
123 * @access private
124 */
125 function wrapTitle($title, $row, $bank = 0) {
126 // Hook for overriding the page title
127 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.webpagetree.php']['pageTitleOverlay'])) {
128 $_params = array('title' => &$title, 'row' => &$row);
129 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.webpagetree.php']['pageTitleOverlay'] as $_funcRef) {
130 t3lib_div::callUserFunction($_funcRef, $_params, $this);
131 }
132 unset($_params);
133 }
134
135 $aOnClick = 'return jumpTo(\''.$this->getJumpToParam($row).'\',this,\''.$this->domIdPrefix.$this->getId($row).'\','.$bank.');';
136 $CSM = ' oncontextmenu="'.htmlspecialchars($GLOBALS['TBE_TEMPLATE']->wrapClickMenuOnIcon('', 'pages', $row['uid'], 0, '&bank='.$this->bank, '', TRUE)).';"';
137 $thePageTitle='<a href="#" onclick="'.htmlspecialchars($aOnClick).'"'.$CSM.'>'.$title.'</a>';
138
139 // Wrap title in a drag/drop span.
140 return '<span class="dragTitle" id="dragTitleID_'.$row['uid'].'">'.$thePageTitle.'</span>';
141 }
142
143 /**
144 * Compiles the HTML code for displaying the structure found inside the ->tree array
145 *
146 * @param array $treeArr "tree-array" - if blank string, the internal ->tree array is used.
147 * @return string The HTML code for the tree
148 */
149 function printTree($treeArr = '') {
150 $titleLen = intval($this->BE_USER->uc['titleLen']);
151 if (!is_array($treeArr)) {
152 $treeArr = $this->tree;
153 }
154
155 $out = '
156 <!-- TYPO3 tree structure. -->
157 <ul class="tree" id="treeRoot">
158 ';
159
160 // -- evaluate AJAX request
161 // IE takes anchor as parameter
162 $PM = t3lib_div::_GP('PM');
163 if (($PMpos = strpos($PM, '#')) !== FALSE) {
164 $PM = substr($PM, 0, $PMpos);
165 }
166 $PM = explode('_', $PM);
167 if ((TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) && is_array($PM) && count($PM) == 4 && $PM[2] != 0) {
168 if ($PM[1]) {
169 $expandedPageUid = $PM[2];
170 $ajaxOutput = '';
171 // We don't know yet. Will be set later.
172 $invertedDepthOfAjaxRequestedItem = 0;
173 $doExpand = TRUE;
174 } else {
175 $collapsedPageUid = $PM[2];
176 $doCollapse = TRUE;
177 }
178 }
179
180 // We need to count the opened <ul>'s every time we dig into another level,
181 // so we know how many we have to close when all children are done rendering
182 $closeDepth = array();
183
184 foreach ($treeArr as $k => $v) {
185 $classAttr = $v['row']['_CSSCLASS'];
186 $uid = $v['row']['uid'];
187 $idAttr = htmlspecialchars($this->domIdPrefix.$this->getId($v['row']).'_'.$v['bank']);
188 $itemHTML = '';
189
190 // If this item is the start of a new level,
191 // then a new level <ul> is needed, but not in ajax mode
192 if($v['isFirst'] && !($doCollapse) && !($doExpand && $expandedPageUid == $uid)) {
193 $itemHTML = '<ul>';
194 }
195
196 // Add CSS classes to the list item
197 if ($v['hasSub']) {
198 $classAttr .= ($classAttr) ? ' expanded' : 'expanded';
199 }
200 if ($v['isLast']) {
201 $classAttr .= ($classAttr) ? ' last' : 'last';
202 }
203
204 $itemHTML .= '
205 <li id="'.$idAttr.'"'.($classAttr ? ' class="'.$classAttr.'"' : '').'><div class="treeLinkItem">'.
206 $v['HTML'].
207 $this->wrapTitle($this->getTitleStr($v['row'], $titleLen), $v['row'], $v['bank'])."</div>\n";
208
209
210 if (!$v['hasSub']) {
211 $itemHTML .= '</li>';
212 }
213
214 // We have to remember if this is the last one
215 // on level X so the last child on level X+1 closes the <ul>-tag
216 if ($v['isLast'] && !($doExpand && $expandedPageUid == $uid)) {
217 $closeDepth[$v['invertedDepth']] = 1;
218 }
219
220 // If this is the last one and does not have subitems, we need to close
221 // the tree as long as the upper levels have last items too
222 if ($v['isLast'] && !$v['hasSub'] && !$doCollapse && !($doExpand && $expandedPageUid == $uid)) {
223 for ($i = $v['invertedDepth']; $closeDepth[$i] == 1; $i++) {
224 $closeDepth[$i] = 0;
225 $itemHTML .= '</ul></li>';
226 }
227 }
228
229 // Ajax request: collapse
230 if ($doCollapse && $collapsedPageUid == $uid) {
231 $this->ajaxStatus = TRUE;
232 return $itemHTML;
233 }
234
235 // ajax request: expand
236 if ($doExpand && $expandedPageUid == $uid) {
237 $ajaxOutput .= $itemHTML;
238 $invertedDepthOfAjaxRequestedItem = $v['invertedDepth'];
239 } elseif ($invertedDepthOfAjaxRequestedItem) {
240 if ($v['invertedDepth'] < $invertedDepthOfAjaxRequestedItem) {
241 $ajaxOutput .= $itemHTML;
242 } else {
243 $this->ajaxStatus = TRUE;
244 return $ajaxOutput;
245 }
246 }
247
248 $out .= $itemHTML;
249 }
250
251 if ($ajaxOutput) {
252 $this->ajaxStatus = TRUE;
253 return $ajaxOutput;
254 }
255
256 // Finally close the first ul
257 $out .= '</ul>';
258 return $out;
259 }
260
261 /**
262 * Generate the plus/minus icon for the browsable tree.
263 *
264 * @param array $row Record for the entry
265 * @param integer $a The current entry number
266 * @param integer $c The total number of entries. If equal to $a, a "bottom" element is returned.
267 * @param integer $nextCount The number of sub-elements to the current element.
268 * @param boolean $exp The element was expanded to render subelements if this flag is set.
269 * @return string Image tag with the plus/minus icon.
270 * @access private
271 * @see t3lib_pageTree::PMicon()
272 */
273 function PMicon($row, $a, $c, $nextCount, $exp) {
274 $PM = $nextCount ? ($exp ? 'minus' : 'plus') : 'join';
275 $BTM = ($a == $c) ? 'bottom' : '';
276 $icon = '<img'.t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/'.$PM.$BTM.'.gif', 'width="18" height="16"').' alt="" />';
277
278 if ($nextCount) {
279 $cmd = $this->bank.'_'.($exp?'0_':'1_').$row['uid'].'_'.$this->treeName;
280 $icon = $this->PMiconATagWrap($icon, $cmd, !$exp);
281 }
282 return $icon;
283 }
284
285 /**
286 * Wrap the plus/minus icon in a link
287 *
288 * @param string $icon HTML string to wrap, probably an image tag.
289 * @param string $cmd Command for 'PM' get var
290 * @return boolean $isExpand Link-wrapped input string
291 * @access private
292 */
293 function PMiconATagWrap($icon, $cmd, $isExpand = TRUE) {
294 if ($this->thisScript) {
295 // Activate dynamic ajax-based tree
296 $js = htmlspecialchars('Tree.load(\''.$cmd.'\', '.intval($isExpand).', this);');
297 return '<a class="pm" onclick="'.$js.'">'.$icon.'</a>';
298 } else {
299 return $icon;
300 }
301 }
302
303 /**
304 * Will create and return the HTML code for a browsable tree
305 * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
306 *
307 * @return string HTML code for the browsable tree
308 */
309 function getBrowsableTree() {
310
311 // Get stored tree structure AND updating it if needed according to incoming PM GET var.
312 $this->initializePositionSaving();
313
314 // Init done:
315 $titleLen = intval($this->BE_USER->uc['titleLen']);
316 $treeArr = array();
317
318 // Traverse mounts:
319 foreach ($this->MOUNTS as $idx => $uid) {
320
321 // Set first:
322 $this->bank = $idx;
323 $isOpen = $this->stored[$idx][$uid] || $this->expandFirst || $uid === '0';
324
325 // Save ids while resetting everything else.
326 $curIds = $this->ids;
327 $this->reset();
328 $this->ids = $curIds;
329
330 // Set PM icon for root of mount:
331 $cmd = $this->bank . '_' . ($isOpen? '0_' : '1_') . $uid . '_' . $this->treeName;
332 // Only, if not for uid 0
333 if ($uid) {
334 $icon = '<img' . t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/' . ($isOpen ? 'minus' : 'plus' ) . 'only.gif') . ' alt="" />';
335 $firstHtml = $this->PMiconATagWrap($icon, $cmd, !$isOpen);
336 }
337
338 // Preparing rootRec for the mount
339 if ($uid) {
340 $rootRec = $this->getRecord($uid);
341 $firstHtml.=$this->getIcon($rootRec);
342 } else {
343 // Artificial record for the tree root, id=0
344 $rootRec = $this->getRootRecord($uid);
345 $firstHtml.=$this->getRootIcon($rootRec);
346 }
347
348 if (is_array($rootRec)) {
349 // In case it was swapped inside getRecord due to workspaces.
350 $uid = $rootRec['uid'];
351
352 // Add the root of the mount to ->tree
353 $this->tree[] = array('HTML'=>$firstHtml, 'row'=>$rootRec, 'bank'=>$this->bank, 'hasSub'=>TRUE, 'invertedDepth'=>1000);
354
355 // If the mount is expanded, go down:
356 if ($isOpen) {
357 // Set depth:
358 if ($this->addSelfId) {
359 $this->ids[] = $uid;
360 }
361 $this->getTree($uid, 999, '', $rootRec['_SUBCSSCLASS']);
362 }
363 // Add tree:
364 $treeArr = array_merge($treeArr, $this->tree);
365 }
366 }
367 return $this->printTree($treeArr);
368 }
369
370 /**
371 * Fetches the data for the tree
372 *
373 * @param integer $uid Item id for which to select subitems (parent id)
374 * @param integer $depth Max depth (recursivity limit)
375 * @param string $blankLineCode ? (internal)
376 * @param string $subCSSclass
377 * @return integer The count of items on the level
378 */
379 function getTree($uid, $depth = 999, $blankLineCode = '', $subCSSclass = '') {
380
381 // Buffer for id hierarchy is reset:
382 $this->buffer_idH = array();
383
384 // Init vars
385 $depth = intval($depth);
386 $HTML = '';
387 $a = 0;
388
389 $res = $this->getDataInit($uid, $subCSSclass);
390 $c = $this->getDataCount($res);
391 $crazyRecursionLimiter = 999;
392
393 $inMenuPages = array();
394 $outOfMenuPages = array();
395 $outOfMenuPagesTextIndex = array();
396 while ($crazyRecursionLimiter > 0 && $row = $this->getDataNext($res, $subCSSclass)) {
397 $crazyRecursionLimiter--;
398
399 // Not in menu:
400 if ($this->ext_separateNotinmenuPages &&
401 ($row['doktype'] == t3lib_pageSelect::DOKTYPE_BE_USER_SECTION ||
402 $row['doktype'] >= 200 || $row['nav_hide'])) {
403 $outOfMenuPages[] = $row;
404 $outOfMenuPagesTextIndex[] = ($row['doktype']>=200 ? 'zzz'.$row['doktype'].'_' : '').$row['title'];
405 } else {
406 $inMenuPages[] = $row;
407 }
408 }
409
410 $label_shownAlphabetically = '';
411 if (count($outOfMenuPages)) {
412 // Sort out-of-menu pages:
413 $outOfMenuPages_alphabetic = array();
414 if ($this->ext_alphasortNotinmenuPages) {
415 asort($outOfMenuPagesTextIndex);
416 $label_shownAlphabetically = ' (alphabetic)';
417 }
418 foreach ($outOfMenuPagesTextIndex as $idx => $txt) {
419 $outOfMenuPages_alphabetic[] = $outOfMenuPages[$idx];
420 }
421
422 // Merge:
423 $outOfMenuPages_alphabetic[0]['_FIRST_NOT_IN_MENU'] = TRUE;
424 $allRows = array_merge($inMenuPages, $outOfMenuPages_alphabetic);
425 } else {
426 $allRows = $inMenuPages;
427 }
428
429 // Traverse the records:
430 foreach ($allRows as $row) {
431 $a++;
432
433 $newID = $row['uid'];
434 // Reserve space.
435 $this->tree[] = array();
436 end($this->tree);
437 // Get the key for this space
438 $treeKey = key($this->tree);
439 $LN = ($a == $c) ? 'blank' : 'line';
440
441 // If records should be accumulated, do so
442 if ($this->setRecs) {
443 $this->recs[$row['uid']] = $row;
444 }
445
446 // Accumulate the id of the element in the internal arrays
447 $this->ids[] = $idH[$row['uid']]['uid'] = $row['uid'];
448 $this->ids_hierarchy[$depth][] = $row['uid'];
449
450 // Make a recursive call to the next level
451 if ($depth > 1 && $this->expandNext($newID) && !$row['php_tree_stop']) {
452 $nextCount=$this->getTree(
453 $newID,
454 $depth-1,
455 $blankLineCode.','.$LN,
456 $row['_SUBCSSCLASS']
457 );
458 if (count($this->buffer_idH)) {
459 $idH[$row['uid']]['subrow']=$this->buffer_idH;
460 }
461 // Set "did expand" flag
462 $exp = 1;
463 } else {
464 $nextCount = $this->getCount($newID);
465 // Clear "did expand" flag
466 $exp = 0;
467 }
468
469 // Set HTML-icons, if any:
470 if ($this->makeHTML) {
471 if ($row['_FIRST_NOT_IN_MENU']) {
472 $HTML = '<img'.t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/line.gif').' alt="" /><br/><img'.t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/line.gif').' alt="" /><i>Not shown in menu'.$label_shownAlphabetically.':</i><br>';
473 } else {
474 $HTML = '';
475 }
476
477 $HTML .= $this->PMicon($row, $a, $c, $nextCount, $exp);
478 $HTML .= $this->wrapStop($this->getIcon($row), $row);
479 }
480
481 // Finally, add the row/HTML content to the ->tree array in the reserved key.
482 $this->tree[$treeKey] = array(
483 'row' => $row,
484 'HTML' => $HTML,
485 'hasSub' => $nextCount && $this->expandNext($newID),
486 'isFirst'=> $a == 1,
487 'isLast' => FALSE,
488 'invertedDepth'=> $depth,
489 'blankLineCode'=> $blankLineCode,
490 'bank' => $this->bank
491 );
492 }
493
494 if ($a) {
495 $this->tree[$treeKey]['isLast'] = TRUE;
496 }
497
498 $this->getDataFree($res);
499 $this->buffer_idH = $idH;
500 return $c;
501 }
502 }
503 ?>