[BUGFIX] Streamline bool arguments throughout the core
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / ContentObject / Menu / AbstractMenuContentObject.php
1 <?php
2 namespace TYPO3\CMS\Frontend\ContentObject\Menu;
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\Core\Cache\CacheManager;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Database\RelationHandler;
20 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
21 use TYPO3\CMS\Core\TypoScript\TemplateService;
22 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\MathUtility;
25 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
26 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
27 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
28 use TYPO3\CMS\Frontend\Page\PageRepository;
29
30 /**
31 * Generating navigation/menus from TypoScript
32 *
33 * The HMENU content object uses this (or more precisely one of the extension classes).
34 * Among others the class generates an array of menu items. Thereafter functions from the subclasses are called.
35 * The class is always used through extension classes (like GraphicalMenuContentObject or TextMenuContentObject).
36 */
37 abstract class AbstractMenuContentObject
38 {
39 /**
40 * tells you which menu number this is. This is important when getting data from the setup
41 *
42 * @var int
43 */
44 public $menuNumber = 1;
45
46 /**
47 * 0 = rootFolder
48 *
49 * @var int
50 */
51 public $entryLevel = 0;
52
53 /**
54 * The doktype-number that defines a spacer
55 *
56 * @var string
57 */
58 public $spacerIDList = '199';
59
60 /**
61 * Doktypes that define which should not be included in a menu
62 *
63 * @var string
64 */
65 public $doktypeExcludeList = '6';
66
67 /**
68 * @var int[]
69 */
70 public $alwaysActivePIDlist = [];
71
72 /**
73 * @var string
74 */
75 public $imgNamePrefix = 'img';
76
77 /**
78 * @var int
79 */
80 public $imgNameNotRandom = 0;
81
82 /**
83 * @var bool
84 */
85 public $debug = false;
86
87 /**
88 * Loaded with the parent cObj-object when a new HMENU is made
89 *
90 * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
91 */
92 public $parent_cObj = null;
93
94 /**
95 * @var string
96 */
97 public $GMENU_fixKey = 'gmenu';
98
99 /**
100 * accumulation of mount point data
101 *
102 * @var string[]
103 */
104 public $MP_array = [];
105
106 /**
107 * HMENU configuration
108 *
109 * @var array
110 */
111 public $conf = [];
112
113 /**
114 * xMENU configuration (TMENU, GMENU etc)
115 *
116 * @var array
117 */
118 public $mconf = [];
119
120 /**
121 * @var \TYPO3\CMS\Core\TypoScript\TemplateService
122 */
123 public $tmpl = null;
124
125 /**
126 * @var \TYPO3\CMS\Frontend\Page\PageRepository
127 */
128 public $sys_page = null;
129
130 /**
131 * The base page-id of the menu.
132 *
133 * @var int
134 */
135 public $id;
136
137 /**
138 * Holds the page uid of the NEXT page in the root line from the page pointed to by entryLevel;
139 * Used to expand the menu automatically if in a certain root line.
140 *
141 * @var string
142 */
143 public $nextActive;
144
145 /**
146 * The array of menuItems which is built
147 *
148 * @var array[]
149 */
150 public $menuArr;
151
152 /**
153 * @var string
154 */
155 public $hash;
156
157 /**
158 * @var array
159 */
160 public $result = [];
161
162 /**
163 * Is filled with an array of page uid numbers + RL parameters which are in the current
164 * root line (used to evaluate whether a menu item is in active state)
165 *
166 * @var array
167 */
168 public $rL_uidRegister;
169
170 /**
171 * @var string
172 */
173 public $INPfixMD5;
174
175 /**
176 * @var mixed[]
177 */
178 public $I;
179
180 /**
181 * @var string
182 */
183 public $WMresult;
184
185 /**
186 * @var string
187 */
188 public $WMfreezePrefix;
189
190 /**
191 * @var int
192 */
193 public $WMmenuItems;
194
195 /**
196 * @var array[]
197 */
198 public $WMsubmenuObjSuffixes;
199
200 /**
201 * @var string
202 */
203 public $WMextraScript;
204
205 /**
206 * @var ContentObjectRenderer
207 */
208 public $WMcObj = null;
209
210 /**
211 * Can be set to contain menu item arrays for sub-levels.
212 *
213 * @var string
214 */
215 public $alternativeMenuTempArray = '';
216
217 /**
218 * Will be 'id' in XHTML-mode
219 *
220 * @var string
221 */
222 public $nameAttribute = 'name';
223
224 /**
225 * TRUE to use cHash in generated link (normally only for the language
226 * selector and if parameters exist in the URL).
227 *
228 * @var bool
229 */
230 protected $useCacheHash = false;
231
232 /**
233 * Array key of the parentMenuItem in the parentMenuArr, if this menu is a subMenu.
234 *
235 * @var null|int
236 */
237 protected $parentMenuArrItemKey;
238
239 /**
240 * The initialization of the object. This just sets some internal variables.
241 *
242 * @param TemplateService $tmpl The $this->getTypoScriptFrontendController()->tmpl object
243 * @param PageRepository $sys_page The $this->getTypoScriptFrontendController()->sys_page object
244 * @param int|string $id A starting point page id. This should probably be blank since the 'entryLevel' value will be used then.
245 * @param array $conf The TypoScript configuration for the HMENU cObject
246 * @param int $menuNumber Menu number; 1,2,3. Should probably be 1
247 * @param string $objSuffix Submenu Object suffix. This offers submenus a way to use alternative configuration for specific positions in the menu; By default "1 = TMENU" would use "1." for the TMENU configuration, but if this string is set to eg. "a" then "1a." would be used for configuration instead (while "1 = " is still used for the overall object definition of "TMENU")
248 * @return bool Returns TRUE on success
249 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::HMENU()
250 */
251 public function start($tmpl, $sys_page, $id, $conf, $menuNumber, $objSuffix = '')
252 {
253 $tsfe = $this->getTypoScriptFrontendController();
254 // Init:
255 $this->conf = $conf;
256 $this->menuNumber = $menuNumber;
257 $this->mconf = $conf[$this->menuNumber . $objSuffix . '.'];
258 $this->debug = $tsfe->debug;
259 $this->WMcObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
260 // In XHTML and HTML5 there is no "name" attribute anymore
261 switch ($tsfe->xhtmlDoctype) {
262 case 'xhtml_strict':
263 // intended fall-through
264 case 'xhtml_11':
265 // intended fall-through
266 case 'html5':
267 // intended fall-through
268 case '':
269 // empty means that it's HTML5 by default
270 $this->nameAttribute = 'id';
271 break;
272 default:
273 $this->nameAttribute = 'name';
274 }
275 // Sets the internal vars. $tmpl MUST be the template-object. $sys_page MUST be the sys_page object
276 if ($this->conf[$this->menuNumber . $objSuffix] && is_object($tmpl) && is_object($sys_page)) {
277 $this->tmpl = $tmpl;
278 $this->sys_page = $sys_page;
279 // alwaysActivePIDlist initialized:
280 if (trim($this->conf['alwaysActivePIDlist']) || isset($this->conf['alwaysActivePIDlist.'])) {
281 if (isset($this->conf['alwaysActivePIDlist.'])) {
282 $this->conf['alwaysActivePIDlist'] = $this->parent_cObj->stdWrap(
283 $this->conf['alwaysActivePIDlist'],
284 $this->conf['alwaysActivePIDlist.']
285 );
286 }
287 $this->alwaysActivePIDlist = GeneralUtility::intExplode(',', $this->conf['alwaysActivePIDlist']);
288 }
289 // 'not in menu' doktypes
290 if ($this->conf['excludeDoktypes']) {
291 $this->doktypeExcludeList = implode(',', GeneralUtility::intExplode(',', $this->conf['excludeDoktypes']));
292 }
293 // EntryLevel
294 $this->entryLevel = $this->parent_cObj->getKey(
295 isset($conf['entryLevel.']) ? $this->parent_cObj->stdWrap(
296 $conf['entryLevel'],
297 $conf['entryLevel.']
298 ) : $conf['entryLevel'],
299 $this->tmpl->rootLine
300 );
301 // Set parent page: If $id not stated with start() then the base-id will be found from rootLine[$this->entryLevel]
302 // Called as the next level in a menu. It is assumed that $this->MP_array is set from parent menu.
303 if ($id) {
304 $this->id = (int)$id;
305 } else {
306 // This is a BRAND NEW menu, first level. So we take ID from rootline and also find MP_array (mount points)
307 $this->id = (int)$this->tmpl->rootLine[$this->entryLevel]['uid'];
308 // Traverse rootline to build MP_array of pages BEFORE the entryLevel
309 // (MP var for ->id is picked up in the next part of the code...)
310 foreach ($this->tmpl->rootLine as $entryLevel => $levelRec) {
311 // For overlaid mount points, set the variable right now:
312 if ($levelRec['_MP_PARAM'] && $levelRec['_MOUNT_OL']) {
313 $this->MP_array[] = $levelRec['_MP_PARAM'];
314 }
315 // Break when entry level is reached:
316 if ($entryLevel >= $this->entryLevel) {
317 break;
318 }
319 // For normal mount points, set the variable for next level.
320 if ($levelRec['_MP_PARAM'] && !$levelRec['_MOUNT_OL']) {
321 $this->MP_array[] = $levelRec['_MP_PARAM'];
322 }
323 }
324 }
325 // Return FALSE if no page ID was set (thus no menu of subpages can be made).
326 if ($this->id <= 0) {
327 return false;
328 }
329 // Check if page is a mount point, and if so set id and MP_array
330 // (basically this is ONLY for non-overlay mode, but in overlay mode an ID with a mount point should never reach this point anyways, so no harm done...)
331 $mount_info = $this->sys_page->getMountPointInfo($this->id);
332 if (is_array($mount_info)) {
333 $this->MP_array[] = $mount_info['MPvar'];
334 $this->id = $mount_info['mount_pid'];
335 }
336 // Gather list of page uids in root line (for "isActive" evaluation). Also adds the MP params in the path so Mount Points are respected.
337 // (List is specific for this rootline, so it may be supplied from parent menus for speed...)
338 if ($this->rL_uidRegister === null) {
339 $this->rL_uidRegister = [];
340 $rl_MParray = [];
341 foreach ($this->tmpl->rootLine as $v_rl) {
342 // For overlaid mount points, set the variable right now:
343 if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
344 $rl_MParray[] = $v_rl['_MP_PARAM'];
345 }
346 // Add to register:
347 $this->rL_uidRegister[] = 'ITEM:' . $v_rl['uid'] .
348 (!empty($rl_MParray)
349 ? ':' . implode(',', $rl_MParray)
350 : ''
351 );
352 // For normal mount points, set the variable for next level.
353 if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
354 $rl_MParray[] = $v_rl['_MP_PARAM'];
355 }
356 }
357 }
358 // Set $directoryLevel so the following evalution of the nextActive will not return
359 // an invalid value if .special=directory was set
360 $directoryLevel = 0;
361 if ($this->conf['special'] === 'directory') {
362 $value = isset($this->conf['special.']['value.']) ? $this->parent_cObj->stdWrap(
363 $this->conf['special.']['value'],
364 $this->conf['special.']['value.']
365 ) : $this->conf['special.']['value'];
366 if ($value === '') {
367 $value = $tsfe->page['uid'];
368 }
369 $directoryLevel = (int)$tsfe->tmpl->getRootlineLevel($value);
370 }
371 // Setting "nextActive": This is the page uid + MPvar of the NEXT page in rootline. Used to expand the menu if we are in the right branch of the tree
372 // Notice: The automatic expansion of a menu is designed to work only when no "special" modes (except "directory") are used.
373 $startLevel = $directoryLevel ?: $this->entryLevel;
374 $currentLevel = $startLevel + $this->menuNumber;
375 if (is_array($this->tmpl->rootLine[$currentLevel])) {
376 $nextMParray = $this->MP_array;
377 if (empty($nextMParray) && !$this->tmpl->rootLine[$currentLevel]['_MOUNT_OL'] && $currentLevel > 0) {
378 // Make sure to slide-down any mount point information (_MP_PARAM) to children records in the rootline
379 // otherwise automatic expansion will not work
380 $parentRecord = $this->tmpl->rootLine[$currentLevel - 1];
381 if (isset($parentRecord['_MP_PARAM'])) {
382 $nextMParray[] = $parentRecord['_MP_PARAM'];
383 }
384 }
385 // In overlay mode, add next level MPvars as well:
386 if ($this->tmpl->rootLine[$currentLevel]['_MOUNT_OL']) {
387 $nextMParray[] = $this->tmpl->rootLine[$currentLevel]['_MP_PARAM'];
388 }
389 $this->nextActive = $this->tmpl->rootLine[$currentLevel]['uid'] .
390 (!empty($nextMParray)
391 ? ':' . implode(',', $nextMParray)
392 : ''
393 );
394 } else {
395 $this->nextActive = '';
396 }
397 // imgNamePrefix
398 if ($this->mconf['imgNamePrefix']) {
399 $this->imgNamePrefix = $this->mconf['imgNamePrefix'];
400 }
401 $this->imgNameNotRandom = $this->mconf['imgNameNotRandom'];
402 $retVal = true;
403 } else {
404 $this->getTimeTracker()->setTSlogMessage('ERROR in menu', 3);
405 $retVal = false;
406 }
407 return $retVal;
408 }
409
410 /**
411 * Creates the menu in the internal variables, ready for output.
412 * Basically this will read the page records needed and fill in the internal $this->menuArr
413 * Based on a hash of this array and some other variables the $this->result variable will be
414 * loaded either from cache OR by calling the generate() method of the class to create the menu for real.
415 */
416 public function makeMenu()
417 {
418 if (!$this->id) {
419 return;
420 }
421
422 $this->useCacheHash = false;
423
424 // Initializing showAccessRestrictedPages
425 $SAVED_where_groupAccess = '';
426 if ($this->mconf['showAccessRestrictedPages']) {
427 // SAVING where_groupAccess
428 $SAVED_where_groupAccess = $this->sys_page->where_groupAccess;
429 // Temporarily removing fe_group checking!
430 $this->sys_page->where_groupAccess = '';
431 }
432
433 $menuItems = $this->prepareMenuItems();
434
435 $c = 0;
436 $c_b = 0;
437 $minItems = (int)($this->mconf['minItems'] ?: $this->conf['minItems']);
438 $maxItems = (int)($this->mconf['maxItems'] ?: $this->conf['maxItems']);
439 $begin = $this->parent_cObj->calc($this->mconf['begin'] ? $this->mconf['begin'] : $this->conf['begin']);
440 $minItemsConf = isset($this->mconf['minItems.']) ? $this->mconf['minItems.'] : (isset($this->conf['minItems.']) ? $this->conf['minItems.'] : null);
441 $minItems = is_array($minItemsConf) ? $this->parent_cObj->stdWrap($minItems, $minItemsConf) : $minItems;
442 $maxItemsConf = isset($this->mconf['maxItems.']) ? $this->mconf['maxItems.'] : (isset($this->conf['maxItems.']) ? $this->conf['maxItems.'] : null);
443 $maxItems = is_array($maxItemsConf) ? $this->parent_cObj->stdWrap($maxItems, $maxItemsConf) : $maxItems;
444 $beginConf = isset($this->mconf['begin.']) ? $this->mconf['begin.'] : (isset($this->conf['begin.']) ? $this->conf['begin.'] : null);
445 $begin = is_array($beginConf) ? $this->parent_cObj->stdWrap($begin, $beginConf) : $begin;
446 $banUidArray = $this->getBannedUids();
447 // Fill in the menuArr with elements that should go into the menu:
448 $this->menuArr = [];
449 foreach ($menuItems as $data) {
450 $spacer = GeneralUtility::inList($this->spacerIDList, $data['doktype']) || $data['ITEM_STATE'] === 'SPC';
451 // if item is a spacer, $spacer is set
452 if ($this->filterMenuPages($data, $banUidArray, $spacer)) {
453 $c_b++;
454 // If the beginning item has been reached.
455 if ($begin <= $c_b) {
456 $this->menuArr[$c] = $data;
457 $this->menuArr[$c]['isSpacer'] = $spacer;
458 $c++;
459 if ($maxItems && $c >= $maxItems) {
460 break;
461 }
462 }
463 }
464 }
465 // Fill in fake items, if min-items is set.
466 if ($minItems) {
467 while ($c < $minItems) {
468 $this->menuArr[$c] = [
469 'title' => '...',
470 'uid' => $this->getTypoScriptFrontendController()->id
471 ];
472 $c++;
473 }
474 }
475 // Passing the menuArr through a user defined function:
476 if ($this->mconf['itemArrayProcFunc']) {
477 $this->menuArr = $this->userProcess('itemArrayProcFunc', $this->menuArr);
478 }
479 // Setting number of menu items
480 $this->getTypoScriptFrontendController()->register['count_menuItems'] = count($this->menuArr);
481 $this->hash = md5(
482 serialize($this->menuArr) .
483 serialize($this->mconf) .
484 serialize($this->tmpl->rootLine) .
485 serialize($this->MP_array)
486 );
487 // Get the cache timeout:
488 if ($this->conf['cache_period']) {
489 $cacheTimeout = $this->conf['cache_period'];
490 } else {
491 $cacheTimeout = $this->getTypoScriptFrontendController()->get_cache_timeout();
492 }
493 $cache = $this->getCache();
494 $cachedData = $cache->get($this->hash);
495 if (!is_array($cachedData)) {
496 $this->generate();
497 $cache->set($this->hash, $this->result, ['ident_MENUDATA'], (int)$cacheTimeout);
498 } else {
499 $this->result = $cachedData;
500 }
501 // End showAccessRestrictedPages
502 if ($this->mconf['showAccessRestrictedPages']) {
503 // RESTORING where_groupAccess
504 $this->sys_page->where_groupAccess = $SAVED_where_groupAccess;
505 }
506 }
507
508 /**
509 * Generates the the menu data.
510 *
511 * Subclasses should overwrite this method.
512 */
513 public function generate()
514 {
515 }
516
517 /**
518 * @return string The HTML for the menu
519 */
520 public function writeMenu()
521 {
522 return '';
523 }
524
525 /**
526 * Gets an array of page rows and removes all, which are not accessible
527 *
528 * @param array $pages
529 * @return array
530 */
531 protected function removeInaccessiblePages(array $pages)
532 {
533 $banned = $this->getBannedUids();
534 $filteredPages = [];
535 foreach ($pages as $aPage) {
536 if ($this->filterMenuPages($aPage, $banned, $aPage['doktype'] === PageRepository::DOKTYPE_SPACER)) {
537 $filteredPages[$aPage['uid']] = $aPage;
538 }
539 }
540 return $filteredPages;
541 }
542
543 /**
544 * Main function for retrieving menu items based on the menu type (special or sectionIndex or "normal")
545 *
546 * @return array
547 */
548 protected function prepareMenuItems()
549 {
550 $menuItems = [];
551 $alternativeSortingField = trim($this->mconf['alternativeSortingField']) ?: 'sorting';
552
553 // Additional where clause, usually starts with AND (as usual with all additionalWhere functionality in TS)
554 $additionalWhere = isset($this->mconf['additionalWhere']) ? $this->mconf['additionalWhere'] : '';
555 if (isset($this->mconf['additionalWhere.'])) {
556 $additionalWhere = $this->parent_cObj->stdWrap($additionalWhere, $this->mconf['additionalWhere.']);
557 }
558
559 // ... only for the FIRST level of a HMENU
560 if ($this->menuNumber == 1 && $this->conf['special']) {
561 $value = isset($this->conf['special.']['value.'])
562 ? $this->parent_cObj->stdWrap($this->conf['special.']['value'], $this->conf['special.']['value.'])
563 : $this->conf['special.']['value'];
564 switch ($this->conf['special']) {
565 case 'userfunction':
566 $menuItems = $this->prepareMenuItemsForUserSpecificMenu($value, $alternativeSortingField);
567 break;
568 case 'language':
569 $menuItems = $this->prepareMenuItemsForLanguageMenu($value);
570 break;
571 case 'directory':
572 $menuItems = $this->prepareMenuItemsForDirectoryMenu($value, $alternativeSortingField);
573 break;
574 case 'list':
575 $menuItems = $this->prepareMenuItemsForListMenu($value);
576 break;
577 case 'updated':
578 $menuItems = $this->prepareMenuItemsForUpdatedMenu(
579 $value,
580 $this->mconf['alternativeSortingField'] ?: false
581 );
582 break;
583 case 'keywords':
584 $menuItems = $this->prepareMenuItemsForKeywordsMenu(
585 $value,
586 $this->mconf['alternativeSortingField'] ?: false
587 );
588 break;
589 case 'categories':
590 /** @var CategoryMenuUtility $categoryMenuUtility */
591 $categoryMenuUtility = GeneralUtility::makeInstance(CategoryMenuUtility::class);
592 $menuItems = $categoryMenuUtility->collectPages($value, $this->conf['special.'], $this);
593 break;
594 case 'rootline':
595 $menuItems = $this->prepareMenuItemsForRootlineMenu();
596 break;
597 case 'browse':
598 $menuItems = $this->prepareMenuItemsForBrowseMenu($value, $alternativeSortingField, $additionalWhere);
599 break;
600 }
601 if ($this->mconf['sectionIndex']) {
602 $sectionIndexes = [];
603 foreach ($menuItems as $page) {
604 $sectionIndexes = $sectionIndexes + $this->sectionIndex($alternativeSortingField, $page['uid']);
605 }
606 $menuItems = $sectionIndexes;
607 }
608 } elseif (is_array($this->alternativeMenuTempArray)) {
609 // Setting $menuItems array if not level 1.
610 $menuItems = $this->alternativeMenuTempArray;
611 } elseif ($this->mconf['sectionIndex']) {
612 $menuItems = $this->sectionIndex($alternativeSortingField);
613 } else {
614 // Default: Gets a hierarchical menu based on subpages of $this->id
615 $menuItems = $this->sys_page->getMenu($this->id, '*', $alternativeSortingField, $additionalWhere);
616 }
617 return $menuItems;
618 }
619
620 /**
621 * Fetches all menuitems if special = userfunction is set
622 *
623 * @param string $specialValue The value from special.value
624 * @param string $sortingField The sorting field
625 * @return array
626 */
627 protected function prepareMenuItemsForUserSpecificMenu($specialValue, $sortingField)
628 {
629 $menuItems = $this->parent_cObj->callUserFunction(
630 $this->conf['special.']['userFunc'],
631 array_merge($this->conf['special.'], ['value' => $specialValue, '_altSortField' => $sortingField]),
632 ''
633 );
634 if (!is_array($menuItems)) {
635 $menuItems = [];
636 }
637 return $menuItems;
638 }
639
640 /**
641 * Fetches all menuitems if special = language is set
642 *
643 * @param string $specialValue The value from special.value
644 * @return array
645 */
646 protected function prepareMenuItemsForLanguageMenu($specialValue)
647 {
648 $menuItems = [];
649 // Getting current page record NOT overlaid by any translation:
650 $tsfe = $this->getTypoScriptFrontendController();
651 $currentPageWithNoOverlay = $this->sys_page->getRawRecord('pages', $tsfe->page['uid']);
652 // Traverse languages set up:
653 $languageItems = GeneralUtility::intExplode(',', $specialValue);
654 foreach ($languageItems as $sUid) {
655 // Find overlay record:
656 if ($sUid) {
657 $lRecs = $this->sys_page->getPageOverlay($tsfe->page['uid'], $sUid);
658 } else {
659 $lRecs = [];
660 }
661 // Checking if the "disabled" state should be set.
662 if (GeneralUtility::hideIfNotTranslated($tsfe->page['l18n_cfg']) && $sUid &&
663 empty($lRecs) || GeneralUtility::hideIfDefaultLanguage($tsfe->page['l18n_cfg']) &&
664 (!$sUid || empty($lRecs)) ||
665 !$this->conf['special.']['normalWhenNoLanguage'] && $sUid && empty($lRecs)
666 ) {
667 $iState = $tsfe->sys_language_uid == $sUid ? 'USERDEF2' : 'USERDEF1';
668 } else {
669 $iState = $tsfe->sys_language_uid == $sUid ? 'ACT' : 'NO';
670 }
671 if ($this->conf['addQueryString']) {
672 $getVars = $this->parent_cObj->getQueryArguments(
673 $this->conf['addQueryString.'],
674 ['L' => $sUid],
675 true
676 );
677 $this->analyzeCacheHashRequirements($getVars);
678 } else {
679 $getVars = '&L=' . $sUid;
680 }
681 // Adding menu item:
682 $menuItems[] = array_merge(
683 array_merge($currentPageWithNoOverlay, $lRecs),
684 [
685 'ITEM_STATE' => $iState,
686 '_ADD_GETVARS' => $getVars,
687 '_SAFE' => true
688 ]
689 );
690 }
691 return $menuItems;
692 }
693
694 /**
695 * Fetches all menuitems if special = directory is set
696 *
697 * @param string $specialValue The value from special.value
698 * @param string $sortingField The sorting field
699 * @return array
700 */
701 protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField)
702 {
703 $tsfe = $this->getTypoScriptFrontendController();
704 $menuItems = [];
705 if ($specialValue == '') {
706 $specialValue = $tsfe->page['uid'];
707 }
708 $items = GeneralUtility::intExplode(',', $specialValue);
709 foreach ($items as $id) {
710 $MP = $this->tmpl->getFromMPmap($id);
711 // Checking if a page is a mount page and if so, change the ID and set the MP var properly.
712 $mount_info = $this->sys_page->getMountPointInfo($id);
713 if (is_array($mount_info)) {
714 if ($mount_info['overlay']) {
715 // Overlays should already have their full MPvars calculated:
716 $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
717 $MP = $MP ? $MP : $mount_info['MPvar'];
718 } else {
719 $MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
720 }
721 $id = $mount_info['mount_pid'];
722 }
723 // Get sub-pages:
724 $statement = $this->parent_cObj->exec_getQuery('pages', ['pidInList' => $id, 'orderBy' => $sortingField]);
725 while ($row = $statement->fetch()) {
726 $tsfe->sys_page->versionOL('pages', $row, true);
727 if (!empty($row)) {
728 // Keep mount point?
729 $mount_info = $this->sys_page->getMountPointInfo($row['uid'], $row);
730 // There is a valid mount point.
731 if (is_array($mount_info) && $mount_info['overlay']) {
732 // Using "getPage" is OK since we need the check for enableFields
733 // AND for type 2 of mount pids we DO require a doktype < 200!
734 $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
735 if (!empty($mp_row)) {
736 $row = $mp_row;
737 $row['_MP_PARAM'] = $mount_info['MPvar'];
738 } else {
739 // If the mount point could not be fetched with respect
740 // to enableFields, unset the row so it does not become a part of the menu!
741 unset($row);
742 }
743 }
744 // Add external MP params, then the row:
745 if (!empty($row)) {
746 if ($MP) {
747 $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
748 }
749 $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
750 }
751 }
752 }
753 }
754
755 return $menuItems;
756 }
757
758 /**
759 * Fetches all menuitems if special = list is set
760 *
761 * @param string $specialValue The value from special.value
762 * @return array
763 */
764 protected function prepareMenuItemsForListMenu($specialValue)
765 {
766 $menuItems = [];
767 if ($specialValue == '') {
768 $specialValue = $this->id;
769 }
770 $skippedEnableFields = [];
771 if (!empty($this->mconf['showAccessRestrictedPages'])) {
772 $skippedEnableFields = ['fe_group' => 1];
773 }
774 /** @var RelationHandler $loadDB*/
775 $loadDB = GeneralUtility::makeInstance(RelationHandler::class);
776 $loadDB->setFetchAllFields(true);
777 $loadDB->start($specialValue, 'pages');
778 $loadDB->additionalWhere['pages'] = $this->parent_cObj->enableFields('pages', false, $skippedEnableFields);
779 $loadDB->getFromDB();
780 foreach ($loadDB->itemArray as $val) {
781 $MP = $this->tmpl->getFromMPmap($val['id']);
782 // Keep mount point?
783 $mount_info = $this->sys_page->getMountPointInfo($val['id']);
784 // There is a valid mount point.
785 if (is_array($mount_info) && $mount_info['overlay']) {
786 // Using "getPage" is OK since we need the check for enableFields
787 // AND for type 2 of mount pids we DO require a doktype < 200!
788 $mp_row = $this->sys_page->getPage($mount_info['mount_pid']);
789 if (!empty($mp_row)) {
790 $row = $mp_row;
791 $row['_MP_PARAM'] = $mount_info['MPvar'];
792 // Overlays should already have their full MPvars calculated
793 if ($mount_info['overlay']) {
794 $MP = $this->tmpl->getFromMPmap($mount_info['mount_pid']);
795 if ($MP) {
796 unset($row['_MP_PARAM']);
797 }
798 }
799 } else {
800 // If the mount point could not be fetched with respect to
801 // enableFields, unset the row so it does not become a part of the menu!
802 unset($row);
803 }
804 } else {
805 $row = $loadDB->results['pages'][$val['id']];
806 }
807 // Add versioning overlay for current page (to respect workspaces)
808 if (isset($row) && is_array($row)) {
809 $this->sys_page->versionOL('pages', $row, true);
810 }
811 // Add external MP params, then the row:
812 if (isset($row) && is_array($row)) {
813 if ($MP) {
814 $row['_MP_PARAM'] = $MP . ($row['_MP_PARAM'] ? ',' . $row['_MP_PARAM'] : '');
815 }
816 $menuItems[] = $this->sys_page->getPageOverlay($row);
817 }
818 }
819 return $menuItems;
820 }
821
822 /**
823 * Fetches all menuitems if special = updated is set
824 *
825 * @param string $specialValue The value from special.value
826 * @param string $sortingField The sorting field
827 * @return array
828 */
829 protected function prepareMenuItemsForUpdatedMenu($specialValue, $sortingField)
830 {
831 $tsfe = $this->getTypoScriptFrontendController();
832 $menuItems = [];
833 if ($specialValue == '') {
834 $specialValue = $tsfe->page['uid'];
835 }
836 $items = GeneralUtility::intExplode(',', $specialValue);
837 if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
838 $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 1, 20);
839 } else {
840 $depth = 20;
841 }
842 // Max number of items
843 $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
844 $maxAge = (int)$this->parent_cObj->calc($this->conf['special.']['maxAge']);
845 if (!$limit) {
846 $limit = 10;
847 }
848 // *'auto', 'manual', 'tstamp'
849 $mode = $this->conf['special.']['mode'];
850 // Get id's
851 $id_list_arr = [];
852 foreach ($items as $id) {
853 $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
854 $id_list_arr[] = $this->parent_cObj->getTreeList(-1 * $id, $depth - 1 + $bA, $bA - 1);
855 }
856 $id_list = implode(',', $id_list_arr);
857 // Get sortField (mode)
858 switch ($mode) {
859 case 'starttime':
860 $sortField = 'starttime';
861 break;
862 case 'lastUpdated':
863 case 'manual':
864 $sortField = 'lastUpdated';
865 break;
866 case 'tstamp':
867 $sortField = 'tstamp';
868 break;
869 case 'crdate':
870 $sortField = 'crdate';
871 break;
872 default:
873 $sortField = 'SYS_LASTCHANGED';
874 }
875 $extraWhere = ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
876 if ($this->conf['special.']['excludeNoSearchPages']) {
877 $extraWhere .= ' AND pages.no_search=0';
878 }
879 if ($maxAge > 0) {
880 $extraWhere .= ' AND ' . $sortField . '>' . ($GLOBALS['SIM_ACCESS_TIME'] - $maxAge);
881 }
882 $statement = $this->parent_cObj->exec_getQuery('pages', [
883 'pidInList' => '0',
884 'uidInList' => $id_list,
885 'where' => $sortField . '>=0' . $extraWhere,
886 'orderBy' => $sortingField ?: $sortField . ' DESC',
887 'max' => $limit
888 ]);
889 while ($row = $statement->fetch()) {
890 $tsfe->sys_page->versionOL('pages', $row, true);
891 if (is_array($row)) {
892 $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
893 }
894 }
895
896 return $menuItems;
897 }
898
899 /**
900 * Fetches all menuitems if special = keywords is set
901 *
902 * @param string $specialValue The value from special.value
903 * @param string $sortingField The sorting field
904 * @return array
905 */
906 protected function prepareMenuItemsForKeywordsMenu($specialValue, $sortingField)
907 {
908 $tsfe = $this->getTypoScriptFrontendController();
909 $menuItems = [];
910 list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
911 if (!$specialValue) {
912 $specialValue = $tsfe->page['uid'];
913 }
914 if ($this->conf['special.']['setKeywords'] || $this->conf['special.']['setKeywords.']) {
915 $kw = isset($this->conf['special.']['setKeywords.']) ? $this->parent_cObj->stdWrap($this->conf['special.']['setKeywords'], $this->conf['special.']['setKeywords.']) : $this->conf['special.']['setKeywords'];
916 } else {
917 // The page record of the 'value'.
918 $value_rec = $this->sys_page->getPage($specialValue);
919 $kfieldSrc = $this->conf['special.']['keywordsField.']['sourceField'] ? $this->conf['special.']['keywordsField.']['sourceField'] : 'keywords';
920 // keywords.
921 $kw = trim($this->parent_cObj->keywords($value_rec[$kfieldSrc]));
922 }
923 // *'auto', 'manual', 'tstamp'
924 $mode = $this->conf['special.']['mode'];
925 switch ($mode) {
926 case 'starttime':
927 $sortField = 'starttime';
928 break;
929 case 'lastUpdated':
930 case 'manual':
931 $sortField = 'lastUpdated';
932 break;
933 case 'tstamp':
934 $sortField = 'tstamp';
935 break;
936 case 'crdate':
937 $sortField = 'crdate';
938 break;
939 default:
940 $sortField = 'SYS_LASTCHANGED';
941 }
942 // Depth, limit, extra where
943 if (MathUtility::canBeInterpretedAsInteger($this->conf['special.']['depth'])) {
944 $depth = MathUtility::forceIntegerInRange($this->conf['special.']['depth'], 0, 20);
945 } else {
946 $depth = 20;
947 }
948 // Max number of items
949 $limit = MathUtility::forceIntegerInRange($this->conf['special.']['limit'], 0, 100);
950 // Start point
951 $eLevel = $this->parent_cObj->getKey(isset($this->conf['special.']['entryLevel.'])
952 ? $this->parent_cObj->stdWrap($this->conf['special.']['entryLevel'], $this->conf['special.']['entryLevel.'])
953 : $this->conf['special.']['entryLevel'], $this->tmpl->rootLine
954 );
955 $startUid = (int)$this->tmpl->rootLine[$eLevel]['uid'];
956 // Which field is for keywords
957 $kfield = 'keywords';
958 if ($this->conf['special.']['keywordsField']) {
959 list($kfield) = explode(' ', trim($this->conf['special.']['keywordsField']));
960 }
961 // If there are keywords and the startuid is present
962 if ($kw && $startUid) {
963 $bA = MathUtility::forceIntegerInRange($this->conf['special.']['beginAtLevel'], 0, 100);
964 $id_list = $this->parent_cObj->getTreeList(-1 * $startUid, $depth - 1 + $bA, $bA - 1);
965 $kwArr = GeneralUtility::trimExplode(',', $kw, true);
966 $keyWordsWhereArr = [];
967 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
968 foreach ($kwArr as $word) {
969 $keyWordsWhereArr[] = $queryBuilder->expr()->like(
970 $kfield,
971 $queryBuilder->createNamedParameter(
972 '%' . $queryBuilder->escapeLikeWildcards($word) . '%',
973 \PDO::PARAM_STR
974 )
975 );
976 }
977 $queryBuilder
978 ->select('*')
979 ->from('pages')
980 ->where(
981 $queryBuilder->expr()->in(
982 'uid',
983 GeneralUtility::intExplode(',', $id_list, true)
984 ),
985 $queryBuilder->expr()->neq(
986 'uid',
987 $queryBuilder->createNamedParameter($specialValue, \PDO::PARAM_INT)
988 )
989 );
990
991 if (count($keyWordsWhereArr) !== 0) {
992 $queryBuilder->andWhere($queryBuilder->expr()->orX(...$keyWordsWhereArr));
993 }
994
995 if ($this->doktypeExcludeList) {
996 $queryBuilder->andWhere(
997 $queryBuilder->expr()->notIn(
998 'pages.doktype',
999 GeneralUtility::intExplode(',', $this->doktypeExcludeList, true)
1000 )
1001 );
1002 }
1003
1004 if (!$this->conf['includeNotInMenu']) {
1005 $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.nav_hide', 0));
1006 }
1007
1008 if ($this->conf['special.']['excludeNoSearchPages']) {
1009 $queryBuilder->andWhere($queryBuilder->expr()->eq('pages.no_search', 0));
1010 }
1011
1012 if ($limit > 0) {
1013 $queryBuilder->setMaxResults($limit);
1014 }
1015
1016 if ($sortingField) {
1017 $queryBuilder->orderBy($sortingField);
1018 } else {
1019 $queryBuilder->orderBy($sortField, 'desc');
1020 }
1021
1022 $result = $queryBuilder->execute();
1023 while ($row = $result->fetch()) {
1024 $tsfe->sys_page->versionOL('pages', $row, true);
1025 if (is_array($row)) {
1026 $menuItems[$row['uid']] = $this->sys_page->getPageOverlay($row);
1027 }
1028 }
1029 }
1030
1031 return $menuItems;
1032 }
1033
1034 /**
1035 * Fetches all menuitems if special = rootline is set
1036 *
1037 * @return array
1038 */
1039 protected function prepareMenuItemsForRootlineMenu()
1040 {
1041 $menuItems = [];
1042 $range = isset($this->conf['special.']['range.'])
1043 ? $this->parent_cObj->stdWrap($this->conf['special.']['range'], $this->conf['special.']['range.'])
1044 : $this->conf['special.']['range'];
1045 $begin_end = explode('|', $range);
1046 $begin_end[0] = (int)$begin_end[0];
1047 if (!MathUtility::canBeInterpretedAsInteger($begin_end[1])) {
1048 $begin_end[1] = -1;
1049 }
1050 $beginKey = $this->parent_cObj->getKey($begin_end[0], $this->tmpl->rootLine);
1051 $endKey = $this->parent_cObj->getKey($begin_end[1], $this->tmpl->rootLine);
1052 if ($endKey < $beginKey) {
1053 $endKey = $beginKey;
1054 }
1055 $rl_MParray = [];
1056 foreach ($this->tmpl->rootLine as $k_rl => $v_rl) {
1057 // For overlaid mount points, set the variable right now:
1058 if ($v_rl['_MP_PARAM'] && $v_rl['_MOUNT_OL']) {
1059 $rl_MParray[] = $v_rl['_MP_PARAM'];
1060 }
1061 // Traverse rootline:
1062 if ($k_rl >= $beginKey && $k_rl <= $endKey) {
1063 $temp_key = $k_rl;
1064 $menuItems[$temp_key] = $this->sys_page->getPage($v_rl['uid']);
1065 if (!empty($menuItems[$temp_key])) {
1066 // If there are no specific target for the page, put the level specific target on.
1067 if (!$menuItems[$temp_key]['target']) {
1068 $menuItems[$temp_key]['target'] = $this->conf['special.']['targets.'][$k_rl];
1069 $menuItems[$temp_key]['_MP_PARAM'] = implode(',', $rl_MParray);
1070 }
1071 } else {
1072 unset($menuItems[$temp_key]);
1073 }
1074 }
1075 // For normal mount points, set the variable for next level.
1076 if ($v_rl['_MP_PARAM'] && !$v_rl['_MOUNT_OL']) {
1077 $rl_MParray[] = $v_rl['_MP_PARAM'];
1078 }
1079 }
1080 // Reverse order of elements (e.g. "1,2,3,4" gets "4,3,2,1"):
1081 if (isset($this->conf['special.']['reverseOrder']) && $this->conf['special.']['reverseOrder']) {
1082 $menuItems = array_reverse($menuItems);
1083 }
1084 return $menuItems;
1085 }
1086
1087 /**
1088 * Fetches all menuitems if special = browse is set
1089 *
1090 * @param string $specialValue The value from special.value
1091 * @param string $sortingField The sorting field
1092 * @param string $additionalWhere Additional WHERE clause
1093 * @return array
1094 */
1095 protected function prepareMenuItemsForBrowseMenu($specialValue, $sortingField, $additionalWhere)
1096 {
1097 $menuItems = [];
1098 list($specialValue) = GeneralUtility::intExplode(',', $specialValue);
1099 if (!$specialValue) {
1100 $specialValue = $this->getTypoScriptFrontendController()->page['uid'];
1101 }
1102 // Will not work out of rootline
1103 if ($specialValue != $this->tmpl->rootLine[0]['uid']) {
1104 $recArr = [];
1105 // The page record of the 'value'.
1106 $value_rec = $this->sys_page->getPage($specialValue);
1107 // 'up' page cannot be outside rootline
1108 if ($value_rec['pid']) {
1109 // The page record of 'up'.
1110 $recArr['up'] = $this->sys_page->getPage($value_rec['pid']);
1111 }
1112 // If the 'up' item was NOT level 0 in rootline...
1113 if ($recArr['up']['pid'] && $value_rec['pid'] != $this->tmpl->rootLine[0]['uid']) {
1114 // The page record of "index".
1115 $recArr['index'] = $this->sys_page->getPage($recArr['up']['pid']);
1116 }
1117 // check if certain pages should be excluded
1118 $additionalWhere .= ($this->conf['includeNotInMenu'] ? '' : ' AND pages.nav_hide=0') . $this->getDoktypeExcludeWhere();
1119 if ($this->conf['special.']['excludeNoSearchPages']) {
1120 $additionalWhere .= ' AND pages.no_search=0';
1121 }
1122 // prev / next is found
1123 $prevnext_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($value_rec['pid'], '*', $sortingField, $additionalWhere));
1124 $lastKey = 0;
1125 $nextActive = 0;
1126 foreach ($prevnext_menu as $k_b => $v_b) {
1127 if ($nextActive) {
1128 $recArr['next'] = $v_b;
1129 $nextActive = 0;
1130 }
1131 if ($v_b['uid'] == $specialValue) {
1132 if ($lastKey) {
1133 $recArr['prev'] = $prevnext_menu[$lastKey];
1134 }
1135 $nextActive = 1;
1136 }
1137 $lastKey = $k_b;
1138 }
1139
1140 $recArr['first'] = reset($prevnext_menu);
1141 $recArr['last'] = end($prevnext_menu);
1142 // prevsection / nextsection is found
1143 // You can only do this, if there is a valid page two levels up!
1144 if (!empty($recArr['index']['uid'])) {
1145 $prevnextsection_menu = $this->removeInaccessiblePages($this->sys_page->getMenu($recArr['index']['uid'], '*', $sortingField, $additionalWhere));
1146 $lastKey = 0;
1147 $nextActive = 0;
1148 foreach ($prevnextsection_menu as $k_b => $v_b) {
1149 if ($nextActive) {
1150 $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($v_b['uid'], '*', $sortingField, $additionalWhere));
1151 if (!empty($sectionRec_temp)) {
1152 $recArr['nextsection'] = reset($sectionRec_temp);
1153 $recArr['nextsection_last'] = end($sectionRec_temp);
1154 $nextActive = 0;
1155 }
1156 }
1157 if ($v_b['uid'] == $value_rec['pid']) {
1158 if ($lastKey) {
1159 $sectionRec_temp = $this->removeInaccessiblePages($this->sys_page->getMenu($prevnextsection_menu[$lastKey]['uid'], '*', $sortingField, $additionalWhere));
1160 if (!empty($sectionRec_temp)) {
1161 $recArr['prevsection'] = reset($sectionRec_temp);
1162 $recArr['prevsection_last'] = end($sectionRec_temp);
1163 }
1164 }
1165 $nextActive = 1;
1166 }
1167 $lastKey = $k_b;
1168 }
1169 }
1170 if ($this->conf['special.']['items.']['prevnextToSection']) {
1171 if (!is_array($recArr['prev']) && is_array($recArr['prevsection_last'])) {
1172 $recArr['prev'] = $recArr['prevsection_last'];
1173 }
1174 if (!is_array($recArr['next']) && is_array($recArr['nextsection'])) {
1175 $recArr['next'] = $recArr['nextsection'];
1176 }
1177 }
1178 $items = explode('|', $this->conf['special.']['items']);
1179 $c = 0;
1180 foreach ($items as $k_b => $v_b) {
1181 $v_b = strtolower(trim($v_b));
1182 if ((int)$this->conf['special.'][$v_b . '.']['uid']) {
1183 $recArr[$v_b] = $this->sys_page->getPage((int)$this->conf['special.'][$v_b . '.']['uid']);
1184 }
1185 if (is_array($recArr[$v_b])) {
1186 $menuItems[$c] = $recArr[$v_b];
1187 if ($this->conf['special.'][$v_b . '.']['target']) {
1188 $menuItems[$c]['target'] = $this->conf['special.'][$v_b . '.']['target'];
1189 }
1190 $tmpSpecialFields = $this->conf['special.'][$v_b . '.']['fields.'];
1191 if (is_array($tmpSpecialFields)) {
1192 foreach ($tmpSpecialFields as $fk => $val) {
1193 $menuItems[$c][$fk] = $val;
1194 }
1195 }
1196 $c++;
1197 }
1198 }
1199 }
1200 return $menuItems;
1201 }
1202
1203 /**
1204 * Analyzes the parameters to find if the link needs a cHash parameter.
1205 *
1206 * @param string $queryString
1207 */
1208 protected function analyzeCacheHashRequirements($queryString)
1209 {
1210 $parameters = GeneralUtility::explodeUrl2Array($queryString);
1211 if (!empty($parameters)) {
1212 if (!isset($parameters['id'])) {
1213 $queryString .= '&id=' . $this->getTypoScriptFrontendController()->id;
1214 }
1215 /** @var CacheHashCalculator $cacheHashCalculator */
1216 $cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
1217 $cHashParameters = $cacheHashCalculator->getRelevantParameters($queryString);
1218 if (count($cHashParameters) > 1) {
1219 $this->useCacheHash = (
1220 $GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ||
1221 !isset($parameters['no_cache']) ||
1222 !$parameters['no_cache']
1223 );
1224 }
1225 }
1226 }
1227
1228 /**
1229 * Checks if a page is OK to include in the final menu item array. Pages can be excluded if the doktype is wrong,
1230 * if they are hidden in navigation, have a uid in the list of banned uids etc.
1231 *
1232 * @param array $data Array of menu items
1233 * @param array $banUidArray Array of page uids which are to be excluded
1234 * @param bool $spacer If set, then the page is a spacer.
1235 * @return bool Returns TRUE if the page can be safely included.
1236 *
1237 * @throws \UnexpectedValueException
1238 */
1239 public function filterMenuPages(&$data, $banUidArray, $spacer)
1240 {
1241 $includePage = true;
1242 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'])) {
1243 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] as $className) {
1244 $hookObject = GeneralUtility::makeInstance($className);
1245 if (!$hookObject instanceof AbstractMenuFilterPagesHookInterface) {
1246 throw new \UnexpectedValueException($className . ' must implement interface ' . AbstractMenuFilterPagesHookInterface::class, 1269877402);
1247 }
1248 $includePage = $includePage && $hookObject->processFilter($data, $banUidArray, $spacer, $this);
1249 }
1250 }
1251 if (!$includePage) {
1252 return false;
1253 }
1254 if ($data['_SAFE']) {
1255 return true;
1256 }
1257
1258 if (
1259 ($this->mconf['SPC'] || !$spacer) // If the spacer-function is not enabled, spacers will not enter the $menuArr
1260 && (!$data['nav_hide'] || $this->conf['includeNotInMenu']) // Not hidden in navigation
1261 && !GeneralUtility::inList($this->doktypeExcludeList, $data['doktype']) // Page may not be 'not_in_menu' or 'Backend User Section'
1262 && !in_array($data['uid'], $banUidArray, false) // not in banned uid's
1263 ) {
1264 // Checks if the default language version can be shown:
1265 // Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
1266 $tsfe = $this->getTypoScriptFrontendController();
1267 $blockPage = GeneralUtility::hideIfDefaultLanguage($data['l18n_cfg']) && (!$tsfe->sys_language_uid || $tsfe->sys_language_uid && !$data['_PAGES_OVERLAY']);
1268 if (!$blockPage) {
1269 // Checking if a page should be shown in the menu depending on whether a translation exists:
1270 $tok = true;
1271 // There is an alternative language active AND the current page requires a translation:
1272 if ($tsfe->sys_language_uid && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
1273 if (!$data['_PAGES_OVERLAY']) {
1274 $tok = false;
1275 }
1276 }
1277 // Continue if token is TRUE:
1278 if ($tok) {
1279 // Checking if "&L" should be modified so links to non-accessible pages will not happen.
1280 if ($this->conf['protectLvar']) {
1281 $languageUid = (int)$tsfe->config['config']['sys_language_uid'];
1282 if ($languageUid && ($this->conf['protectLvar'] === 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg']))) {
1283 $olRec = $tsfe->sys_page->getPageOverlay($data['uid'], $languageUid);
1284 if (empty($olRec)) {
1285 // If no pages_language_overlay record then page can NOT be accessed in
1286 // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
1287 $data['_ADD_GETVARS'] .= '&L=0';
1288 }
1289 }
1290 }
1291 return true;
1292 }
1293 }
1294 }
1295 return false;
1296 }
1297
1298 /**
1299 * Generating the per-menu-item configuration arrays based on the settings for item states (NO, RO, ACT, CUR etc)
1300 * set in ->mconf (config for the current menu object)
1301 * Basically it will produce an individual array for each menu item based on the item states.
1302 * BUT in addition the "optionSplit" syntax for the values is ALSO evaluated here so that all property-values
1303 * are "option-splitted" and the output will thus be resolved.
1304 * Is called from the "generate" functions in the extension classes. The function is processor intensive due to
1305 * the option split feature in particular. But since the generate function is not always called
1306 * (since the ->result array may be cached, see makeMenu) it doesn't hurt so badly.
1307 *
1308 * @param int $splitCount Number of menu items in the menu
1309 * @return array An array with two keys: array($NOconf,$ROconf) - where $NOconf contains the resolved configuration for each item when NOT rolled-over and $ROconf contains the ditto for the mouseover state (if any)
1310 *
1311 * @internal
1312 */
1313 public function procesItemStates($splitCount)
1314 {
1315 // Prepare normal settings
1316 if (!is_array($this->mconf['NO.']) && $this->mconf['NO']) {
1317 // Setting a blank array if NO=1 and there are no properties.
1318 $this->mconf['NO.'] = [];
1319 }
1320 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
1321 $NOconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['NO.'], $splitCount);
1322 // Prepare rollOver settings, overriding normal settings
1323 $ROconf = [];
1324 if ($this->mconf['RO']) {
1325 $ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['RO.'], $splitCount);
1326 }
1327 // Prepare IFSUB settings, overriding normal settings
1328 // IFSUB is TRUE if there exist submenu items to the current item
1329 if (!empty($this->mconf['IFSUB'])) {
1330 $IFSUBconf = null;
1331 $IFSUBROconf = null;
1332 foreach ($NOconf as $key => $val) {
1333 if ($this->isItemState('IFSUB', $key)) {
1334 // if this is the first IFSUB element, we must generate IFSUB.
1335 if ($IFSUBconf === null) {
1336 $IFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['IFSUB.'], $splitCount);
1337 if (!empty($this->mconf['IFSUBRO'])) {
1338 $IFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['IFSUBRO.'], $splitCount);
1339 }
1340 }
1341 // Substitute normal with ifsub
1342 if (isset($IFSUBconf[$key])) {
1343 $NOconf[$key] = $IFSUBconf[$key];
1344 }
1345 // If rollOver on normal, we must apply a state for rollOver on the active
1346 if ($ROconf) {
1347 // If RollOver on active then apply this
1348 $ROconf[$key] = !empty($IFSUBROconf[$key]) ? $IFSUBROconf[$key] : $IFSUBconf[$key];
1349 }
1350 }
1351 }
1352 }
1353 // Prepare active settings, overriding normal settings
1354 if (!empty($this->mconf['ACT'])) {
1355 $ACTconf = null;
1356 $ACTROconf = null;
1357 // Find active
1358 foreach ($NOconf as $key => $val) {
1359 if ($this->isItemState('ACT', $key)) {
1360 // If this is the first 'active', we must generate ACT.
1361 if ($ACTconf === null) {
1362 $ACTconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACT.'], $splitCount);
1363 // Prepare active rollOver settings, overriding normal active settings
1364 if (!empty($this->mconf['ACTRO'])) {
1365 $ACTROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTRO.'], $splitCount);
1366 }
1367 }
1368 // Substitute normal with active
1369 if (isset($ACTconf[$key])) {
1370 $NOconf[$key] = $ACTconf[$key];
1371 }
1372 // If rollOver on normal, we must apply a state for rollOver on the active
1373 if ($ROconf) {
1374 // If RollOver on active then apply this
1375 $ROconf[$key] = !empty($ACTROconf[$key]) ? $ACTROconf[$key] : $ACTconf[$key];
1376 }
1377 }
1378 }
1379 }
1380 // Prepare ACT (active)/IFSUB settings, overriding normal settings
1381 // ACTIFSUB is TRUE if there exist submenu items to the current item and the current item is active
1382 if (!empty($this->mconf['ACTIFSUB'])) {
1383 $ACTIFSUBconf = null;
1384 $ACTIFSUBROconf = null;
1385 // Find active
1386 foreach ($NOconf as $key => $val) {
1387 if ($this->isItemState('ACTIFSUB', $key)) {
1388 // If this is the first 'active', we must generate ACTIFSUB.
1389 if ($ACTIFSUBconf === null) {
1390 $ACTIFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTIFSUB.'], $splitCount);
1391 // Prepare active rollOver settings, overriding normal active settings
1392 if (!empty($this->mconf['ACTIFSUBRO'])) {
1393 $ACTIFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['ACTIFSUBRO.'], $splitCount);
1394 }
1395 }
1396 // Substitute normal with active
1397 if (isset($ACTIFSUBconf[$key])) {
1398 $NOconf[$key] = $ACTIFSUBconf[$key];
1399 }
1400 // If rollOver on normal, we must apply a state for rollOver on the active
1401 if ($ROconf) {
1402 // If RollOver on active then apply this
1403 $ROconf[$key] = !empty($ACTIFSUBROconf[$key]) ? $ACTIFSUBROconf[$key] : $ACTIFSUBconf[$key];
1404 }
1405 }
1406 }
1407 }
1408 // Prepare CUR (current) settings, overriding normal settings
1409 // CUR is TRUE if the current page equals the item here!
1410 if (!empty($this->mconf['CUR'])) {
1411 $CURconf = null;
1412 $CURROconf = null;
1413 foreach ($NOconf as $key => $val) {
1414 if ($this->isItemState('CUR', $key)) {
1415 // if this is the first 'current', we must generate CUR. Basically this control is just inherited
1416 // from the other implementations as current would only exist one time and that's it
1417 // (unless you use special-features of HMENU)
1418 if ($CURconf === null) {
1419 $CURconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CUR.'], $splitCount);
1420 if (!empty($this->mconf['CURRO'])) {
1421 $CURROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURRO.'], $splitCount);
1422 }
1423 }
1424 // Substitute normal with current
1425 if (isset($CURconf[$key])) {
1426 $NOconf[$key] = $CURconf[$key];
1427 }
1428 // If rollOver on normal, we must apply a state for rollOver on the active
1429 if ($ROconf) {
1430 // If RollOver on active then apply this
1431 $ROconf[$key] = !empty($CURROconf[$key]) ? $CURROconf[$key] : $CURconf[$key];
1432 }
1433 }
1434 }
1435 }
1436 // Prepare CUR (current)/IFSUB settings, overriding normal settings
1437 // CURIFSUB is TRUE if there exist submenu items to the current item and the current page equals the item here!
1438 if (!empty($this->mconf['CURIFSUB'])) {
1439 $CURIFSUBconf = null;
1440 $CURIFSUBROconf = null;
1441 foreach ($NOconf as $key => $val) {
1442 if ($this->isItemState('CURIFSUB', $key)) {
1443 // If this is the first 'current', we must generate CURIFSUB.
1444 if ($CURIFSUBconf === null) {
1445 $CURIFSUBconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURIFSUB.'], $splitCount);
1446 // Prepare current rollOver settings, overriding normal current settings
1447 if (!empty($this->mconf['CURIFSUBRO'])) {
1448 $CURIFSUBROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['CURIFSUBRO.'], $splitCount);
1449 }
1450 }
1451 // Substitute normal with active
1452 if ($CURIFSUBconf[$key]) {
1453 $NOconf[$key] = $CURIFSUBconf[$key];
1454 }
1455 // If rollOver on normal, we must apply a state for rollOver on the current
1456 if ($ROconf) {
1457 // If RollOver on current then apply this
1458 $ROconf[$key] = !empty($CURIFSUBROconf[$key]) ? $CURIFSUBROconf[$key] : $CURIFSUBconf[$key];
1459 }
1460 }
1461 }
1462 }
1463 // Prepare active settings, overriding normal settings
1464 if (!empty($this->mconf['USR'])) {
1465 $USRconf = null;
1466 $USRROconf = null;
1467 // Find active
1468 foreach ($NOconf as $key => $val) {
1469 if ($this->isItemState('USR', $key)) {
1470 // if this is the first active, we must generate USR.
1471 if ($USRconf === null) {
1472 $USRconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USR.'], $splitCount);
1473 // Prepare active rollOver settings, overriding normal active settings
1474 if (!empty($this->mconf['USRRO'])) {
1475 $USRROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USRRO.'], $splitCount);
1476 }
1477 }
1478 // Substitute normal with active
1479 if ($USRconf[$key]) {
1480 $NOconf[$key] = $USRconf[$key];
1481 }
1482 // If rollOver on normal, we must apply a state for rollOver on the active
1483 if ($ROconf) {
1484 // If RollOver on active then apply this
1485 $ROconf[$key] = !empty($USRROconf[$key]) ? $USRROconf[$key] : $USRconf[$key];
1486 }
1487 }
1488 }
1489 }
1490 // Prepare spacer settings, overriding normal settings
1491 if (!empty($this->mconf['SPC'])) {
1492 $SPCconf = null;
1493 // Find spacers
1494 foreach ($NOconf as $key => $val) {
1495 if ($this->isItemState('SPC', $key)) {
1496 // If this is the first spacer, we must generate SPC.
1497 if ($SPCconf === null) {
1498 $SPCconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['SPC.'], $splitCount);
1499 }
1500 // Substitute normal with spacer
1501 if (isset($SPCconf[$key])) {
1502 $NOconf[$key] = $SPCconf[$key];
1503 }
1504 }
1505 }
1506 }
1507 // Prepare Userdefined settings
1508 if (!empty($this->mconf['USERDEF1'])) {
1509 $USERDEF1conf = null;
1510 $USERDEF1ROconf = null;
1511 // Find active
1512 foreach ($NOconf as $key => $val) {
1513 if ($this->isItemState('USERDEF1', $key)) {
1514 // If this is the first active, we must generate USERDEF1.
1515 if ($USERDEF1conf === null) {
1516 $USERDEF1conf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF1.'], $splitCount);
1517 // Prepare active rollOver settings, overriding normal active settings
1518 if (!empty($this->mconf['USERDEF1RO'])) {
1519 $USERDEF1ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF1RO.'], $splitCount);
1520 }
1521 }
1522 // Substitute normal with active
1523 if (isset($USERDEF1conf[$key])) {
1524 $NOconf[$key] = $USERDEF1conf[$key];
1525 }
1526 // If rollOver on normal, we must apply a state for rollOver on the active
1527 if ($ROconf) {
1528 // If RollOver on active then apply this
1529 $ROconf[$key] = !empty($USERDEF1ROconf[$key]) ? $USERDEF1ROconf[$key] : $USERDEF1conf[$key];
1530 }
1531 }
1532 }
1533 }
1534 // Prepare Userdefined settings
1535 if (!empty($this->mconf['USERDEF2'])) {
1536 $USERDEF2conf = null;
1537 $USERDEF2ROconf = null;
1538 // Find active
1539 foreach ($NOconf as $key => $val) {
1540 if ($this->isItemState('USERDEF2', $key)) {
1541 // If this is the first active, we must generate USERDEF2.
1542 if ($USERDEF2conf === null) {
1543 $USERDEF2conf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF2.'], $splitCount);
1544 // Prepare active rollOver settings, overriding normal active settings
1545 if (!empty($this->mconf['USERDEF2RO'])) {
1546 $USERDEF2ROconf = $typoScriptService->explodeConfigurationForOptionSplit((array)$this->mconf['USERDEF2RO.'], $splitCount);
1547 }
1548 }
1549 // Substitute normal with active
1550 if (isset($USERDEF2conf[$key])) {
1551 $NOconf[$key] = $USERDEF2conf[$key];
1552 }
1553 // If rollOver on normal, we must apply a state for rollOver on the active
1554 if ($ROconf) {
1555 // If RollOver on active then apply this
1556 $ROconf[$key] = !empty($USERDEF2ROconf[$key]) ? $USERDEF2ROconf[$key] : $USERDEF2conf[$key];
1557 }
1558 }
1559 }
1560 }
1561 return [$NOconf, $ROconf];
1562 }
1563
1564 /**
1565 * Creates the URL, target and onclick values for the menu item link. Returns them in an array as key/value pairs for <A>-tag attributes
1566 * This function doesn't care about the url, because if we let the url be redirected, it will be logged in the stat!!!
1567 *
1568 * @param int $key Pointer to a key in the $this->menuArr array where the value for that key represents the menu item we are linking to (page record)
1569 * @param string $altTarget Alternative target
1570 * @param string $typeOverride Alternative type
1571 * @return array Returns an array with A-tag attributes as key/value pairs (HREF, TARGET and onClick)
1572 * @internal
1573 */
1574 public function link($key, $altTarget = '', $typeOverride = '')
1575 {
1576 $runtimeCache = $this->getRuntimeCache();
1577 $cacheId = 'menu-generated-links-' . md5($key . $altTarget . $typeOverride . serialize($this->menuArr[$key]));
1578 $runtimeCachedLink = $runtimeCache->get($cacheId);
1579 if ($runtimeCachedLink !== false) {
1580 return $runtimeCachedLink;
1581 }
1582
1583 // Mount points:
1584 $MP_var = $this->getMPvar($key);
1585 $MP_params = $MP_var ? '&MP=' . rawurlencode($MP_var) : '';
1586 // Setting override ID
1587 if ($this->mconf['overrideId'] || $this->menuArr[$key]['overrideId']) {
1588 $overrideArray = [];
1589 // If a user script returned the value overrideId in the menu array we use that as page id
1590 $overrideArray['uid'] = $this->mconf['overrideId'] ?: $this->menuArr[$key]['overrideId'];
1591 $overrideArray['alias'] = '';
1592 // Clear MP parameters since ID was changed.
1593 $MP_params = '';
1594 } else {
1595 $overrideArray = '';
1596 }
1597 // Setting main target:
1598 if ($altTarget) {
1599 $mainTarget = $altTarget;
1600 } elseif ($this->mconf['target.']) {
1601 $mainTarget = $this->parent_cObj->stdWrap($this->mconf['target'], $this->mconf['target.']);
1602 } else {
1603 $mainTarget = $this->mconf['target'];
1604 }
1605 // Creating link:
1606 $addParams = $this->mconf['addParams'] . $MP_params;
1607 if ($this->mconf['collapse'] && $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key))) {
1608 $thePage = $this->sys_page->getPage($this->menuArr[$key]['pid']);
1609 $addParams .= $this->menuArr[$key]['_ADD_GETVARS'];
1610 $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1611 } else {
1612 $addParams .= $this->I['val']['additionalParams'] . $this->menuArr[$key]['_ADD_GETVARS'];
1613 $LD = $this->menuTypoLink($this->menuArr[$key], $mainTarget, '', '', $overrideArray, $addParams, $typeOverride);
1614 }
1615 // Override default target configuration if option is set
1616 if ($this->menuArr[$key]['target']) {
1617 $LD['target'] = $this->menuArr[$key]['target'];
1618 }
1619 // Override URL if using "External URL"
1620 if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_LINK) {
1621 if ($this->menuArr[$key]['urltype'] == 3 && GeneralUtility::validEmail($this->menuArr[$key]['url'])) {
1622 // Create mailto-link using \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink (concerning spamProtectEmailAddresses):
1623 $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $this->menuArr[$key]['url']]);
1624 $LD['target'] = '';
1625 } else {
1626 $LD['totalURL'] = $this->parent_cObj->typoLink_URL(['parameter' => $this->getSysPage()->getExtURL($this->menuArr[$key])]);
1627 }
1628 }
1629
1630 $tsfe = $this->getTypoScriptFrontendController();
1631
1632 // Override url if current page is a shortcut
1633 $shortcut = null;
1634 if ($this->menuArr[$key]['doktype'] == PageRepository::DOKTYPE_SHORTCUT && $this->menuArr[$key]['shortcut_mode'] != PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
1635 $menuItem = $this->determineOriginalShortcutPage($this->menuArr[$key]);
1636 try {
1637 $shortcut = $tsfe->getPageShortcut(
1638 $menuItem['shortcut'],
1639 $menuItem['shortcut_mode'],
1640 $menuItem['uid'],
1641 20,
1642 [],
1643 true
1644 );
1645 } catch (\Exception $ex) {
1646 }
1647 if (!is_array($shortcut)) {
1648 $runtimeCache->set($cacheId, []);
1649 return [];
1650 }
1651 // Only setting url, not target
1652 $LD['totalURL'] = $this->parent_cObj->typoLink_URL([
1653 'parameter' => $shortcut['uid'],
1654 'additionalParams' => $addParams . $this->I['val']['additionalParams'] . $menuItem['_ADD_GETVARS'],
1655 'linkAccessRestrictedPages' => !empty($this->mconf['showAccessRestrictedPages'])
1656 ]);
1657 }
1658 if ($shortcut) {
1659 $pageData = $shortcut;
1660 $pageData['_SHORTCUT_PAGE_UID'] = $this->menuArr[$key]['uid'];
1661 } else {
1662 $pageData = $this->menuArr[$key];
1663 }
1664 // Manipulation in case of access restricted pages:
1665 $this->changeLinksForAccessRestrictedPages($LD, $pageData, $mainTarget, $typeOverride);
1666 // Overriding URL / Target if set to do so:
1667 if ($this->menuArr[$key]['_OVERRIDE_HREF']) {
1668 $LD['totalURL'] = $this->menuArr[$key]['_OVERRIDE_HREF'];
1669 if ($this->menuArr[$key]['_OVERRIDE_TARGET']) {
1670 $LD['target'] = $this->menuArr[$key]['_OVERRIDE_TARGET'];
1671 }
1672 }
1673 // OnClick open in windows.
1674 $onClick = '';
1675 if ($this->mconf['JSWindow']) {
1676 $conf = $this->mconf['JSWindow.'];
1677 $url = $LD['totalURL'];
1678 $LD['totalURL'] = '#';
1679 $onClick = 'openPic('
1680 . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($url)) . ','
1681 . '\'' . ($conf['newWindow'] ? md5($url) : 'theNewPage') . '\','
1682 . GeneralUtility::quoteJSvalue($conf['params']) . '); return false;';
1683 $tsfe->setJS('openPic');
1684 }
1685 // look for type and popup
1686 // following settings are valid in field target:
1687 // 230 will add type=230 to the link
1688 // 230 500x600 will add type=230 to the link and open in popup window with 500x600 pixels
1689 // 230 _blank will add type=230 to the link and open with target "_blank"
1690 // 230x450:resizable=0,location=1 will open in popup window with 500x600 pixels with settings "resizable=0,location=1"
1691 $matches = [];
1692 $targetIsType = $LD['target'] && MathUtility::canBeInterpretedAsInteger($LD['target']) ? (int)$LD['target'] : false;
1693 if (preg_match('/([0-9]+[\\s])?(([0-9]+)x([0-9]+))?(:.+)?/s', $LD['target'], $matches) || $targetIsType) {
1694 // has type?
1695 if ((int)$matches[1] || $targetIsType) {
1696 $LD['totalURL'] .= (strpos($LD['totalURL'], '?') === false ? '?' : '&') . 'type=' . ($targetIsType ?: (int)$matches[1]);
1697 $LD['target'] = $targetIsType ? '' : trim(substr($LD['target'], strlen($matches[1]) + 1));
1698 }
1699 // Open in popup window?
1700 if ($matches[3] && $matches[4]) {
1701 $JSparamWH = 'width=' . $matches[3] . ',height=' . $matches[4] . ($matches[5] ? ',' . substr($matches[5], 1) : '');
1702 $onClick = 'vHWin=window.open('
1703 . GeneralUtility::quoteJSvalue($tsfe->baseUrlWrap($LD['totalURL']))
1704 . ',\'FEopenLink\',' . GeneralUtility::quoteJSvalue($JSparamWH) . ');vHWin.focus();return false;';
1705 $LD['target'] = '';
1706 }
1707 }
1708 // out:
1709 $list = [];
1710 // Added this check: What it does is to enter the baseUrl (if set, which it should for "realurl" based sites)
1711 // as URL if the calculated value is empty. The problem is that no link is generated with a blank URL
1712 // and blank URLs might appear when the realurl encoding is used and a link to the frontpage is generated.
1713 $list['HREF'] = (string)$LD['totalURL'] !== '' ? $LD['totalURL'] : $tsfe->baseUrl;
1714 $list['TARGET'] = $LD['target'];
1715 $list['onClick'] = $onClick;
1716 $runtimeCache->set($cacheId, $list);
1717 return $list;
1718 }
1719
1720 /**
1721 * Determines original shortcut destination in page overlays.
1722 *
1723 * Since the pages records used for menu rendering are overlaid by default,
1724 * the original 'shortcut' value is lost, if a translation did not define one.
1725 *
1726 * @param array $page
1727 * @return array
1728 */
1729 protected function determineOriginalShortcutPage(array $page)
1730 {
1731 // Check if modification is required
1732 if (
1733 $this->getTypoScriptFrontendController()->sys_language_uid > 0
1734 && empty($page['shortcut'])
1735 && !empty($page['uid'])
1736 && !empty($page['_PAGES_OVERLAY'])
1737 && !empty($page['_PAGES_OVERLAY_UID'])
1738 ) {
1739 // Using raw record since the record was overlaid and is correct already:
1740 $originalPage = $this->sys_page->getRawRecord('pages', $page['uid']);
1741
1742 if ($originalPage['shortcut_mode'] === $page['shortcut_mode'] && !empty($originalPage['shortcut'])) {
1743 $page['shortcut'] = $originalPage['shortcut'];
1744 }
1745 }
1746
1747 return $page;
1748 }
1749
1750 /**
1751 * Will change $LD (passed by reference) if the page is access restricted
1752 *
1753 * @param array $LD The array from the linkData() function
1754 * @param array $page Page array
1755 * @param string $mainTarget Main target value
1756 * @param string $typeOverride Type number override if any
1757 */
1758 public function changeLinksForAccessRestrictedPages(&$LD, $page, $mainTarget, $typeOverride)
1759 {
1760 // If access restricted pages should be shown in menus, change the link of such pages to link to a redirection page:
1761 if ($this->mconf['showAccessRestrictedPages'] && $this->mconf['showAccessRestrictedPages'] !== 'NONE' && !$this->getTypoScriptFrontendController()->checkPageGroupAccess($page)) {
1762 $thePage = $this->sys_page->getPage($this->mconf['showAccessRestrictedPages']);
1763 $addParams = str_replace(
1764 [
1765 '###RETURN_URL###',
1766 '###PAGE_ID###'
1767 ],
1768 [
1769 rawurlencode($LD['totalURL']),
1770 isset($page['_SHORTCUT_PAGE_UID']) ? $page['_SHORTCUT_PAGE_UID'] : $page['uid']
1771 ],
1772 $this->mconf['showAccessRestrictedPages.']['addParams']
1773 );
1774 $LD = $this->menuTypoLink($thePage, $mainTarget, '', '', '', $addParams, $typeOverride);
1775 }
1776 }
1777
1778 /**
1779 * Creates a submenu level to the current level - if configured for.
1780 *
1781 * @param int $uid Page id of the current page for which a submenu MAY be produced (if conditions are met)
1782 * @param string $objSuffix Object prefix, see ->start()
1783 * @return string HTML content of the submenu
1784 * @internal
1785 */
1786 public function subMenu($uid, $objSuffix = '')
1787 {
1788 // Setting alternative menu item array if _SUB_MENU has been defined in the current ->menuArr
1789 $altArray = '';
1790 if (is_array($this->menuArr[$this->I['key']]['_SUB_MENU']) && !empty($this->menuArr[$this->I['key']]['_SUB_MENU'])) {
1791 $altArray = $this->menuArr[$this->I['key']]['_SUB_MENU'];
1792 }
1793 // Make submenu if the page is the next active
1794 $menuType = $this->conf[($this->menuNumber + 1) . $objSuffix];
1795 // stdWrap for expAll
1796 if (isset($this->mconf['expAll.'])) {
1797 $this->mconf['expAll'] = $this->parent_cObj->stdWrap($this->mconf['expAll'], $this->mconf['expAll.']);
1798 }
1799 if (($this->mconf['expAll'] || $this->isNext($uid, $this->getMPvar($this->I['key'])) || is_array($altArray)) && !$this->mconf['sectionIndex']) {
1800 try {
1801 $menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
1802 /** @var $submenu AbstractMenuContentObject */
1803 $submenu = $menuObjectFactory->getMenuObjectByType($menuType);
1804 $submenu->entryLevel = $this->entryLevel + 1;
1805 $submenu->rL_uidRegister = $this->rL_uidRegister;
1806 $submenu->MP_array = $this->MP_array;
1807 if ($this->menuArr[$this->I['key']]['_MP_PARAM']) {
1808 $submenu->MP_array[] = $this->menuArr[$this->I['key']]['_MP_PARAM'];
1809 }
1810 // Especially scripts that build the submenu needs the parent data
1811 $submenu->parent_cObj = $this->parent_cObj;
1812 $submenu->setParentMenu($this->menuArr, $this->I['key']);
1813 // Setting alternativeMenuTempArray (will be effective only if an array)
1814 if (is_array($altArray)) {
1815 $submenu->alternativeMenuTempArray = $altArray;
1816 }
1817 if ($submenu->start($this->tmpl, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix)) {
1818 $submenu->makeMenu();
1819 // Memorize the current menu item count
1820 $tsfe = $this->getTypoScriptFrontendController();
1821 $tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1822 // Reset the menu item count for the submenu
1823 $tsfe->register['count_MENUOBJ'] = 0;
1824 $content = $submenu->writeMenu();
1825 // Restore the item count now that the submenu has been handled
1826 $tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1827 $tsfe->register['count_menuItems'] = count($this->menuArr);
1828 return $content;
1829 }
1830 } catch (Exception\NoSuchMenuTypeException $e) {
1831 }
1832 }
1833 return '';
1834 }
1835
1836 /**
1837 * Returns TRUE if the page with UID $uid is the NEXT page in root line (which means a submenu should be drawn)
1838 *
1839 * @param int $uid Page uid to evaluate.
1840 * @param string $MPvar MPvar for the current position of item.
1841 * @return bool TRUE if page with $uid is active
1842 * @internal
1843 * @see subMenu()
1844 */
1845 public function isNext($uid, $MPvar = '')
1846 {
1847 // Check for always active PIDs:
1848 if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1849 return true;
1850 }
1851 $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1852 if ($uid && $testUid == $this->nextActive) {
1853 return true;
1854 }
1855 return false;
1856 }
1857
1858 /**
1859 * Returns TRUE if the page with UID $uid is active (in the current rootline)
1860 *
1861 * @param int $uid Page uid to evaluate.
1862 * @param string $MPvar MPvar for the current position of item.
1863 * @return bool TRUE if page with $uid is active
1864 * @internal
1865 */
1866 public function isActive($uid, $MPvar = '')
1867 {
1868 // Check for always active PIDs:
1869 if (!empty($this->alwaysActivePIDlist) && in_array((int)$uid, $this->alwaysActivePIDlist, true)) {
1870 return true;
1871 }
1872 $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1873 if ($uid && in_array('ITEM:' . $testUid, $this->rL_uidRegister, true)) {
1874 return true;
1875 }
1876 return false;
1877 }
1878
1879 /**
1880 * Returns TRUE if the page with UID $uid is the CURRENT page (equals $this->getTypoScriptFrontendController()->id)
1881 *
1882 * @param int $uid Page uid to evaluate.
1883 * @param string $MPvar MPvar for the current position of item.
1884 * @return bool TRUE if page $uid = $this->getTypoScriptFrontendController()->id
1885 * @internal
1886 */
1887 public function isCurrent($uid, $MPvar = '')
1888 {
1889 $testUid = $uid . ($MPvar ? ':' . $MPvar : '');
1890 return $uid && end($this->rL_uidRegister) === 'ITEM:' . $testUid;
1891 }
1892
1893 /**
1894 * Returns TRUE if there is a submenu with items for the page id, $uid
1895 * Used by the item states "IFSUB", "ACTIFSUB" and "CURIFSUB" to check if there is a submenu
1896 *
1897 * @param int $uid Page uid for which to search for a submenu
1898 * @return bool Returns TRUE if there was a submenu with items found
1899 * @internal
1900 */
1901 public function isSubMenu($uid)
1902 {
1903 $cacheId = 'menucontentobject-is-submenu-decision-' . $uid;
1904 $runtimeCache = $this->getRuntimeCache();
1905 $cachedDecision = $runtimeCache->get($cacheId);
1906 if (isset($cachedDecision['result'])) {
1907 return $cachedDecision['result'];
1908 }
1909 // Looking for a mount-pid for this UID since if that
1910 // exists we should look for a subpages THERE and not in the input $uid;
1911 $mount_info = $this->sys_page->getMountPointInfo($uid);
1912 if (is_array($mount_info)) {
1913 $uid = $mount_info['mount_pid'];
1914 }
1915 $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
1916 $hasSubPages = false;
1917 $bannedUids = $this->getBannedUids();
1918 foreach ($recs as $theRec) {
1919 // no valid subpage if the document type is excluded from the menu
1920 if (GeneralUtility::inList($this->doktypeExcludeList, $theRec['doktype'])) {
1921 continue;
1922 }
1923 // No valid subpage if the page is hidden inside menus and
1924 // it wasn't forced to show such entries
1925 if ($theRec['nav_hide'] && !$this->conf['includeNotInMenu']) {
1926 continue;
1927 }
1928 // No valid subpage if the default language should be shown and the page settings
1929 // are excluding the visibility of the default language
1930 if (!$this->getTypoScriptFrontendController()->sys_language_uid && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
1931 continue;
1932 }
1933 // No valid subpage if the alternative language should be shown and the page settings
1934 // are requiring a valid overlay but it doesn't exists
1935 $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg']);
1936 if ($this->getTypoScriptFrontendController()->sys_language_uid && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
1937 continue;
1938 }
1939 // No valid subpage if the subpage is banned by excludeUidList
1940 if (in_array($theRec['uid'], $bannedUids)) {
1941 continue;
1942 }
1943 $hasSubPages = true;
1944 break;
1945 }
1946 $runtimeCache->set($cacheId, ['result' => $hasSubPages]);
1947 return $hasSubPages;
1948 }
1949
1950 /**
1951 * Used by procesItemStates() to evaluate if a menu item (identified by $key) is in a certain state.
1952 *
1953 * @param string $kind The item state to evaluate (SPC, IFSUB, ACT etc... but no xxxRO states of course)
1954 * @param int $key Key pointing to menu item from ->menuArr
1955 * @return bool Returns TRUE if state matches
1956 * @internal
1957 * @see procesItemStates()
1958 */
1959 public function isItemState($kind, $key)
1960 {
1961 $natVal = false;
1962 // If any value is set for ITEM_STATE the normal evaluation is discarded
1963 if ($this->menuArr[$key]['ITEM_STATE']) {
1964 if ((string)$this->menuArr[$key]['ITEM_STATE'] === (string)$kind) {
1965 $natVal = true;
1966 }
1967 } else {
1968 switch ($kind) {
1969 case 'SPC':
1970 $natVal = (bool)$this->menuArr[$key]['isSpacer'];
1971 break;
1972 case 'IFSUB':
1973 $natVal = $this->isSubMenu($this->menuArr[$key]['uid']);
1974 break;
1975 case 'ACT':
1976 $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key));
1977 break;
1978 case 'ACTIFSUB':
1979 $natVal = $this->isActive($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1980 break;
1981 case 'CUR':
1982 $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key));
1983 break;
1984 case 'CURIFSUB':
1985 $natVal = $this->isCurrent($this->menuArr[$key]['uid'], $this->getMPvar($key)) && $this->isSubMenu($this->menuArr[$key]['uid']);
1986 break;
1987 case 'USR':
1988 $natVal = (bool)$this->menuArr[$key]['fe_group'];
1989 break;
1990 }
1991 }
1992 return $natVal;
1993 }
1994
1995 /**
1996 * Creates an access-key for a TMENU/GMENU menu item based on the menu item titles first letter
1997 *
1998 * @param string $title Menu item title.
1999 * @return array Returns an array with keys "code" ("accesskey" attribute for the img-tag) and "alt" (text-addition to the "alt" attribute) if an access key was defined. Otherwise array was empty
2000 * @internal
2001 */
2002 public function accessKey($title)
2003 {
2004 $tsfe = $this->getTypoScriptFrontendController();
2005 // The global array ACCESSKEY is used to globally control if letters are already used!!
2006 $result = [];
2007 $title = trim(strip_tags($title));
2008 $titleLen = strlen($title);
2009 for ($a = 0; $a < $titleLen; $a++) {
2010 $key = strtoupper(substr($title, $a, 1));
2011 if (preg_match('/[A-Z]/', $key) && !isset($tsfe->accessKey[$key])) {
2012 $tsfe->accessKey[$key] = 1;
2013 $result['code'] = ' accesskey="' . $key . '"';
2014 $result['alt'] = ' (ALT+' . $key . ')';
2015 $result['key'] = $key;
2016 break;
2017 }
2018 }
2019 return $result;
2020 }
2021
2022 /**
2023 * Calls a user function for processing of internal data.
2024 * Used for the properties "IProcFunc" and "itemArrayProcFunc"
2025 *
2026 * @param string $mConfKey Key pointing for the property in the current ->mconf array holding possibly parameters to pass along to the function/method. Currently the keys used are "IProcFunc" and "itemArrayProcFunc".
2027 * @param mixed $passVar A variable to pass to the user function and which should be returned again from the user function. The idea is that the user function modifies this variable according to what you want to achieve and then returns it. For "itemArrayProcFunc" this variable is $this->menuArr, for "IProcFunc" it is $this->I
2028 * @return mixed The processed $passVar
2029 * @internal
2030 */
2031 public function userProcess($mConfKey, $passVar)
2032 {
2033 if ($this->mconf[$mConfKey]) {
2034 $funcConf = $this->mconf[$mConfKey . '.'];
2035 $funcConf['parentObj'] = $this;
2036 $passVar = $this->parent_cObj->callUserFunction($this->mconf[$mConfKey], $funcConf, $passVar);
2037 }
2038 return $passVar;
2039 }
2040
2041 /**
2042 * Creates the <A> tag parts for the current item (in $this->I, [A1] and [A2]) based on other information in this array (like $this->I['linkHREF'])
2043 *
2044 * @internal
2045 */
2046 public function setATagParts()
2047 {
2048 $params = trim($this->I['val']['ATagParams']) . $this->I['accessKey']['code'];
2049 $params = $params !== '' ? ' ' . $params : '';
2050 $this->I['A1'] = '<a ' . GeneralUtility::implodeAttributes($this->I['linkHREF'], true) . $params . '>';
2051 $this->I['A2'] = '</a>';
2052 }
2053
2054 /**
2055 * Returns the title for the navigation
2056 *
2057 * @param string $title The current page title
2058 * @param string $nav_title The current value of the navigation title
2059 * @return string Returns the navigation title if it is NOT blank, otherwise the page title.
2060 * @internal
2061 */
2062 public function getPageTitle($title, $nav_title)
2063 {
2064 return trim($nav_title) !== '' ? $nav_title : $title;
2065 }
2066
2067 /**
2068 * Return MPvar string for entry $key in ->menuArr
2069 *
2070 * @param int $key Pointer to element in ->menuArr
2071 * @return string MP vars for element.
2072 * @see link()
2073 */
2074 public function getMPvar($key)
2075 {
2076 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
2077 $localMP_array = $this->MP_array;
2078 // NOTICE: "_MP_PARAM" is allowed to be a commalist of PID pairs!
2079 if ($this->menuArr[$key]['_MP_PARAM']) {
2080 $localMP_array[] = $this->menuArr[$key]['_MP_PARAM'];
2081 }
2082 return !empty($localMP_array) ? implode(',', $localMP_array) : '';
2083 }
2084 return '';
2085 }
2086
2087 /**
2088 * Returns where clause part to exclude 'not in menu' pages
2089 *
2090 * @return string where clause part.
2091 * @internal
2092 */
2093 public function getDoktypeExcludeWhere()
2094 {
2095 return $this->doktypeExcludeList ? ' AND pages.doktype NOT IN (' . $this->doktypeExcludeList . ')' : '';
2096 }
2097
2098 /**
2099 * Returns an array of banned UIDs (from excludeUidList)
2100 *
2101 * @return array Array of banned UIDs
2102 * @internal
2103 */
2104 public function getBannedUids()
2105 {
2106 $excludeUidList = isset($this->conf['excludeUidList.'])
2107 ? $this->parent_cObj->stdWrap($this->conf['excludeUidList'], $this->conf['excludeUidList.'])
2108 : $this->conf['excludeUidList'];
2109
2110 if (!trim($excludeUidList)) {
2111 return [];
2112 }
2113
2114 $banUidList = str_replace('current', $this->getTypoScriptFrontendController()->page['uid'], $excludeUidList);
2115 return GeneralUtility::intExplode(',', $banUidList);
2116 }
2117
2118 /**
2119 * Calls typolink to create menu item links.
2120 *
2121 * @param array $page Page record (uid points where to link to)
2122 * @param string $oTarget Target frame/window
2123 * @param bool $no_cache TRUE if caching should be disabled
2124 * @param string $script Alternative script name (unused)
2125 * @param array|string $overrideArray Array to override values in $page, empty string to skip override
2126 * @param string $addParams Parameters to add to URL
2127 * @param int|string $typeOverride "type" value, empty string means "not set"
2128 * @return array See linkData
2129 */
2130 public function menuTypoLink($page, $oTarget, $no_cache, $script, $overrideArray = '', $addParams = '', $typeOverride = '')
2131 {
2132 $conf = [
2133 'parameter' => is_array($overrideArray) && $overrideArray['uid'] ? $overrideArray['uid'] : $page['uid']
2134 ];
2135 if (MathUtility::canBeInterpretedAsInteger($typeOverride)) {
2136 $conf['parameter'] .= ',' . (int)$typeOverride;
2137 }
2138 if ($addParams) {
2139 $conf['additionalParams'] = $addParams;
2140 }
2141 if ($no_cache) {
2142 $conf['no_cache'] = true;
2143 } elseif ($this->useCacheHash) {
2144 $conf['useCacheHash'] = true;
2145 }
2146 if ($oTarget) {
2147 $conf['target'] = $oTarget;
2148 }
2149 if ($page['sectionIndex_uid']) {
2150 $conf['section'] = $page['sectionIndex_uid'];
2151 }
2152 $conf['linkAccessRestrictedPages'] = !empty($this->mconf['showAccessRestrictedPages']);
2153 $this->parent_cObj->typoLink('|', $conf);
2154 $LD = $this->parent_cObj->lastTypoLinkLD;
2155 $LD['totalURL'] = $this->parent_cObj->lastTypoLinkUrl;
2156 return $LD;
2157 }
2158
2159 /**
2160 * Generates a list of content objects with sectionIndex enabled
2161 * available on a specific page
2162 *
2163 * Used for menus with sectionIndex enabled
2164 *
2165 * @param string $altSortField Alternative sorting field
2166 * @param int $pid The page id to search for sections
2167 * @throws \UnexpectedValueException if the query to fetch the content elements unexpectedly fails
2168 * @return array
2169 */
2170 protected function sectionIndex($altSortField, $pid = null)
2171 {
2172 $pid = (int)($pid ?: $this->id);
2173 $basePageRow = $this->sys_page->getPage($pid);
2174 if (!is_array($basePageRow)) {
2175 return [];
2176 }
2177 $tsfe = $this->getTypoScriptFrontendController();
2178 $configuration = $this->mconf['sectionIndex.'];
2179 $useColPos = 0;
2180 if (trim($configuration['useColPos']) !== '' || is_array($configuration['useColPos.'])) {
2181 $useColPos = $tsfe->cObj->stdWrap($configuration['useColPos'], $configuration['useColPos.']);
2182 $useColPos = (int)$useColPos;
2183 }
2184 $selectSetup = [
2185 'pidInList' => $pid,
2186 'orderBy' => $altSortField,
2187 'languageField' => 'sys_language_uid',
2188 'where' => ''
2189 ];
2190
2191 if ($useColPos >= 0) {
2192 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2193 ->getConnectionForTable('tt_content')
2194 ->getExpressionBuilder();
2195 $selectSetup['where'] = $expressionBuilder->eq('colPos', $useColPos);
2196 }
2197
2198 if ($basePageRow['content_from_pid']) {
2199 // If the page is configured to show content from a referenced page the sectionIndex contains only contents of
2200 // the referenced page
2201 $selectSetup['pidInList'] = $basePageRow['content_from_pid'];
2202 }
2203 $statement = $this->parent_cObj->exec_getQuery('tt_content', $selectSetup);
2204 if (!$statement) {
2205 $message = 'SectionIndex: Query to fetch the content elements failed!';
2206 throw new \UnexpectedValueException($message, 1337334849);
2207 }
2208 $result = [];
2209 while ($row = $statement->fetch()) {
2210 $this->sys_page->versionOL('tt_content', $row);
2211 if ($tsfe->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
2212 $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL);
2213 }
2214 if ($this->mconf['sectionIndex.']['type'] !== 'all') {
2215 $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
2216 $doHeaderCheck = $this->mconf['sectionIndex.']['type'] === 'header';
2217 $isValidHeader = ((int)$row['header_layout'] !== 100 || !empty($this->mconf['sectionIndex.']['includeHiddenHeaders'])) && trim($row['header']) !== '';
2218 if (!$doIncludeInSectionIndex || $doHeaderCheck && !$isValidHeader) {
2219 continue;
2220 }
2221 }
2222 if (is_array($row)) {
2223 $uid = $row['uid'];
2224 $result[$uid] = $basePageRow;
2225 $result[$uid]['title'] = $row['header'];
2226 $result[$uid]['nav_title'] = $row['header'];
2227 // Prevent false exclusion in filterMenuPages, thus: Always show tt_content records
2228 $result[$uid]['nav_hide'] = 0;
2229 $result[$uid]['subtitle'] = $row['subheader'];
2230 $result[$uid]['starttime'] = $row['starttime'];
2231 $result[$uid]['endtime'] = $row['endtime'];
2232 $result[$uid]['fe_group'] = $row['fe_group'];
2233 $result[$uid]['media'] = $row['media'];
2234 $result[$uid]['header_layout'] = $row['header_layout'];
2235 $result[$uid]['bodytext'] = $row['bodytext'];
2236 $result[$uid]['image'] = $row['image'];
2237 $result[$uid]['sectionIndex_uid'] = $uid;
2238 }
2239 }
2240
2241 return $result;
2242 }
2243
2244 /**
2245 * Returns the sys_page object
2246 *
2247 * @return \TYPO3\CMS\Frontend\Page\PageRepository
2248 */
2249 public function getSysPage()
2250 {
2251 return $this->sys_page;
2252 }
2253
2254 /**
2255 * Returns the parent content object
2256 *
2257 * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
2258 */
2259 public function getParentContentObject()
2260 {
2261 return $this->parent_cObj;
2262 }
2263
2264 /**
2265 * @return TypoScriptFrontendController
2266 */
2267 protected function getTypoScriptFrontendController()
2268 {
2269 return $GLOBALS['TSFE'];
2270 }
2271
2272 /**
2273 * @return TimeTracker
2274 */
2275 protected function getTimeTracker()
2276 {
2277 return GeneralUtility::makeInstance(TimeTracker::class);
2278 }
2279
2280 /**
2281 * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
2282 */
2283 protected function getCache()
2284 {
2285 return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
2286 }
2287
2288 /**
2289 * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
2290 */
2291 protected function getRuntimeCache()
2292 {
2293 return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
2294 }
2295
2296 /**
2297 * Set the parentMenuArr and key to provide the parentMenu informations to the
2298 * subMenu, special fur IProcFunc and itemArrayProcFunc user functions.
2299 *
2300 * @param array $menuArr
2301 * @param int $menuItemKey
2302 * @internal
2303 */
2304 public function setParentMenu(array $menuArr = [], $menuItemKey)
2305 {
2306 // check if menuArr is a valid array and that menuItemKey matches an existing menuItem in menuArr
2307 if (is_array($menuArr)
2308 && (is_int($menuItemKey) && $menuItemKey >= 0 && isset($menuArr[$menuItemKey]))
2309 ) {
2310 $this->parentMenuArr = $menuArr;
2311 $this->parentMenuArrItemKey = $menuItemKey;
2312 }
2313 }
2314
2315 /**
2316 * Check if there is an valid parentMenuArr.
2317 *
2318 * @return bool
2319 */
2320 protected function hasParentMenuArr()
2321 {
2322 return
2323 $this->menuNumber > 1
2324 && is_array($this->parentMenuArr)
2325 && !empty($this->parentMenuArr)
2326 ;
2327 }
2328
2329 /**
2330 * Check if we have an parentMenutArrItemKey
2331 */
2332 protected function hasParentMenuItemKey()
2333 {
2334 return null !== $this->parentMenuArrItemKey;
2335 }
2336
2337 /**
2338 * Check if the the parentMenuItem exists
2339 */
2340 protected function hasParentMenuItem()
2341 {
2342 return
2343 $this->hasParentMenuArr()
2344 && $this->hasParentMenuItemKey()
2345 && isset($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2346 ;
2347 }
2348
2349 /**
2350 * Get the parentMenuArr, if this is subMenu.
2351 *
2352 * @return array
2353 */
2354 public function getParentMenuArr()
2355 {
2356 return $this->hasParentMenuArr() ? $this->parentMenuArr : [];
2357 }
2358
2359 /**
2360 * Get the parentMenuItem from the parentMenuArr, if this is a subMenu
2361 *
2362 * @return null|array
2363 */
2364 public function getParentMenuItem()
2365 {
2366 // check if we have an parentMenuItem and if it is an array
2367 if ($this->hasParentMenuItem()
2368 && is_array($this->getParentMenuArr()[$this->parentMenuArrItemKey])
2369 ) {
2370 return $this->getParentMenuArr()[$this->parentMenuArrItemKey];
2371 }
2372
2373 return null;
2374 }
2375 }