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