[TASK] Call explicit render() on icon objects
[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\Tree\View\BrowseTreeView;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Imaging\Icon;
20 use TYPO3\CMS\Core\Imaging\IconFactory;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23 /**
24 * Browse pages in Web module
25 */
26 class PageTreeView extends BrowseTreeView {
27
28 /**
29 * @var bool
30 */
31 public $ext_showPageId = FALSE;
32
33 /**
34 * @var bool
35 */
36 public $ext_showNavTitle = FALSE;
37
38 /**
39 * @var string
40 */
41 public $ext_IconMode;
42
43 /**
44 * @var string
45 */
46 public $ext_separateNotinmenuPages;
47
48 /**
49 * @var string
50 */
51 public $ext_alphasortNotinmenuPages;
52
53 /**
54 * Indicates, whether the ajax call was successful, i.e. the requested page has been found
55 *
56 * @var bool
57 */
58 public $ajaxStatus = FALSE;
59
60 /**
61 * Calls init functions
62 */
63 public function __construct() {
64 $this->init();
65 }
66
67 /**
68 * Wrapping icon in browse tree
69 *
70 * @param string $thePageIcon Icon IMG code
71 * @param array $row Data row for element.
72 * @return string Page icon
73 */
74 public function wrapIcon($thePageIcon, &$row) {
75 /** @var $iconFactory IconFactory */
76 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
77 // If the record is locked, present a warning sign.
78 if ($lockInfo = BackendUtility::isRecordLocked('pages', $row['uid'])) {
79 $aOnClick = 'alert(' . GeneralUtility::quoteJSvalue($lockInfo['msg']) . ');return false;';
80 $lockIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">'
81 . '<span title="' . htmlspecialchars($lockInfo['msg']) . '">' . $iconFactory->getIcon('status-warning-in-use', Icon::SIZE_SMALL)->render() . '</span></a>';
82 } else {
83 $lockIcon = '';
84 }
85 // Wrap icon in click-menu link.
86 if (!$this->ext_IconMode) {
87 $thePageIcon = $this->getDocumentTemplate()->wrapClickMenuOnIcon($thePageIcon, 'pages', $row['uid'], 0, '&bank=' . $this->bank);
88 } elseif ($this->ext_IconMode === 'titlelink') {
89 $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,' . GeneralUtility::quoteJSvalue($this->treeName) . ');';
90 $thePageIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $thePageIcon . '</a>';
91 }
92 // Wrap icon in a drag/drop span.
93 $dragDropIcon = '<span class="list-tree-icon dragIcon" id="dragIconID_' . $row['uid'] . '">' . $thePageIcon . '</span> ';
94 // Add Page ID:
95 $pageIdStr = '';
96 if ($this->ext_showPageId) {
97 $pageIdStr = '<span class="dragId">[' . $row['uid'] . ']</span> ';
98 }
99 // Call stats information hook
100 $stat = '';
101 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'])) {
102 $_params = array('pages', $row['uid']);
103 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] as $_funcRef) {
104 $stat .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
105 }
106 }
107 return $dragDropIcon . $lockIcon . $pageIdStr . $stat;
108 }
109
110 /**
111 * Wrapping $title in a-tags.
112 *
113 * @param string $title Title string
114 * @param string $row Item record
115 * @param int $bank Bank pointer (which mount point number)
116 * @return string
117 * @access private
118 */
119 public function wrapTitle($title, $row, $bank = 0) {
120 // Hook for overriding the page title
121 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.webpagetree.php']['pageTitleOverlay'])) {
122 $_params = array('title' => &$title, 'row' => &$row);
123 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.webpagetree.php']['pageTitleOverlay'] as $_funcRef) {
124 GeneralUtility::callUserFunction($_funcRef, $_params, $this);
125 }
126 unset($_params);
127 }
128 $aOnClick = 'return jumpTo(' . GeneralUtility::quoteJSvalue($this->getJumpToParam($row)) . ',this,' . GeneralUtility::quoteJSvalue($this->domIdPrefix . $this->getId($row)) . ',' . $bank . ');';
129 $clickMenuParts = $this->getDocumentTemplate()->wrapClickMenuOnIcon('', 'pages', $row['uid'], 0, ('&bank=' . $this->bank), '', TRUE);
130
131 $thePageTitle = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '"' . GeneralUtility::implodeAttributes($clickMenuParts) . '>' . $title . '</a>';
132 // Wrap title in a drag/drop span.
133 return '<span class="list-tree-title dragTitle" id="dragTitleID_' . $row['uid'] . '">' . $thePageTitle . '</span>';
134 }
135
136 /**
137 * Compiles the HTML code for displaying the structure found inside the ->tree array
138 *
139 * @param array|string $treeArr "tree-array" - if blank string, the internal ->tree array is used.
140 * @return string The HTML code for the tree
141 */
142 public function printTree($treeArr = '') {
143 $titleLen = (int)$this->BE_USER->uc['titleLen'];
144 if (!is_array($treeArr)) {
145 $treeArr = $this->tree;
146 }
147 $out = '<ul class="list-tree list-tree-root">';
148 // -- evaluate AJAX request
149 // IE takes anchor as parameter
150 $PM = GeneralUtility::_GP('PM');
151 if (($PMpos = strpos($PM, '#')) !== FALSE) {
152 $PM = substr($PM, 0, $PMpos);
153 }
154 $PM = explode('_', $PM);
155
156 $doCollapse = FALSE;
157 $doExpand = FALSE;
158 $expandedPageUid = NULL;
159 $collapsedPageUid = NULL;
160 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX && is_array($PM) && count($PM) === 4 && $PM[2] != 0) {
161 if ($PM[1]) {
162 $expandedPageUid = $PM[2];
163 $doExpand = TRUE;
164 } else {
165 $collapsedPageUid = $PM[2];
166 $doCollapse = TRUE;
167 }
168 }
169 // We need to count the opened <ul>'s every time we dig into another level,
170 // so we know how many we have to close when all children are done rendering
171 $closeDepth = array();
172 $ajaxOutput = '';
173 $invertedDepthOfAjaxRequestedItem = 0;
174 foreach ($treeArr as $k => $treeItem) {
175 $classAttr = $treeItem['row']['_CSSCLASS'];
176 $uid = $treeItem['row']['uid'];
177 $idAttr = htmlspecialchars($this->domIdPrefix . $this->getId($treeItem['row']) . '_' . $treeItem['bank']);
178 $itemHTML = '';
179 // If this item is the start of a new level,
180 // then a new level <ul> is needed, but not in ajax mode
181 if ($treeItem['isFirst'] && !$doCollapse && (!$doExpand || (int)$expandedPageUid !== (int)$uid)) {
182 $itemHTML = '<ul class="list-tree">';
183 }
184
185 // Add CSS classes to the list item
186 if ($treeItem['hasSub']) {
187 $classAttr .= ' list-tree-control-open';
188 }
189 $itemHTML .= '<li id="' . $idAttr . '" ' . ($classAttr ? ' class="' . trim($classAttr) . '"' : '')
190 . '><span class="list-tree-group">' . $treeItem['HTML']
191 . $this->wrapTitle($this->getTitleStr($treeItem['row'], $titleLen), $treeItem['row'], $treeItem['bank']) . '</span>';
192 if (!$treeItem['hasSub']) {
193 $itemHTML .= '</li>';
194 }
195
196 // We have to remember if this is the last one
197 // on level X so the last child on level X+1 closes the <ul>-tag
198 if ($treeItem['isLast'] && !($doExpand && $expandedPageUid == $uid)) {
199 $closeDepth[$treeItem['invertedDepth']] = 1;
200 }
201 // If this is the last one and does not have subitems, we need to close
202 // the tree as long as the upper levels have last items too
203 if ($treeItem['isLast'] && !$treeItem['hasSub'] && !$doCollapse && !($doExpand && $expandedPageUid == $uid)) {
204 for ($i = $treeItem['invertedDepth']; $closeDepth[$i] == 1; $i++) {
205 $closeDepth[$i] = 0;
206 $itemHTML .= '</ul></li>';
207 }
208 }
209 // Ajax request: collapse
210 if ($doCollapse && (int)$collapsedPageUid === (int)$uid) {
211 $this->ajaxStatus = TRUE;
212 return $itemHTML;
213 }
214 // ajax request: expand
215 if ($doExpand && (int)$expandedPageUid === (int)$uid) {
216 $ajaxOutput .= $itemHTML;
217 $invertedDepthOfAjaxRequestedItem = $treeItem['invertedDepth'];
218 } elseif ($invertedDepthOfAjaxRequestedItem) {
219 if ($treeItem['invertedDepth'] < $invertedDepthOfAjaxRequestedItem) {
220 $ajaxOutput .= $itemHTML;
221 } else {
222 $this->ajaxStatus = TRUE;
223 return $ajaxOutput;
224 }
225 }
226 $out .= $itemHTML;
227 }
228 if ($ajaxOutput) {
229 $this->ajaxStatus = TRUE;
230 return $ajaxOutput;
231 }
232 // Finally close the first ul
233 $out .= '</ul>';
234 return $out;
235 }
236
237 /**
238 * Generate the plus/minus icon for the browsable tree.
239 *
240 * @param array $row Record for the entry
241 * @param int $a The current entry number
242 * @param int $c The total number of entries. If equal to $a, a "bottom" element is returned.
243 * @param int $nextCount The number of sub-elements to the current element.
244 * @param bool $exp The element was expanded to render subelements if this flag is set.
245 * @return string Image tag with the plus/minus icon.
246 * @access private
247 * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
248 */
249 public function PMicon($row, $a, $c, $nextCount, $exp) {
250 $icon = '';
251 if ($nextCount) {
252 $cmd = $this->bank . '_' . ($exp ? '0_' : '1_') . $row['uid'] . '_' . $this->treeName;
253 $icon = $this->PMiconATagWrap($icon, $cmd, !$exp);
254 }
255 return $icon;
256 }
257
258 /**
259 * Wrap the plus/minus icon in a link
260 *
261 * @param string $icon HTML string to wrap, probably an image tag.
262 * @param string $cmd Command for 'PM' get var
263 * @param bool $isExpand Link-wrapped input string
264 * @return string
265 * @access private
266 */
267 public function PMiconATagWrap($icon, $cmd, $isExpand = TRUE) {
268 if ($this->thisScript) {
269 // Activate dynamic ajax-based tree
270 $js = htmlspecialchars('Tree.load(' . GeneralUtility::quoteJSvalue($cmd) . ', ' . (int)$isExpand . ', this);');
271 return '<a class="list-tree-control' . (!$isExpand ? ' list-tree-control-open' : ' list-tree-control-closed') . '" onclick="' . $js . '"><i class="fa"></i></a>';
272 } else {
273 return $icon;
274 }
275 }
276
277 /**
278 * Will create and return the HTML code for a browsable tree
279 * Is based on the mounts found in the internal array ->MOUNTS (set in the constructor)
280 *
281 * @return string HTML code for the browsable tree
282 */
283 public function getBrowsableTree() {
284 // Get stored tree structure AND updating it if needed according to incoming PM GET var.
285 $this->initializePositionSaving();
286 // Init done:
287 $treeArr = array();
288 // Traverse mounts:
289 $firstHtml = '';
290 foreach ($this->MOUNTS as $idx => $uid) {
291 // Set first:
292 $this->bank = $idx;
293 $isOpen = $this->stored[$idx][$uid] || $this->expandFirst || $uid === '0';
294 // Save ids while resetting everything else.
295 $curIds = $this->ids;
296 $this->reset();
297 $this->ids = $curIds;
298 // Only, if not for uid 0
299 if ($uid) {
300 // Set PM icon for root of mount:
301 $cmd = $this->bank . '_' . ($isOpen ? '0_' : '1_') . $uid . '_' . $this->treeName;
302 $firstHtml = '<a class="list-tree-control list-tree-control-' . ($isOpen ? 'open' : 'closed')
303 . '" href="' . htmlspecialchars($this->getThisScript() . 'PM=' . $cmd) . '"><i class="fa"></i></a>';
304 }
305 // Preparing rootRec for the mount
306 if ($uid) {
307 $rootRec = $this->getRecord($uid);
308 $firstHtml .= $this->getIcon($rootRec);
309 } else {
310 // Artificial record for the tree root, id=0
311 $rootRec = $this->getRootRecord($uid);
312 $firstHtml .= $this->getRootIcon($rootRec);
313 }
314 if (is_array($rootRec)) {
315 // In case it was swapped inside getRecord due to workspaces.
316 $uid = $rootRec['uid'];
317 // Add the root of the mount to ->tree
318 $this->tree[] = array('HTML' => $firstHtml, 'row' => $rootRec, 'bank' => $this->bank, 'hasSub' => TRUE, 'invertedDepth' => 1000);
319 // If the mount is expanded, go down:
320 if ($isOpen) {
321 // Set depth:
322 if ($this->addSelfId) {
323 $this->ids[] = $uid;
324 }
325 $this->getTree($uid);
326 }
327 // Add tree:
328 $treeArr = array_merge($treeArr, $this->tree);
329 }
330 }
331 return $this->printTree($treeArr);
332 }
333
334 /**
335 * Returns an instance of DocumentTemplate
336 *
337 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
338 */
339 protected function getDocumentTemplate() {
340 return $GLOBALS['TBE_TEMPLATE'];
341 }
342
343 }