[TASK] Fix CGL violations against CharacterAfterPHPClosingTag
[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 if (!$v['hasSub']) {
210 $itemHTML .= '</li>';
211 }
212
213 // We have to remember if this is the last one
214 // on level X so the last child on level X+1 closes the <ul>-tag
215 if ($v['isLast'] && !($doExpand && $expandedPageUid == $uid)) {
216 $closeDepth[$v['invertedDepth']] = 1;
217 }
218
219 // If this is the last one and does not have subitems, we need to close
220 // the tree as long as the upper levels have last items too
221 if ($v['isLast'] && !$v['hasSub'] && !$doCollapse && !($doExpand && $expandedPageUid == $uid)) {
222 for ($i = $v['invertedDepth']; $closeDepth[$i] == 1; $i++) {
223 $closeDepth[$i] = 0;
224 $itemHTML .= '</ul></li>';
225 }
226 }
227
228 // Ajax request: collapse
229 if ($doCollapse && $collapsedPageUid == $uid) {
230 $this->ajaxStatus = TRUE;
231 return $itemHTML;
232 }
233
234 // ajax request: expand
235 if ($doExpand && $expandedPageUid == $uid) {
236 $ajaxOutput .= $itemHTML;
237 $invertedDepthOfAjaxRequestedItem = $v['invertedDepth'];
238 } elseif ($invertedDepthOfAjaxRequestedItem) {
239 if ($v['invertedDepth'] < $invertedDepthOfAjaxRequestedItem) {
240 $ajaxOutput .= $itemHTML;
241 } else {
242 $this->ajaxStatus = TRUE;
243 return $ajaxOutput;
244 }
245 }
246
247 $out .= $itemHTML;
248 }
249
250 if ($ajaxOutput) {
251 $this->ajaxStatus = TRUE;
252 return $ajaxOutput;
253 }
254
255 // Finally close the first ul
256 $out .= '</ul>';
257 return $out;
258 }
259
260 /**
261 * Generate the plus/minus icon for the browsable tree.
262 *
263 * @param array $row Record for the entry
264 * @param integer $a The current entry number
265 * @param integer $c The total number of entries. If equal to $a, a "bottom" element is returned.
266 * @param integer $nextCount The number of sub-elements to the current element.
267 * @param boolean $exp The element was expanded to render subelements if this flag is set.
268 * @return string Image tag with the plus/minus icon.
269 * @access private
270 * @see t3lib_pageTree::PMicon()
271 */
272 function PMicon($row, $a, $c, $nextCount, $exp) {
273 $PM = $nextCount ? ($exp ? 'minus' : 'plus') : 'join';
274 $BTM = ($a == $c) ? 'bottom' : '';
275 $icon = '<img'.t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/'.$PM.$BTM.'.gif', 'width="18" height="16"').' alt="" />';
276
277 if ($nextCount) {
278 $cmd = $this->bank.'_'.($exp?'0_':'1_').$row['uid'].'_'.$this->treeName;
279 $icon = $this->PMiconATagWrap($icon, $cmd, !$exp);
280 }
281 return $icon;
282 }
283
284 /**
285 * Wrap the plus/minus icon in a link
286 *
287 * @param string $icon HTML string to wrap, probably an image tag.
288 * @param string $cmd Command for 'PM' get var
289 * @return boolean $isExpand Link-wrapped input string
290 * @access private
291 */
292 function PMiconATagWrap($icon, $cmd, $isExpand = TRUE) {
293 if ($this->thisScript) {
294 // Activate dynamic ajax-based tree
295 $js = htmlspecialchars('Tree.load(\''.$cmd.'\', '.intval($isExpand).', this);');
296 return '<a class="pm" onclick="'.$js.'">'.$icon.'</a>';
297 } else {
298 return $icon;
299 }
300 }
301
302 /**
303 * Will create and return the HTML code for a browsable tree
304 * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
305 *
306 * @return string HTML code for the browsable tree
307 */
308 function getBrowsableTree() {
309
310 // Get stored tree structure AND updating it if needed according to incoming PM GET var.
311 $this->initializePositionSaving();
312
313 // Init done:
314 $titleLen = intval($this->BE_USER->uc['titleLen']);
315 $treeArr = array();
316
317 // Traverse mounts:
318 foreach ($this->MOUNTS as $idx => $uid) {
319
320 // Set first:
321 $this->bank = $idx;
322 $isOpen = $this->stored[$idx][$uid] || $this->expandFirst || $uid === '0';
323
324 // Save ids while resetting everything else.
325 $curIds = $this->ids;
326 $this->reset();
327 $this->ids = $curIds;
328
329 // Set PM icon for root of mount:
330 $cmd = $this->bank . '_' . ($isOpen? '0_' : '1_') . $uid . '_' . $this->treeName;
331 // Only, if not for uid 0
332 if ($uid) {
333 $icon = '<img' . t3lib_iconWorks::skinImg($this->backPath, 'gfx/ol/' . ($isOpen ? 'minus' : 'plus' ) . 'only.gif') . ' alt="" />';
334 $firstHtml = $this->PMiconATagWrap($icon, $cmd, !$isOpen);
335 }
336
337 // Preparing rootRec for the mount
338 if ($uid) {
339 $rootRec = $this->getRecord($uid);
340 $firstHtml.=$this->getIcon($rootRec);
341 } else {
342 // Artificial record for the tree root, id=0
343 $rootRec = $this->getRootRecord($uid);
344 $firstHtml.=$this->getRootIcon($rootRec);
345 }
346
347 if (is_array($rootRec)) {
348 // In case it was swapped inside getRecord due to workspaces.
349 $uid = $rootRec['uid'];
350
351 // Add the root of the mount to ->tree
352 $this->tree[] = array('HTML'=>$firstHtml, 'row'=>$rootRec, 'bank'=>$this->bank, 'hasSub'=>TRUE, 'invertedDepth'=>1000);
353
354 // If the mount is expanded, go down:
355 if ($isOpen) {
356 // Set depth:
357 if ($this->addSelfId) {
358 $this->ids[] = $uid;
359 }
360 $this->getTree($uid, 999, '', $rootRec['_SUBCSSCLASS']);
361 }
362 // Add tree:
363 $treeArr = array_merge($treeArr, $this->tree);
364 }
365 }
366 return $this->printTree($treeArr);
367 }
368
369 /**
370 * Fetches the data for the tree
371 *
372 * @param integer $uid Item id for which to select subitems (parent id)
373 * @param integer $depth Max depth (recursivity limit)
374 * @param string $blankLineCode ? (internal)
375 * @param string $subCSSclass
376 * @return integer The count of items on the level
377 */
378 function getTree($uid, $depth = 999, $blankLineCode = '', $subCSSclass = '') {
379
380 // Buffer for id hierarchy is reset:
381 $this->buffer_idH = array();
382
383 // Init vars
384 $depth = intval($depth);
385 $HTML = '';
386 $a = 0;
387
388 $res = $this->getDataInit($uid, $subCSSclass);
389 $c = $this->getDataCount($res);
390 $crazyRecursionLimiter = 999;
391
392 $inMenuPages = array();
393 $outOfMenuPages = array();
394 $outOfMenuPagesTextIndex = array();
395 while ($crazyRecursionLimiter > 0 && $row = $this->getDataNext($res, $subCSSclass)) {
396 $crazyRecursionLimiter--;
397
398 // Not in menu:
399 if ($this->ext_separateNotinmenuPages &&
400 ($row['doktype'] == t3lib_pageSelect::DOKTYPE_BE_USER_SECTION ||
401 $row['doktype'] >= 200 || $row['nav_hide'])) {
402 $outOfMenuPages[] = $row;
403 $outOfMenuPagesTextIndex[] = ($row['doktype']>=200 ? 'zzz'.$row['doktype'].'_' : '').$row['title'];
404 } else {
405 $inMenuPages[] = $row;
406 }
407 }
408
409 $label_shownAlphabetically = '';
410 if (count($outOfMenuPages)) {
411 // Sort out-of-menu pages:
412 $outOfMenuPages_alphabetic = array();
413 if ($this->ext_alphasortNotinmenuPages) {
414 asort($outOfMenuPagesTextIndex);
415 $label_shownAlphabetically = ' (alphabetic)';
416 }
417 foreach ($outOfMenuPagesTextIndex as $idx => $txt) {
418 $outOfMenuPages_alphabetic[] = $outOfMenuPages[$idx];
419 }
420
421 // Merge:
422 $outOfMenuPages_alphabetic[0]['_FIRST_NOT_IN_MENU'] = TRUE;
423 $allRows = array_merge($inMenuPages, $outOfMenuPages_alphabetic);
424 } else {
425 $allRows = $inMenuPages;
426 }
427
428 // Traverse the records:
429 foreach ($allRows as $row) {
430 $a++;
431
432 $newID = $row['uid'];
433 // Reserve space.
434 $this->tree[] = array();
435 end($this->tree);
436 // Get the key for this space
437 $treeKey = key($this->tree);
438 $LN = ($a == $c) ? 'blank' : 'line';
439
440 // If records should be accumulated, do so
441 if ($this->setRecs) {
442 $this->recs[$row['uid']] = $row;
443 }
444
445 // Accumulate the id of the element in the internal arrays
446 $this->ids[] = $idH[$row['uid']]['uid'] = $row['uid'];
447 $this->ids_hierarchy[$depth][] = $row['uid'];
448
449 // Make a recursive call to the next level
450 if ($depth > 1 && $this->expandNext($newID) && !$row['php_tree_stop']) {
451 $nextCount=$this->getTree(
452 $newID,
453 $depth-1,
454 $blankLineCode.','.$LN,
455 $row['_SUBCSSCLASS']
456 );
457 if (count($this->buffer_idH)) {
458 $idH[$row['uid']]['subrow']=$this->buffer_idH;
459 }
460 // Set "did expand" flag
461 $exp = 1;
462 } else {
463 $nextCount = $this->getCount($newID);
464 // Clear "did expand" flag
465 $exp = 0;
466 }
467
468 // Set HTML-icons, if any:
469 if ($this->makeHTML) {
470 if ($row['_FIRST_NOT_IN_MENU']) {
471 $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>';
472 } else {
473 $HTML = '';
474 }
475
476 $HTML .= $this->PMicon($row, $a, $c, $nextCount, $exp);
477 $HTML .= $this->wrapStop($this->getIcon($row), $row);
478 }
479
480 // Finally, add the row/HTML content to the ->tree array in the reserved key.
481 $this->tree[$treeKey] = array(
482 'row' => $row,
483 'HTML' => $HTML,
484 'hasSub' => $nextCount && $this->expandNext($newID),
485 'isFirst'=> $a == 1,
486 'isLast' => FALSE,
487 'invertedDepth'=> $depth,
488 'blankLineCode'=> $blankLineCode,
489 'bank' => $this->bank
490 );
491 }
492
493 if ($a) {
494 $this->tree[$treeKey]['isLast'] = TRUE;
495 }
496
497 $this->getDataFree($res);
498 $this->buffer_idH = $idH;
499 return $c;
500 }
501 }
502 ?>