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