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