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