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